Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graphql responses are now http 200 (fix #1368) #2064

Merged
merged 23 commits into from May 10, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2e194ac
fix all graphql responses to be http 200 (fix #1368)
ecthiender Apr 24, 2019
daed9e6
run all graphql tests on both graphql endpoints
ecthiender Apr 25, 2019
7507cf2
fix faulty test spec (did not have response key)
ecthiender Apr 25, 2019
8c34961
add docs
ecthiender Apr 25, 2019
9f1e04c
add v1/graphql/explain endpoint
ecthiender Apr 26, 2019
54badc9
Merge branch 'master' into fix-1368
shahidhk Apr 26, 2019
936493f
Merge branch 'master' into fix-1368
shahidhk Apr 30, 2019
1de10db
docs url changes
shahidhk Apr 30, 2019
f12ca6f
community content url changes
shahidhk Apr 30, 2019
aa5ba9f
install-manifests url changes
shahidhk Apr 30, 2019
a6bae57
Merge branch 'master' into fix-1368
shahidhk Apr 30, 2019
e65599b
Merge branch 'master' into fix-1368
shahidhk Apr 30, 2019
1db897a
fix tests
shahidhk Apr 30, 2019
ae72963
use graphql compliant errors in websocket
ecthiender May 2, 2019
d80356b
fix nested errors keys
shahidhk May 2, 2019
8010d0d
change response type for validation errors
shahidhk May 2, 2019
979dc2d
add start failed error
shahidhk May 3, 2019
41e581c
handle both graphql endpoints for websocket transport
ecthiender May 3, 2019
d2470ae
fix tests for v1alpha1/graphql and v1/graphql
ecthiender May 7, 2019
1531ed9
Merge branch 'master' of github.com:hasura/graphql-engine into fix-1368
ecthiender May 7, 2019
eedc41f
fix review comments and test
ecthiender May 8, 2019
bd16413
Merge branch 'master' into fix-1368
0x777 May 9, 2019
246ff07
Merge branch 'master' into fix-1368
0x777 May 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/graphql/manual/api-reference/index.rst
Expand Up @@ -11,7 +11,14 @@ GraphQL API

All GraphQL requests for queries, subscriptions and mutations are made to the GraphQL API.

All requests are ``POST`` requests to the ``/v1alpha1/graphql`` endpoint.
All requests are ``POST`` requests to the ``/v1alpha1/graphql`` and ``/v1/graphql`` endpoint.

.. note::

``/v1/graphql`` endpoint returns HTTP 200 status codes for all responses.
This is a **breaking** change from ``/v1alpha1/graphql`` behaviour, where
request errors and internal errors were responded with 4xx and 5xx status
codes.

Request types
^^^^^^^^^^^^^
Expand Down
8 changes: 6 additions & 2 deletions server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs
Expand Up @@ -155,8 +155,12 @@ onConn (L.Logger logger) corsPolicy wsId requestHead = do
(BL.toStrict $ J.encode $ encodeGQLErr False qErr)

checkPath =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkPath should return ErrRespType. You don't have to look at paths twice.

when (WS.requestPath requestHead /= "/v1alpha1/graphql") $
throw404 "only /v1alpha1/graphql is supported on websockets"
when (WS.requestPath requestHead `notElem` allowedPaths) $
throw404 $ "only [" <> T.intercalate ", " allowedPaths
<> "] is supported on websockets"

allowedPaths :: (IsString a) => [a]
allowedPaths = ["/v1alpha1/graphql", "/v1/graphql"]

getOrigin =
find ((==) "Origin" . fst) (WS.requestHeaders requestHead)
Expand Down
47 changes: 30 additions & 17 deletions server/src-lib/Hasura/Server/App.hs
Expand Up @@ -193,10 +193,11 @@ logError userInfoM req reqBody sc qErr =
mkSpockAction
:: (MonadIO m)
=> (Bool -> QErr -> Value)
-> (QErr -> QErr)
-> ServerCtx
-> Handler EncJSON
-> ActionT m ()
mkSpockAction qErrEncoder serverCtx handler = do
mkSpockAction qErrEncoder qErrModifier serverCtx handler = do
req <- request
reqBody <- liftIO $ strictRequestBody req
let headers = requestHeaders req
Expand All @@ -212,7 +213,7 @@ mkSpockAction qErrEncoder serverCtx handler = do
result <- liftIO $ runReaderT (runExceptT handler) handlerState
t2 <- liftIO getCurrentTime -- for measuring response time purposes

let resLBS = fmap encJToLBS result
let resLBS = fmapL qErrModifier $ fmap encJToLBS result

-- log result
logResult (Just userInfo) req reqBody serverCtx resLBS $ Just (t1, t2)
Expand Down Expand Up @@ -273,6 +274,9 @@ v1Alpha1GQHandler query = do
GH.runGQ pgExecCtx userInfo sqlGenCtx planCache
sc scVer manager reqHeaders query reqBody

v1GQHandler :: GH.GQLReqUnparsed -> Handler EncJSON
v1GQHandler = v1Alpha1GQHandler

gqlExplainHandler :: GE.GQLExplain -> Handler EncJSON
gqlExplainHandler query = do
onlyAdmin
Expand Down Expand Up @@ -375,13 +379,9 @@ httpApp corsCfg serverCtx enableConsole enableTelemetry = do
-- Health check endpoint
get "healthz" $ do
sc <- liftIO $ getSCFromRef $ scCacheRef serverCtx
let reportOK = do
setStatus N.status200
lazyBytes "OK"
reportError = do
setStatus N.status500
lazyBytes "ERROR"
bool reportError reportOK $ null $ scInconsistentObjs sc
if null $ scInconsistentObjs sc
then setStatus N.status200 >> lazyBytes "OK"
else setStatus N.status500 >> lazyBytes "ERROR"

get "v1/version" $ do
uncurry setHeader jsonHeader
Expand All @@ -393,23 +393,28 @@ httpApp corsCfg serverCtx enableConsole enableTelemetry = do
put ("v1/template" <//> var) tmpltPutOrPostH
delete ("v1/template" <//> var) tmpltGetOrDeleteH

post "v1/query" $ mkSpockAction encodeQErr serverCtx $ do
post "v1/query" $ mkSpockAction encodeQErr id serverCtx $ do
query <- parseBody
v1QueryHandler query

post ("api/1/table" <//> var <//> var) $ \tableName queryType ->
mkSpockAction encodeQErr serverCtx $
mkSpockAction encodeQErr id serverCtx $
legacyQueryHandler (TableName tableName) queryType

when enableGraphQL $ do
post "v1alpha1/graphql/explain" $ mkSpockAction encodeQErr serverCtx $ do
expQuery <- parseBody
gqlExplainHandler expQuery
post "v1alpha1/graphql/explain" gqlExplainAction

post "v1alpha1/graphql" $ mkSpockAction GH.encodeGQErr serverCtx $ do
post "v1alpha1/graphql" $ mkSpockAction GH.encodeGQErr id serverCtx $ do
query <- parseBody
v1Alpha1GQHandler query

post "v1/graphql/explain" gqlExplainAction

post "v1/graphql" $ mkSpockAction GH.encodeGQErr allMod200 serverCtx $ do
query <- parseBody
v1GQHandler query


#ifdef InternalAPIs
get "internal/plan_cache" $ do
respJ <- liftIO $ E.dumpPlanCache $ scPlanCache serverCtx
Expand All @@ -424,15 +429,23 @@ httpApp corsCfg serverCtx enableConsole enableTelemetry = do
raiseGenericApiError qErr

where
-- all graphql errors should be of type 200
allMod200 qe = qe { qeStatus = N.status200 }

gqlExplainAction =
mkSpockAction encodeQErr id serverCtx $ do
expQuery <- parseBody
gqlExplainHandler expQuery

enableGraphQL = isGraphQLEnabled serverCtx
enableMetadata = isMetadataEnabled serverCtx
tmpltGetOrDeleteH tmpltName = do
tmpltArgs <- tmpltArgsFromQueryParams
mkSpockAction encodeQErr serverCtx $ mkQTemplateAction tmpltName tmpltArgs
mkSpockAction encodeQErr id serverCtx $ mkQTemplateAction tmpltName tmpltArgs

tmpltPutOrPostH tmpltName = do
tmpltArgs <- tmpltArgsFromQueryParams
mkSpockAction encodeQErr serverCtx $ do
mkSpockAction encodeQErr id serverCtx $ do
bodyTmpltArgs <- parseBody
mkQTemplateAction tmpltName $ M.union bodyTmpltArgs tmpltArgs

Expand Down
Expand Up @@ -4,6 +4,12 @@
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
response:
errors:
- extensions:
path: $.selectionSet.author.selectionSet.remarks_internal
code: validation-failed
message: "field \"remarks_internal\" not found in type: 'author'"
query:
query: |
query {
Expand Down
9 changes: 5 additions & 4 deletions server/tests-py/test_apis_disabled.py
Expand Up @@ -27,13 +27,14 @@ def test_metadata_api_1_disabled(self, hge_ctx):

@pytest.mark.skipif(not pytest.config.getoption("--test-graphql-disabled"),
reason="--test-graphql-disabled is not set. Cannot run GraphQL disabled tests")
@pytest.mark.parametrize('endpoint', ['/v1alpha1/graphql', '/v1/graphql'])
class TestGraphQLDisabled:

def test_graphql_endpoint_disabled(self, hge_ctx):
check_post_404(hge_ctx,'/v1alpha1/graphql')
def test_graphql_endpoint_disabled(self, hge_ctx, endpoint):
check_post_404(hge_ctx, endpoint)

def test_graphql_explain_disabled(self, hge_ctx):
check_post_404(hge_ctx,'/v1alpha1/graphql/explain')
def test_graphql_explain_disabled(self, hge_ctx, endpoint):
check_post_404(hge_ctx, endpoint + '/explain')


@pytest.mark.skipif(pytest.config.getoption("--test-graphql-disabled"),
Expand Down