-
Notifications
You must be signed in to change notification settings - Fork 33
Top level API #76
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
Top level API #76
Changes from all commits
f85c415
3315cca
4b75952
35c4617
5f7b344
e7ca74e
88056e4
fb0df87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,26 @@ | ||
| {-# LANGUAGE AllowAmbiguousTypes #-} | ||
| {-# LANGUAGE PatternSynonyms #-} | ||
| {-# LANGUAGE RankNTypes #-} | ||
| {-# LANGUAGE ScopedTypeVariables #-} | ||
| -- | Interface for GraphQL API. | ||
| -- | ||
| -- __Note__: This module is highly subject to change. We're still figuring | ||
| -- where to draw the lines and what to expose. | ||
| module GraphQL | ||
| ( QueryError | ||
| , SelectionSet | ||
| , Response(..) | ||
| , VariableValues | ||
| , Value | ||
| , getOperation | ||
| , compileQuery | ||
| , executeQuery | ||
| , interpretQuery | ||
| , interpretAnonymousQuery | ||
| ) where | ||
|
|
||
| import Protolude | ||
|
|
||
| import Data.Attoparsec.Text (parseOnly, endOfInput) | ||
| import Data.List.NonEmpty (NonEmpty(..)) | ||
| import qualified Data.List.NonEmpty as NonEmpty | ||
| import qualified GraphQL.Internal.AST as AST | ||
| import GraphQL.Internal.Execution | ||
|
|
@@ -31,8 +38,14 @@ import GraphQL.Internal.Validation | |
| , getSelectionSet | ||
| , VariableValue | ||
| ) | ||
| import GraphQL.Internal.Output (GraphQLError(..)) | ||
| import GraphQL.Value (Value) | ||
| import GraphQL.Internal.Output | ||
| ( GraphQLError(..) | ||
| , Error(..) | ||
| , Response(..) | ||
| , singleError | ||
| ) | ||
| import GraphQL.Resolver (HasGraph(..), Result(..)) | ||
| import GraphQL.Value (Name, Value, pattern ValueObject) | ||
|
|
||
| -- | Errors that can happen while processing a query document. | ||
| data QueryError | ||
|
|
@@ -45,6 +58,8 @@ data QueryError | |
| | ValidationError ValidationErrors | ||
| -- | Validated, but failed during execution. | ||
| | ExecutionError ExecutionError | ||
| -- | Got a value that wasn't an object. | ||
| | NonObjectResult Value | ||
| deriving (Eq, Show) | ||
|
|
||
| instance GraphQLError QueryError where | ||
|
|
@@ -54,6 +69,61 @@ instance GraphQLError QueryError where | |
| "Validation errors:\n" <> mconcat [" " <> formatError e <> "\n" | e <- NonEmpty.toList es] | ||
| formatError (ExecutionError e) = | ||
| "Execution error: " <> show e | ||
| formatError (NonObjectResult v) = | ||
| "Query returned a value that is not an object: " <> show v | ||
|
|
||
| -- | Execute a GraphQL query. | ||
| executeQuery | ||
| :: forall api m. (HasGraph m api, Applicative m) | ||
| => Handler m api -- ^ Handler for the query. This links the query to the code you've written to handle it. | ||
| -> QueryDocument VariableValue -- ^ A validated query document. Build one with 'compileQuery'. | ||
| -> Maybe Name -- ^ An optional name. If 'Nothing', then executes the only operation in the query. If @Just "something"@, executes the query named @"something". | ||
| -> VariableValues -- ^ Values for variables defined in the query document. A map of 'Variable' to 'Value'. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add how to construct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I started to do this and realised that we don't have a great story for it. Separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm looking for how to accomplish this now, but don't see any new information. Is the mentioned PR available? |
||
| -> m Response -- ^ The outcome of running the query. | ||
| executeQuery handler document name variables = | ||
| case getOperation document name variables of | ||
| Left e -> pure (ExecutionFailure (singleError e)) | ||
| Right operation -> | ||
| toResult <$> buildResolver @m @api handler operation | ||
| where | ||
| toResult (Result errors result) = | ||
| case result of | ||
| -- TODO: Prevent this at compile time. | ||
| ValueObject object -> | ||
| case NonEmpty.nonEmpty errors of | ||
| Nothing -> Success object | ||
| Just errs -> PartialSuccess object (map toError errs) | ||
| v -> ExecutionFailure (singleError (NonObjectResult v)) | ||
|
|
||
| -- | Interpet a GraphQL query. | ||
| -- | ||
| -- Compiles then executes a GraphQL query. | ||
| interpretQuery | ||
| :: forall api m. (Applicative m, HasGraph m api) | ||
| => Handler m api -- ^ Handler for the query. This links the query to the code you've written to handle it. | ||
| -> Text -- ^ The text of a query document. Will be parsed and then executed. | ||
| -> Maybe Name -- ^ An optional name for the operation within document to run. If 'Nothing', execute the only operation in the document. If @Just "something"@, execute the query or mutation named @"something"@. | ||
| -> VariableValues -- ^ Values for variables defined in the query document. A map of 'Variable' to 'Value'. | ||
| -> m Response -- ^ The outcome of running the query. | ||
| interpretQuery handler query name variables = | ||
| case parseQuery query of | ||
| Left err -> pure (PreExecutionFailure (Error err [] :| [])) | ||
| Right parsed -> | ||
| case validate parsed of | ||
| Left errs -> pure (PreExecutionFailure (map toError errs)) | ||
| Right document -> | ||
| executeQuery @api @m handler document name variables | ||
|
|
||
|
|
||
| -- | Interpret an anonymous GraphQL query. | ||
| -- | ||
| -- Anonymous queries have no name and take no variables. | ||
| interpretAnonymousQuery | ||
| :: forall api m. (Applicative m, HasGraph m api) | ||
| => Handler m api -- ^ Handler for the anonymous query. | ||
| -> Text -- ^ The text of the anonymous query. Should defined only a single, unnamed query operation. | ||
| -> m Response -- ^ The result of running the query. | ||
| interpretAnonymousQuery handler query = interpretQuery @api @m handler query Nothing mempty | ||
|
|
||
| -- | Turn some text into a valid query document. | ||
| compileQuery :: Text -> Either QueryError (QueryDocument VariableValue) | ||
|
|
@@ -66,10 +136,7 @@ parseQuery :: Text -> Either Text AST.QueryDocument | |
| parseQuery query = first toS (parseOnly (Parser.queryDocument <* endOfInput) query) | ||
|
|
||
| -- | Get an operation from a query document ready to be processed. | ||
| -- | ||
| -- TODO: Open question whether we want to export this to the end-user. If we | ||
| -- do, it should probably not be in first position. | ||
| getOperation :: QueryDocument VariableValue -> Maybe AST.Name -> VariableValues -> Either QueryError (SelectionSet Value) | ||
| getOperation :: QueryDocument VariableValue -> Maybe Name -> VariableValues -> Either QueryError (SelectionSet Value) | ||
| getOperation document name vars = first ExecutionError $ do | ||
| op <- Execution.getOperation document name | ||
| resolved <- substituteVariables op vars | ||
|
|
||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed adding an
Object n i f ~ apiconstraint to make sureexecuteQuerycan run only on Objects. Up to you.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate PR? Also, overnight I thought that that an
IsQueryRootconstraint (with aresolveRoot :: Handler m a -> m Objectmethod) wouldn't be such a bad thing.