Resolve abstract types
Objects that can be a part of an union or interface should return __typename as string.
This commit is contained in:
parent
d12577ae71
commit
93a0403288
@ -17,8 +17,8 @@ and this project adheres to
|
|||||||
* Invalid (recusrive or non-existing) fragments should be skipped.
|
* Invalid (recusrive or non-existing) fragments should be skipped.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- `Schema.Resolver` cannot return arbitrary JSON anymore, but only
|
- `Schema.Resolver` was moved to `Type.Out`, it is a field and resolver function
|
||||||
`Type.Definition.Value`.
|
pair.
|
||||||
- `AST.Core.Value` was moved into `Type.Definition`. These values are used only
|
- `AST.Core.Value` was moved into `Type.Definition`. These values are used only
|
||||||
in the execution and type system, it is not a part of the parsing tree.
|
in the execution and type system, it is not a part of the parsing tree.
|
||||||
- `Type` module is superseded by `Type.Out`. This module contains now only
|
- `Type` module is superseded by `Type.Out`. This module contains now only
|
||||||
@ -46,6 +46,7 @@ and this project adheres to
|
|||||||
- `Schema.wrappedObject`, `Schema.object`, `Schema.resolversToMap`. There is no
|
- `Schema.wrappedObject`, `Schema.object`, `Schema.resolversToMap`. There is no
|
||||||
need in special functions to construct field resolvers anymore, resolvers are
|
need in special functions to construct field resolvers anymore, resolvers are
|
||||||
normal functions attached to the fields in the schema representation.
|
normal functions attached to the fields in the schema representation.
|
||||||
|
- `Schema.resolve` is superseded by `Execute.Execution`.
|
||||||
- `Error.runAppendErrs` isn't used anywhere.
|
- `Error.runAppendErrs` isn't used anywhere.
|
||||||
- `AST.Core`: `Document`, `Directive`, `Field`, `Fragment`, `Selection`, `Alias`
|
- `AST.Core`: `Document`, `Directive`, `Field`, `Fragment`, `Selection`, `Alias`
|
||||||
`TypeCondition` were modified, moved into `Execute.Transform.Document` and
|
`TypeCondition` were modified, moved into `Execute.Transform.Document` and
|
||||||
|
@ -42,7 +42,10 @@ First we build a GraphQL schema.
|
|||||||
> queryType :: ObjectType IO
|
> queryType :: ObjectType IO
|
||||||
> queryType = ObjectType "Query" Nothing []
|
> queryType = ObjectType "Query" Nothing []
|
||||||
> $ HashMap.singleton "hello"
|
> $ HashMap.singleton "hello"
|
||||||
> $ Field Nothing (Out.NamedScalarType string) mempty hello
|
> $ Out.Resolver helloField hello
|
||||||
|
>
|
||||||
|
> helloField :: Field IO
|
||||||
|
> helloField = Field Nothing (Out.NamedScalarType string) mempty
|
||||||
>
|
>
|
||||||
> hello :: ActionT IO Value
|
> hello :: ActionT IO Value
|
||||||
> hello = pure $ String "it's me"
|
> hello = pure $ String "it's me"
|
||||||
@ -77,7 +80,10 @@ For this example, we're going to be using time.
|
|||||||
> queryType2 :: ObjectType IO
|
> queryType2 :: ObjectType IO
|
||||||
> queryType2 = ObjectType "Query" Nothing []
|
> queryType2 = ObjectType "Query" Nothing []
|
||||||
> $ HashMap.singleton "time"
|
> $ HashMap.singleton "time"
|
||||||
> $ Field Nothing (Out.NamedScalarType string) mempty time
|
> $ Out.Resolver timeField time
|
||||||
|
>
|
||||||
|
> timeField :: Field IO
|
||||||
|
> timeField = Field Nothing (Out.NamedScalarType string) mempty
|
||||||
>
|
>
|
||||||
> time :: ActionT IO Value
|
> time :: ActionT IO Value
|
||||||
> time = do
|
> time = do
|
||||||
@ -140,8 +146,8 @@ Now that we have two resolvers, we can define a schema which uses them both.
|
|||||||
>
|
>
|
||||||
> queryType3 :: ObjectType IO
|
> queryType3 :: ObjectType IO
|
||||||
> queryType3 = ObjectType "Query" Nothing [] $ HashMap.fromList
|
> queryType3 = ObjectType "Query" Nothing [] $ HashMap.fromList
|
||||||
> [ ("hello", Field Nothing (Out.NamedScalarType string) mempty hello)
|
> [ ("hello", Out.Resolver helloField hello)
|
||||||
> , ("time", Field Nothing (Out.NamedScalarType string) mempty time)
|
> , ("time", Out.Resolver timeField time)
|
||||||
> ]
|
> ]
|
||||||
>
|
>
|
||||||
> query3 :: Text
|
> query3 :: Text
|
||||||
|
@ -16,7 +16,7 @@ import Language.GraphQL.Type.Schema
|
|||||||
import Text.Megaparsec (parse)
|
import Text.Megaparsec (parse)
|
||||||
|
|
||||||
-- | If the text parses correctly as a @GraphQL@ query the query is
|
-- | If the text parses correctly as a @GraphQL@ query the query is
|
||||||
-- executed using the given 'Schema.Resolver's.
|
-- executed using the given 'Schema'.
|
||||||
graphql :: Monad m
|
graphql :: Monad m
|
||||||
=> Schema m -- ^ Resolvers.
|
=> Schema m -- ^ Resolvers.
|
||||||
-> Text -- ^ Text representing a @GraphQL@ request document.
|
-> Text -- ^ Text representing a @GraphQL@ request document.
|
||||||
@ -25,7 +25,7 @@ graphql = flip graphqlSubs (mempty :: Aeson.Object)
|
|||||||
|
|
||||||
-- | If the text parses correctly as a @GraphQL@ query the substitution is
|
-- | If the text parses correctly as a @GraphQL@ query the substitution is
|
||||||
-- applied to the query and the query is then executed using to the given
|
-- applied to the query and the query is then executed using to the given
|
||||||
-- 'Schema.Resolver's.
|
-- 'Schema'.
|
||||||
graphqlSubs :: (Monad m, VariableValue a)
|
graphqlSubs :: (Monad m, VariableValue a)
|
||||||
=> Schema m -- ^ Resolvers.
|
=> Schema m -- ^ Resolvers.
|
||||||
-> HashMap Name a -- ^ Variable substitution function.
|
-> HashMap Name a -- ^ Variable substitution function.
|
||||||
|
@ -29,6 +29,7 @@ import Text.Megaparsec
|
|||||||
, unPos
|
, unPos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- | Executor context.
|
||||||
data Resolution m = Resolution
|
data Resolution m = Resolution
|
||||||
{ errors :: [Aeson.Value]
|
{ errors :: [Aeson.Value]
|
||||||
, types :: HashMap Name (Type m)
|
, types :: HashMap Name (Type m)
|
||||||
|
@ -11,10 +11,10 @@ import Data.Sequence (Seq(..))
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Language.GraphQL.AST.Document (Document, Name)
|
import Language.GraphQL.AST.Document (Document, Name)
|
||||||
import Language.GraphQL.Execute.Coerce
|
import Language.GraphQL.Execute.Coerce
|
||||||
|
import Language.GraphQL.Execute.Execution
|
||||||
import qualified Language.GraphQL.Execute.Transform as Transform
|
import qualified Language.GraphQL.Execute.Transform as Transform
|
||||||
import Language.GraphQL.Error
|
import Language.GraphQL.Error
|
||||||
import Language.GraphQL.Type.Definition
|
import Language.GraphQL.Type.Definition
|
||||||
import qualified Language.GraphQL.Schema as Schema
|
|
||||||
import qualified Language.GraphQL.Type.Out as Out
|
import qualified Language.GraphQL.Type.Out as Out
|
||||||
import Language.GraphQL.Type.Schema
|
import Language.GraphQL.Type.Schema
|
||||||
|
|
||||||
@ -68,4 +68,4 @@ executeOperation :: Monad m
|
|||||||
-> Seq (Transform.Selection m)
|
-> Seq (Transform.Selection m)
|
||||||
-> m Aeson.Value
|
-> m Aeson.Value
|
||||||
executeOperation types' objectType fields =
|
executeOperation types' objectType fields =
|
||||||
runCollectErrs types' $ Schema.resolve Null objectType fields
|
runCollectErrs types' $ executeSelectionSet Null objectType fields
|
||||||
|
@ -1,20 +1,38 @@
|
|||||||
{-# LANGUAGE ExplicitForAll #-}
|
{-# LANGUAGE ExplicitForAll #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Language.GraphQL.Execute.Execution
|
module Language.GraphQL.Execute.Execution
|
||||||
( aliasOrName
|
( executeSelectionSet
|
||||||
, collectFields
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import qualified Data.Aeson as Aeson
|
||||||
|
import Control.Monad.Trans.Class (lift)
|
||||||
|
import Control.Monad.Trans.Except (runExceptT)
|
||||||
|
import Control.Monad.Trans.Reader (runReaderT)
|
||||||
|
import Control.Monad.Trans.State (gets)
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
|
import Data.HashMap.Strict (HashMap)
|
||||||
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Sequence (Seq)
|
import Data.Sequence (Seq(..))
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as Text
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
import Language.GraphQL.AST.Document (Name)
|
import Language.GraphQL.AST.Document (Name)
|
||||||
|
import Language.GraphQL.Error
|
||||||
import Language.GraphQL.Execute.Transform
|
import Language.GraphQL.Execute.Transform
|
||||||
|
import Language.GraphQL.Trans
|
||||||
|
import Language.GraphQL.Type.Definition
|
||||||
import qualified Language.GraphQL.Type.Out as Out
|
import qualified Language.GraphQL.Type.Out as Out
|
||||||
import Language.GraphQL.Type.Schema
|
import Language.GraphQL.Type.Schema
|
||||||
|
|
||||||
|
resolveFieldValue :: Monad m => Value -> Field m -> ActionT m a -> m (Either Text a)
|
||||||
|
resolveFieldValue result (Field _ _ args _) =
|
||||||
|
flip runReaderT (Context {arguments=args, values=result})
|
||||||
|
. runExceptT
|
||||||
|
. runActionT
|
||||||
|
|
||||||
collectFields :: Monad m
|
collectFields :: Monad m
|
||||||
=> Out.ObjectType m
|
=> Out.ObjectType m
|
||||||
-> Seq (Selection m)
|
-> Seq (Selection m)
|
||||||
@ -34,6 +52,21 @@ collectFields objectType = foldl forEach Map.empty
|
|||||||
aliasOrName :: forall m. Field m -> Name
|
aliasOrName :: forall m. Field m -> Name
|
||||||
aliasOrName (Field alias name _ _) = fromMaybe name alias
|
aliasOrName (Field alias name _ _) = fromMaybe name alias
|
||||||
|
|
||||||
|
resolveAbstractType :: Monad m
|
||||||
|
=> AbstractType m
|
||||||
|
-> HashMap Name Value
|
||||||
|
-> CollectErrsT m (Maybe (Out.ObjectType m))
|
||||||
|
resolveAbstractType abstractType values'
|
||||||
|
| Just (String typeName) <- HashMap.lookup "__typename" values' = do
|
||||||
|
types' <- gets types
|
||||||
|
case HashMap.lookup typeName types' of
|
||||||
|
Just (ObjectType objectType) ->
|
||||||
|
if instanceOf objectType abstractType
|
||||||
|
then pure $ Just objectType
|
||||||
|
else pure Nothing
|
||||||
|
_ -> pure Nothing
|
||||||
|
| otherwise = pure Nothing
|
||||||
|
|
||||||
doesFragmentTypeApply :: forall m
|
doesFragmentTypeApply :: forall m
|
||||||
. CompositeType m
|
. CompositeType m
|
||||||
-> Out.ObjectType m
|
-> Out.ObjectType m
|
||||||
@ -43,16 +76,88 @@ doesFragmentTypeApply (CompositeObjectType fragmentType) objectType =
|
|||||||
Out.ObjectType objectName _ _ _ = objectType
|
Out.ObjectType objectName _ _ _ = objectType
|
||||||
in fragmentName == objectName
|
in fragmentName == objectName
|
||||||
doesFragmentTypeApply (CompositeInterfaceType fragmentType) objectType =
|
doesFragmentTypeApply (CompositeInterfaceType fragmentType) objectType =
|
||||||
let Out.ObjectType _ _ interfaces _ = objectType
|
instanceOf objectType $ AbstractInterfaceType fragmentType
|
||||||
in foldr instanceOf False interfaces
|
|
||||||
where
|
|
||||||
instanceOf (Out.InterfaceType that _ interfaces _) acc =
|
|
||||||
let Out.InterfaceType this _ _ _ = fragmentType
|
|
||||||
in acc || foldr instanceOf (this == that) interfaces
|
|
||||||
doesFragmentTypeApply (CompositeUnionType fragmentType) objectType =
|
doesFragmentTypeApply (CompositeUnionType fragmentType) objectType =
|
||||||
let Out.UnionType _ _ members = fragmentType
|
instanceOf objectType $ AbstractUnionType fragmentType
|
||||||
in foldr instanceOf False members
|
|
||||||
|
instanceOf :: forall m. Out.ObjectType m -> AbstractType m -> Bool
|
||||||
|
instanceOf objectType (AbstractInterfaceType interfaceType) =
|
||||||
|
let Out.ObjectType _ _ interfaces _ = objectType
|
||||||
|
in foldr go False interfaces
|
||||||
where
|
where
|
||||||
instanceOf (Out.ObjectType that _ _ _) acc =
|
go (Out.InterfaceType that _ interfaces _) acc =
|
||||||
|
let Out.InterfaceType this _ _ _ = interfaceType
|
||||||
|
in acc || foldr go (this == that) interfaces
|
||||||
|
instanceOf objectType (AbstractUnionType unionType) =
|
||||||
|
let Out.UnionType _ _ members = unionType
|
||||||
|
in foldr go False members
|
||||||
|
where
|
||||||
|
go (Out.ObjectType that _ _ _) acc =
|
||||||
let Out.ObjectType this _ _ _ = objectType
|
let Out.ObjectType this _ _ _ = objectType
|
||||||
in acc || this == that
|
in acc || this == that
|
||||||
|
|
||||||
|
executeField :: Monad m
|
||||||
|
=> Value
|
||||||
|
-> Out.Resolver m
|
||||||
|
-> Field m
|
||||||
|
-> CollectErrsT m Aeson.Value
|
||||||
|
executeField prev (Out.Resolver fieldDefinition resolver) field = do
|
||||||
|
let Out.Field _ fieldType _ = fieldDefinition
|
||||||
|
answer <- lift $ resolveFieldValue prev field resolver
|
||||||
|
case answer of
|
||||||
|
Right result -> completeValue fieldType field result
|
||||||
|
Left errorMessage -> errmsg errorMessage
|
||||||
|
|
||||||
|
completeValue :: Monad m
|
||||||
|
=> Out.Type m
|
||||||
|
-> Field m
|
||||||
|
-> Value
|
||||||
|
-> CollectErrsT m Aeson.Value
|
||||||
|
completeValue _ _ Null = pure Aeson.Null
|
||||||
|
completeValue _ _ (Int integer) = pure $ Aeson.toJSON integer
|
||||||
|
completeValue _ _ (Boolean boolean') = pure $ Aeson.Bool boolean'
|
||||||
|
completeValue _ _ (Float float') = pure $ Aeson.toJSON float'
|
||||||
|
completeValue _ _ (Enum enum) = pure $ Aeson.String enum
|
||||||
|
completeValue _ _ (String string') = pure $ Aeson.String string'
|
||||||
|
completeValue (Out.ObjectBaseType objectType) (Field _ _ _ seqSelection) result =
|
||||||
|
executeSelectionSet result objectType seqSelection
|
||||||
|
completeValue (Out.ListBaseType listType) selectionField (List list) =
|
||||||
|
Aeson.toJSON <$> traverse (completeValue listType selectionField) list
|
||||||
|
completeValue (Out.InterfaceBaseType interfaceType) (Field _ _ _ seqSelection) result
|
||||||
|
| Object objectMap <- result = do
|
||||||
|
abstractType <- resolveAbstractType (AbstractInterfaceType interfaceType) objectMap
|
||||||
|
case abstractType of
|
||||||
|
Just objectType -> executeSelectionSet result objectType seqSelection
|
||||||
|
Nothing -> errmsg "Value completion failed."
|
||||||
|
completeValue (Out.UnionBaseType unionType) (Field _ _ _ seqSelection) result
|
||||||
|
| Object objectMap <- result = do
|
||||||
|
abstractType <- resolveAbstractType (AbstractUnionType unionType) objectMap
|
||||||
|
case abstractType of
|
||||||
|
Just objectType -> executeSelectionSet result objectType seqSelection
|
||||||
|
Nothing -> errmsg "Value completion failed."
|
||||||
|
completeValue _ _ _ = errmsg "Value completion failed."
|
||||||
|
|
||||||
|
errmsg :: Monad m => Text -> CollectErrsT m Aeson.Value
|
||||||
|
errmsg errorMessage = addErrMsg errorMessage >> pure Aeson.Null
|
||||||
|
|
||||||
|
-- | Takes an 'Out.ObjectType' and a list of 'Selection's and applies each field
|
||||||
|
-- to each 'Selection'. Resolves into a value containing the resolved
|
||||||
|
-- 'Selection', or a null value and error information.
|
||||||
|
executeSelectionSet :: Monad m
|
||||||
|
=> Value
|
||||||
|
-> Out.ObjectType m
|
||||||
|
-> Seq (Selection m)
|
||||||
|
-> CollectErrsT m Aeson.Value
|
||||||
|
executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do
|
||||||
|
resolvedValues <- Map.traverseMaybeWithKey forEach
|
||||||
|
$ collectFields objectType selectionSet
|
||||||
|
pure $ Aeson.toJSON resolvedValues
|
||||||
|
where
|
||||||
|
forEach _responseKey (field :<| _) =
|
||||||
|
tryResolvers field >>= lift . pure . pure
|
||||||
|
forEach _ _ = pure Nothing
|
||||||
|
lookupResolver = flip HashMap.lookup resolvers
|
||||||
|
tryResolvers fld@(Field _ name _ _)
|
||||||
|
| Just typeField <- lookupResolver name =
|
||||||
|
executeField result typeField fld
|
||||||
|
| otherwise = errmsg $ Text.unwords ["field", name, "not resolved."]
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
{-# LANGUAGE ExplicitForAll #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
-- | This module provides a representation of a @GraphQL@ Schema in addition to
|
|
||||||
-- functions for defining and manipulating schemas.
|
|
||||||
module Language.GraphQL.Schema
|
|
||||||
( Resolver(..)
|
|
||||||
, resolve
|
|
||||||
) where
|
|
||||||
|
|
||||||
import Control.Monad.Trans.Class (lift)
|
|
||||||
import Control.Monad.Trans.Except (runExceptT)
|
|
||||||
import Control.Monad.Trans.Reader (runReaderT)
|
|
||||||
import qualified Data.Aeson as Aeson
|
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
|
||||||
import qualified Data.Map.Strict as Map
|
|
||||||
import Data.Sequence (Seq(..))
|
|
||||||
import Data.Text (Text)
|
|
||||||
import qualified Data.Text as Text
|
|
||||||
import Language.GraphQL.AST.Document (Name)
|
|
||||||
import Language.GraphQL.Error
|
|
||||||
import Language.GraphQL.Execute.Execution
|
|
||||||
import Language.GraphQL.Execute.Transform
|
|
||||||
import Language.GraphQL.Trans
|
|
||||||
import Language.GraphQL.Type.Definition
|
|
||||||
import qualified Language.GraphQL.Type.Out as Out
|
|
||||||
|
|
||||||
-- | Resolves a 'Field' into an @Aeson.@'Data.Aeson.Types.Object' with error
|
|
||||||
-- information (if an error has occurred). @m@ is an arbitrary monad, usually
|
|
||||||
-- 'IO'.
|
|
||||||
--
|
|
||||||
-- Resolving a field can result in a leaf value or an object, which is
|
|
||||||
-- represented as a list of nested resolvers, used to resolve the fields of that
|
|
||||||
-- object.
|
|
||||||
data Resolver m = Resolver Name (ActionT m Value)
|
|
||||||
|
|
||||||
resolveFieldValue :: Monad m => Value -> Field m -> ActionT m a -> m (Either Text a)
|
|
||||||
resolveFieldValue result (Field _ _ args _) =
|
|
||||||
flip runReaderT (Context {arguments=args, values=result})
|
|
||||||
. runExceptT
|
|
||||||
. runActionT
|
|
||||||
|
|
||||||
executeField :: Monad m
|
|
||||||
=> Value
|
|
||||||
-> Out.Field m
|
|
||||||
-> Field m
|
|
||||||
-> CollectErrsT m Aeson.Value
|
|
||||||
executeField prev (Out.Field _ fieldType _ resolver) field = do
|
|
||||||
answer <- lift $ resolveFieldValue prev field resolver
|
|
||||||
case answer of
|
|
||||||
Right result -> completeValue fieldType field result
|
|
||||||
Left errorMessage -> errmsg errorMessage
|
|
||||||
|
|
||||||
completeValue :: Monad m
|
|
||||||
=> Out.Type m
|
|
||||||
-> Field m
|
|
||||||
-> Value
|
|
||||||
-> CollectErrsT m Aeson.Value
|
|
||||||
completeValue _ _ Null = pure Aeson.Null
|
|
||||||
completeValue _ _ (Int integer) = pure $ Aeson.toJSON integer
|
|
||||||
completeValue _ _ (Boolean boolean') = pure $ Aeson.Bool boolean'
|
|
||||||
completeValue _ _ (Float float') = pure $ Aeson.toJSON float'
|
|
||||||
completeValue _ _ (Enum enum) = pure $ Aeson.String enum
|
|
||||||
completeValue _ _ (String string') = pure $ Aeson.String string'
|
|
||||||
completeValue (Out.ObjectBaseType objectType) (Field _ _ _ seqSelection) result =
|
|
||||||
resolve result objectType seqSelection
|
|
||||||
completeValue (Out.ListBaseType listType) selectionField (List list) =
|
|
||||||
Aeson.toJSON <$> traverse (completeValue listType selectionField) list
|
|
||||||
completeValue _ _ _ = errmsg "Value completion failed."
|
|
||||||
|
|
||||||
errmsg :: Monad m => Text -> CollectErrsT m Aeson.Value
|
|
||||||
errmsg errorMessage = addErrMsg errorMessage >> pure Aeson.Null
|
|
||||||
|
|
||||||
-- | Takes a list of 'Resolver's and a list of 'Field's and applies each
|
|
||||||
-- 'Resolver' to each 'Field'. Resolves into a value containing the
|
|
||||||
-- resolved 'Field', or a null value and error information.
|
|
||||||
resolve :: Monad m -- executeSelectionSet
|
|
||||||
=> Value
|
|
||||||
-> Out.ObjectType m
|
|
||||||
-> Seq (Selection m)
|
|
||||||
-> CollectErrsT m Aeson.Value
|
|
||||||
resolve result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do
|
|
||||||
resolvedValues <- Map.traverseMaybeWithKey forEach
|
|
||||||
$ collectFields objectType selectionSet
|
|
||||||
pure $ Aeson.toJSON resolvedValues
|
|
||||||
where
|
|
||||||
forEach _responseKey (field :<| _) =
|
|
||||||
tryResolvers field >>= lift . pure . pure
|
|
||||||
forEach _ _ = pure Nothing
|
|
||||||
lookupResolver = flip HashMap.lookup resolvers
|
|
||||||
tryResolvers fld@(Field _ name _ _)
|
|
||||||
| Just typeField <- lookupResolver name =
|
|
||||||
executeField result typeField fld
|
|
||||||
| otherwise = errmsg $ Text.unwords ["field", name, "not resolved."]
|
|
||||||
{-tryResolvers (Out.SelectionFragment (Out.Fragment typeCondition selections'))
|
|
||||||
| Just (Out.Field _ _ _ resolver) <- lookupResolver "__typename" = do
|
|
||||||
let fakeField = Out.Field Nothing "__typename" mempty mempty
|
|
||||||
that <- lift $ resolveFieldValue result fakeField resolver
|
|
||||||
case that of
|
|
||||||
Right (String typeCondition')
|
|
||||||
| (Out.CompositeObjectType (Out.ObjectType n _ _ _)) <- typeCondition
|
|
||||||
, typeCondition' == n ->
|
|
||||||
fmap fold . traverse tryResolvers $ selections'
|
|
||||||
_ -> pure mempty
|
|
||||||
| otherwise = fmap fold . traverse tryResolvers $ selections'-}
|
|
@ -10,6 +10,7 @@ module Language.GraphQL.Type.Out
|
|||||||
( Field(..)
|
( Field(..)
|
||||||
, InterfaceType(..)
|
, InterfaceType(..)
|
||||||
, ObjectType(..)
|
, ObjectType(..)
|
||||||
|
, Resolver(..)
|
||||||
, Type(..)
|
, Type(..)
|
||||||
, UnionType(..)
|
, UnionType(..)
|
||||||
, isNonNullType
|
, isNonNullType
|
||||||
@ -27,13 +28,22 @@ import Language.GraphQL.AST.Core
|
|||||||
import Language.GraphQL.Trans
|
import Language.GraphQL.Trans
|
||||||
import Language.GraphQL.Type.Definition
|
import Language.GraphQL.Type.Definition
|
||||||
import qualified Language.GraphQL.Type.In as In
|
import qualified Language.GraphQL.Type.In as In
|
||||||
|
|
||||||
|
-- | Resolves a 'Field' into an @Aeson.@'Data.Aeson.Types.Object' with error
|
||||||
|
-- information (if an error has occurred). @m@ is an arbitrary monad, usually
|
||||||
|
-- 'IO'.
|
||||||
--
|
--
|
||||||
|
-- Resolving a field can result in a leaf value or an object, which is
|
||||||
|
-- represented as a list of nested resolvers, used to resolve the fields of that
|
||||||
|
-- object.
|
||||||
|
data Resolver m = Resolver (Field m) (ActionT m Value)
|
||||||
|
|
||||||
-- | Object type definition.
|
-- | Object type definition.
|
||||||
--
|
--
|
||||||
-- Almost all of the GraphQL types you define will be object types. Object
|
-- Almost all of the GraphQL types you define will be object types. Object
|
||||||
-- types have a name, but most importantly describe their fields.
|
-- types have a name, but most importantly describe their fields.
|
||||||
data ObjectType m = ObjectType
|
data ObjectType m = ObjectType
|
||||||
Name (Maybe Text) [InterfaceType m] (HashMap Name (Field m))
|
Name (Maybe Text) [InterfaceType m] (HashMap Name (Resolver m))
|
||||||
|
|
||||||
-- | Interface Type Definition.
|
-- | Interface Type Definition.
|
||||||
--
|
--
|
||||||
@ -54,7 +64,6 @@ data Field m = Field
|
|||||||
(Maybe Text) -- ^ Description.
|
(Maybe Text) -- ^ Description.
|
||||||
(Type m) -- ^ Field type.
|
(Type m) -- ^ Field type.
|
||||||
(HashMap Name In.Argument) -- ^ Arguments.
|
(HashMap Name In.Argument) -- ^ Arguments.
|
||||||
(ActionT m Value) -- ^ Resolver.
|
|
||||||
|
|
||||||
-- | These types may be used as output types as the result of fields.
|
-- | These types may be used as output types as the result of fields.
|
||||||
--
|
--
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
{-# LANGUAGE ExplicitForAll #-}
|
{-# LANGUAGE ExplicitForAll #-}
|
||||||
|
|
||||||
-- | Schema Definition.
|
-- | This module provides a representation of a @GraphQL@ Schema in addition to
|
||||||
|
-- functions for defining and manipulating schemas.
|
||||||
module Language.GraphQL.Type.Schema
|
module Language.GraphQL.Type.Schema
|
||||||
( CompositeType(..)
|
( AbstractType(..)
|
||||||
|
, CompositeType(..)
|
||||||
, Schema(..)
|
, Schema(..)
|
||||||
, Type(..)
|
, Type(..)
|
||||||
, collectReferencedTypes
|
, collectReferencedTypes
|
||||||
@ -30,6 +32,11 @@ data CompositeType m
|
|||||||
| CompositeObjectType (Out.ObjectType m)
|
| CompositeObjectType (Out.ObjectType m)
|
||||||
| CompositeInterfaceType (Out.InterfaceType m)
|
| CompositeInterfaceType (Out.InterfaceType m)
|
||||||
|
|
||||||
|
-- | These types may describe the parent context of a selection set.
|
||||||
|
data AbstractType m
|
||||||
|
= AbstractUnionType (Out.UnionType m)
|
||||||
|
| AbstractInterfaceType (Out.InterfaceType m)
|
||||||
|
|
||||||
-- | A Schema is created by supplying the root types of each type of operation,
|
-- | A Schema is created by supplying the root types of each type of operation,
|
||||||
-- query and mutation (optional). A schema definition is then supplied to the
|
-- query and mutation (optional). A schema definition is then supplied to the
|
||||||
-- validator and executor.
|
-- validator and executor.
|
||||||
@ -51,7 +58,7 @@ collectReferencedTypes schema =
|
|||||||
collect traverser typeName element foundTypes
|
collect traverser typeName element foundTypes
|
||||||
| HashMap.member typeName foundTypes = foundTypes
|
| HashMap.member typeName foundTypes = foundTypes
|
||||||
| otherwise = traverser $ HashMap.insert typeName element foundTypes
|
| otherwise = traverser $ HashMap.insert typeName element foundTypes
|
||||||
visitFields (Out.Field _ outputType arguments _) foundTypes
|
visitFields (Out.Field _ outputType arguments) foundTypes
|
||||||
= traverseOutputType outputType
|
= traverseOutputType outputType
|
||||||
$ foldr visitArguments foundTypes arguments
|
$ foldr visitArguments foundTypes arguments
|
||||||
visitArguments (In.Argument _ inputType _) = traverseInputType inputType
|
visitArguments (In.Argument _ inputType _) = traverseInputType inputType
|
||||||
@ -86,15 +93,17 @@ collectReferencedTypes schema =
|
|||||||
let (Definition.EnumType typeName _ _) = enumType
|
let (Definition.EnumType typeName _ _) = enumType
|
||||||
in collect Prelude.id typeName (EnumType enumType)
|
in collect Prelude.id typeName (EnumType enumType)
|
||||||
traverseObjectType objectType foundTypes =
|
traverseObjectType objectType foundTypes =
|
||||||
let (Out.ObjectType typeName _ interfaces fields) = objectType
|
let (Out.ObjectType typeName _ interfaces resolvers) = objectType
|
||||||
element = ObjectType objectType
|
element = ObjectType objectType
|
||||||
traverser = polymorphicTypeTraverser interfaces fields
|
fields = extractObjectField <$> resolvers
|
||||||
|
traverser = polymorphicTraverser interfaces fields
|
||||||
in collect traverser typeName element foundTypes
|
in collect traverser typeName element foundTypes
|
||||||
traverseInterfaceType interfaceType foundTypes =
|
traverseInterfaceType interfaceType foundTypes =
|
||||||
let (Out.InterfaceType typeName _ interfaces fields) = interfaceType
|
let (Out.InterfaceType typeName _ interfaces fields) = interfaceType
|
||||||
element = InterfaceType interfaceType
|
element = InterfaceType interfaceType
|
||||||
traverser = polymorphicTypeTraverser interfaces fields
|
traverser = polymorphicTraverser interfaces fields
|
||||||
in collect traverser typeName element foundTypes
|
in collect traverser typeName element foundTypes
|
||||||
polymorphicTypeTraverser interfaces fields
|
polymorphicTraverser interfaces fields
|
||||||
= flip (foldr visitFields) fields
|
= flip (foldr visitFields) fields
|
||||||
. flip (foldr traverseInterfaceType) interfaces
|
. flip (foldr traverseInterfaceType) interfaces
|
||||||
|
extractObjectField (Out.Resolver field _) = field
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
resolver: lts-15.14
|
resolver: lts-15.15
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
- .
|
- .
|
||||||
|
@ -20,7 +20,7 @@ experimentalResolver = Schema { query = queryType, mutation = Nothing }
|
|||||||
resolver = pure $ Int 5
|
resolver = pure $ Int 5
|
||||||
queryType = Out.ObjectType "Query" Nothing []
|
queryType = Out.ObjectType "Query" Nothing []
|
||||||
$ HashMap.singleton "experimentalField"
|
$ HashMap.singleton "experimentalField"
|
||||||
$ Out.Field Nothing (Out.NamedScalarType int) mempty resolver
|
$ Out.Resolver (Out.Field Nothing (Out.NamedScalarType int) mempty) resolver
|
||||||
|
|
||||||
emptyObject :: Aeson.Value
|
emptyObject :: Aeson.Value
|
||||||
emptyObject = object
|
emptyObject = object
|
||||||
|
@ -54,32 +54,38 @@ hasErrors _ = True
|
|||||||
shirtType :: Out.ObjectType IO
|
shirtType :: Out.ObjectType IO
|
||||||
shirtType = Out.ObjectType "Shirt" Nothing []
|
shirtType = Out.ObjectType "Shirt" Nothing []
|
||||||
$ HashMap.fromList
|
$ HashMap.fromList
|
||||||
[ ("size", Out.Field Nothing (Out.NamedScalarType string) mempty $ pure $ snd size)
|
[ ("size", Out.Resolver sizeFieldType $ pure $ snd size)
|
||||||
, ("circumference", Out.Field Nothing (Out.NamedScalarType int) mempty $ pure $ snd circumference)
|
, ("circumference", Out.Resolver circumferenceFieldType $ pure $ snd circumference)
|
||||||
, ("__typename", Out.Field Nothing (Out.NamedScalarType string) mempty $ pure $ String "Shirt")
|
|
||||||
]
|
]
|
||||||
|
|
||||||
hatType :: Out.ObjectType IO
|
hatType :: Out.ObjectType IO
|
||||||
hatType = Out.ObjectType "Hat" Nothing []
|
hatType = Out.ObjectType "Hat" Nothing []
|
||||||
$ HashMap.fromList
|
$ HashMap.fromList
|
||||||
[ ("size", Out.Field Nothing (Out.NamedScalarType string) mempty $ pure $ snd size)
|
[ ("size", Out.Resolver sizeFieldType $ pure $ snd size)
|
||||||
, ("circumference", Out.Field Nothing (Out.NamedScalarType int) mempty $ pure $ snd circumference)
|
, ("circumference", Out.Resolver circumferenceFieldType $ pure $ snd circumference)
|
||||||
, ("__typename", Out.Field Nothing (Out.NamedScalarType string) mempty $ pure $ String "Hat")
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
circumferenceFieldType :: Out.Field IO
|
||||||
|
circumferenceFieldType = Out.Field Nothing (Out.NamedScalarType int) mempty
|
||||||
|
|
||||||
|
sizeFieldType :: Out.Field IO
|
||||||
|
sizeFieldType = Out.Field Nothing (Out.NamedScalarType string) mempty
|
||||||
|
|
||||||
toSchema :: Text -> (Text, Value) -> Schema IO
|
toSchema :: Text -> (Text, Value) -> Schema IO
|
||||||
toSchema t (_, resolve) = Schema
|
toSchema t (_, resolve) = Schema
|
||||||
{ query = queryType, mutation = Nothing }
|
{ query = queryType, mutation = Nothing }
|
||||||
where
|
where
|
||||||
unionMember = if t == "Hat" then hatType else shirtType
|
unionMember = if t == "Hat" then hatType else shirtType
|
||||||
|
typeNameField = Out.Field Nothing (Out.NamedScalarType string) mempty
|
||||||
|
garmentField = Out.Field Nothing (Out.NamedObjectType unionMember) mempty
|
||||||
queryType =
|
queryType =
|
||||||
case t of
|
case t of
|
||||||
"circumference" -> hatType
|
"circumference" -> hatType
|
||||||
"size" -> shirtType
|
"size" -> shirtType
|
||||||
_ -> Out.ObjectType "Query" Nothing []
|
_ -> Out.ObjectType "Query" Nothing []
|
||||||
$ HashMap.fromList
|
$ HashMap.fromList
|
||||||
[ ("garment", Out.Field Nothing (Out.NamedObjectType unionMember) mempty $ pure resolve)
|
[ ("garment", Out.Resolver garmentField $ pure resolve)
|
||||||
, ("__typename", Out.Field Nothing (Out.NamedScalarType string) mempty $ pure $ String "Shirt")
|
, ("__typename", Out.Resolver typeNameField $ pure $ String "Shirt")
|
||||||
]
|
]
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
|
@ -16,7 +16,7 @@ import Language.GraphQL.Type.Schema
|
|||||||
hatType :: Out.ObjectType IO
|
hatType :: Out.ObjectType IO
|
||||||
hatType = Out.ObjectType "Hat" Nothing []
|
hatType = Out.ObjectType "Hat" Nothing []
|
||||||
$ HashMap.singleton "circumference"
|
$ HashMap.singleton "circumference"
|
||||||
$ Out.Field Nothing (Out.NamedScalarType int) mempty
|
$ Out.Resolver (Out.Field Nothing (Out.NamedScalarType int) mempty)
|
||||||
$ pure $ Int 60
|
$ pure $ Int 60
|
||||||
|
|
||||||
schema :: Schema IO
|
schema :: Schema IO
|
||||||
@ -28,10 +28,10 @@ schema = Schema
|
|||||||
[ ("circumference", Int 60)
|
[ ("circumference", Int 60)
|
||||||
]
|
]
|
||||||
incrementField = HashMap.singleton "incrementCircumference"
|
incrementField = HashMap.singleton "incrementCircumference"
|
||||||
$ Out.Field Nothing (Out.NamedScalarType int) mempty
|
$ Out.Resolver (Out.Field Nothing (Out.NamedScalarType int) mempty)
|
||||||
$ pure $ Int 61
|
$ pure $ Int 61
|
||||||
hatField = HashMap.singleton "garment"
|
hatField = HashMap.singleton "garment"
|
||||||
$ Out.Field Nothing (Out.NamedObjectType hatType) mempty garment
|
$ Out.Resolver (Out.Field Nothing (Out.NamedObjectType hatType) mempty) garment
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec =
|
spec =
|
||||||
|
@ -24,32 +24,51 @@ schema :: Schema Identity
|
|||||||
schema = Schema { query = queryType, mutation = Nothing }
|
schema = Schema { query = queryType, mutation = Nothing }
|
||||||
where
|
where
|
||||||
queryType = Out.ObjectType "Query" Nothing [] $ HashMap.fromList
|
queryType = Out.ObjectType "Query" Nothing [] $ HashMap.fromList
|
||||||
[ ("hero", Out.Field Nothing (Out.NamedObjectType heroObject) mempty hero)
|
[ ("hero", Out.Resolver (Out.Field Nothing (Out.NamedObjectType heroObject) mempty) hero)
|
||||||
, ("human", Out.Field Nothing (Out.NamedObjectType heroObject) mempty human)
|
, ("human", Out.Resolver (Out.Field Nothing (Out.NamedObjectType heroObject) mempty) human)
|
||||||
, ("droid", Out.Field Nothing (Out.NamedObjectType droidObject) mempty droid)
|
, ("droid", Out.Resolver (Out.Field Nothing (Out.NamedObjectType droidObject) mempty) droid)
|
||||||
]
|
]
|
||||||
|
|
||||||
heroObject :: Out.ObjectType Identity
|
heroObject :: Out.ObjectType Identity
|
||||||
heroObject = Out.ObjectType "Human" Nothing [] $ HashMap.fromList
|
heroObject = Out.ObjectType "Human" Nothing [] $ HashMap.fromList
|
||||||
[ ("id", Out.Field Nothing (Out.NamedScalarType id) mempty (idField "id"))
|
[ ("id", Out.Resolver idFieldType (idField "id"))
|
||||||
, ("name", Out.Field Nothing (Out.NamedScalarType string) mempty (idField "name"))
|
, ("name", Out.Resolver nameFieldType (idField "name"))
|
||||||
, ("friends", Out.Field Nothing (Out.ListType $ Out.NamedObjectType heroObject) mempty (idField "friends"))
|
, ("friends", Out.Resolver friendsFieldType (idField "friends"))
|
||||||
, ("appearsIn", Out.Field Nothing (Out.ListType $ Out.NamedScalarType int) mempty (idField "appearsIn"))
|
, ("appearsIn", Out.Resolver appearsInFieldType (idField "appearsIn"))
|
||||||
, ("homePlanet", Out.Field Nothing (Out.NamedScalarType string) mempty (idField "homePlanet"))
|
, ("homePlanet", Out.Resolver homePlanetFieldType (idField "homePlanet"))
|
||||||
, ("secretBackstory", Out.Field Nothing (Out.NamedScalarType string) mempty (String <$> secretBackstory))
|
, ("secretBackstory", Out.Resolver secretBackstoryFieldType (String <$> secretBackstory))
|
||||||
, ("__typename", Out.Field Nothing (Out.NamedScalarType string) mempty (idField "__typename"))
|
, ("__typename", Out.Resolver (Out.Field Nothing (Out.NamedScalarType string) mempty) (idField "__typename"))
|
||||||
]
|
]
|
||||||
|
where
|
||||||
|
homePlanetFieldType = Out.Field Nothing (Out.NamedScalarType string) mempty
|
||||||
|
|
||||||
droidObject :: Out.ObjectType Identity
|
droidObject :: Out.ObjectType Identity
|
||||||
droidObject = Out.ObjectType "Droid" Nothing [] $ HashMap.fromList
|
droidObject = Out.ObjectType "Droid" Nothing [] $ HashMap.fromList
|
||||||
[ ("id", Out.Field Nothing (Out.NamedScalarType id) mempty (idField "id"))
|
[ ("id", Out.Resolver idFieldType (idField "id"))
|
||||||
, ("name", Out.Field Nothing (Out.NamedScalarType string) mempty (idField "name"))
|
, ("name", Out.Resolver nameFieldType (idField "name"))
|
||||||
, ("friends", Out.Field Nothing (Out.ListType $ Out.NamedObjectType droidObject) mempty (idField "friends"))
|
, ("friends", Out.Resolver friendsFieldType (idField "friends"))
|
||||||
, ("appearsIn", Out.Field Nothing (Out.ListType $ Out.NamedScalarType int) mempty (idField "appearsIn"))
|
, ("appearsIn", Out.Resolver appearsInFieldType (idField "appearsIn"))
|
||||||
, ("primaryFunction", Out.Field Nothing (Out.NamedScalarType string) mempty (idField "primaryFunction"))
|
, ("primaryFunction", Out.Resolver primaryFunctionFieldType (idField "primaryFunction"))
|
||||||
, ("secretBackstory", Out.Field Nothing (Out.NamedScalarType string) mempty (String <$> secretBackstory))
|
, ("secretBackstory", Out.Resolver secretBackstoryFieldType (String <$> secretBackstory))
|
||||||
, ("__typename", Out.Field Nothing (Out.NamedScalarType string) mempty (idField "__typename"))
|
, ("__typename", Out.Resolver (Out.Field Nothing (Out.NamedScalarType string) mempty) (idField "__typename"))
|
||||||
]
|
]
|
||||||
|
where
|
||||||
|
primaryFunctionFieldType = Out.Field Nothing (Out.NamedScalarType string) mempty
|
||||||
|
|
||||||
|
idFieldType :: Out.Field Identity
|
||||||
|
idFieldType = Out.Field Nothing (Out.NamedScalarType id) mempty
|
||||||
|
|
||||||
|
nameFieldType :: Out.Field Identity
|
||||||
|
nameFieldType = Out.Field Nothing (Out.NamedScalarType string) mempty
|
||||||
|
|
||||||
|
friendsFieldType :: Out.Field Identity
|
||||||
|
friendsFieldType = Out.Field Nothing (Out.ListType $ Out.NamedObjectType droidObject) mempty
|
||||||
|
|
||||||
|
appearsInFieldType :: Out.Field Identity
|
||||||
|
appearsInFieldType = Out.Field Nothing (Out.ListType $ Out.NamedScalarType int) mempty
|
||||||
|
|
||||||
|
secretBackstoryFieldType :: Out.Field Identity
|
||||||
|
secretBackstoryFieldType = Out.Field Nothing (Out.NamedScalarType string) mempty
|
||||||
|
|
||||||
idField :: Text -> ActionT Identity Value
|
idField :: Text -> ActionT Identity Value
|
||||||
idField f = do
|
idField f = do
|
||||||
|
Loading…
Reference in New Issue
Block a user