haskell-graphql / graphql-api

Write type-safe GraphQL services in Haskell
BSD 3-Clause "New" or "Revised" License
406 stars 35 forks source link

Provide a way to pass initialValue between resolver calls #209

Open theobat opened 5 years ago

theobat commented 5 years ago

The signature of a resolver in the spec is the following:

// for mutations
ExecuteMutation(mutation, schema, variableValues, initialValue)
// for queries
ExecuteQuery(query, schema, variableValues, initialValue)
// for fields
ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)

source for queries source for mutations source for fields

As far as I understand (and as mentioned in #203) there is not concept of initialValuein graphql-api yet. This issue should be used to study the changes required to get compliant with the spec on those signatures (or, at least closer).

theobat commented 5 years ago

So turns out a good way to provide such thing is to just treat it like any other argument in the exposed API, for instance if we want to pass an arbitrary JSON AST down all the handlers we can do something like:

-- | Interpet a GraphQL query.
--
-- Compiles then executes a GraphQL query.
testInitialValue
  :: forall api m. (Applicative m, HasResolver m api, HasObjectDefinition api)
  => (Aeson.Object -> 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.
testInitialValue handler query name variables =
  case makeSchema @api >>= flip compileQuery query of
    Left err -> pure (PreExecutionFailure (toError err :| []))
    Right document -> executeQuery @api @m (handler mempty) document name variables

The important part here beeing:

(Aeson.Object -> Handler m api) -- ^ Handler for the query. This links the query to the code you've written to handle it.

Thus the handlers will have access to any json (which itself could be the result of a function on the validated graphql AST). like:

root :: Aeson.Object -> Handler IO Query
root initialValue = do
    _ <- print (JSONText.renderObject initialValue)
    pure $ organizationList (lookup ""organizationList" initialValue) :<> plantList

-- and then organizationList is a handler itself and so on

The problem here is that the spec's signature is not typed, and handling json ASTS everywhere is not really a joy in haskell, but it works.

For anyone wondering, my use case is a library between graphql-api's validated AST and beam-postgres This solution is good enough for me, but perhaps not for subscriptions.