forked from OSS/graphql
Merge selection sets
This commit is contained in:
parent
c37b9c88b1
commit
e8c54810f8
@ -18,6 +18,7 @@ and this project adheres to
|
|||||||
- Argument value coercion.
|
- Argument value coercion.
|
||||||
- Variable value coercion.
|
- Variable value coercion.
|
||||||
- The executor should skip the fields missing in the object type and not fail.
|
- The executor should skip the fields missing in the object type and not fail.
|
||||||
|
- Merging subselections.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- `Schema.Resolver` was moved to `Type.Out`, it is a field and resolver function
|
- `Schema.Resolver` was moved to `Type.Out`, it is a field and resolver function
|
||||||
|
@ -10,6 +10,7 @@ import Control.Monad.Trans.Class (lift)
|
|||||||
import Control.Monad.Trans.Except (runExceptT)
|
import Control.Monad.Trans.Except (runExceptT)
|
||||||
import Control.Monad.Trans.Reader (runReaderT)
|
import Control.Monad.Trans.Reader (runReaderT)
|
||||||
import Control.Monad.Trans.State (gets)
|
import Control.Monad.Trans.State (gets)
|
||||||
|
import Data.List.NonEmpty (NonEmpty(..))
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import Data.HashMap.Strict (HashMap)
|
import Data.HashMap.Strict (HashMap)
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
@ -17,7 +18,6 @@ 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 Data.Text (Text)
|
||||||
import qualified Data.Sequence as Seq
|
|
||||||
import Language.GraphQL.AST (Name)
|
import Language.GraphQL.AST (Name)
|
||||||
import Language.GraphQL.AST.Core
|
import Language.GraphQL.AST.Core
|
||||||
import Language.GraphQL.Error
|
import Language.GraphQL.Error
|
||||||
@ -42,12 +42,12 @@ resolveFieldValue result args =
|
|||||||
collectFields :: Monad m
|
collectFields :: Monad m
|
||||||
=> Out.ObjectType m
|
=> Out.ObjectType m
|
||||||
-> Seq (Selection m)
|
-> Seq (Selection m)
|
||||||
-> Map Name (Seq (Field m))
|
-> Map Name (NonEmpty (Field m))
|
||||||
collectFields objectType = foldl forEach Map.empty
|
collectFields objectType = foldl forEach Map.empty
|
||||||
where
|
where
|
||||||
forEach groupedFields (SelectionField field) =
|
forEach groupedFields (SelectionField field) =
|
||||||
let responseKey = aliasOrName field
|
let responseKey = aliasOrName field
|
||||||
in Map.insertWith (<>) responseKey (Seq.singleton field) groupedFields
|
in Map.insertWith (<>) responseKey (field :| []) groupedFields
|
||||||
forEach groupedFields (SelectionFragment selectionFragment)
|
forEach groupedFields (SelectionFragment selectionFragment)
|
||||||
| Fragment fragmentType fragmentSelectionSet <- selectionFragment
|
| Fragment fragmentType fragmentSelectionSet <- selectionFragment
|
||||||
, doesFragmentTypeApply fragmentType objectType =
|
, doesFragmentTypeApply fragmentType objectType =
|
||||||
@ -98,24 +98,24 @@ instanceOf objectType (AbstractUnionType unionType) =
|
|||||||
go unionMemberType acc = acc || objectType == unionMemberType
|
go unionMemberType acc = acc || objectType == unionMemberType
|
||||||
|
|
||||||
executeField :: Monad m
|
executeField :: Monad m
|
||||||
=> Definition.Value
|
=> Out.Resolver m
|
||||||
-> Field m
|
-> Definition.Value
|
||||||
-> Out.Resolver m
|
-> NonEmpty (Field m)
|
||||||
-> CollectErrsT m Aeson.Value
|
-> CollectErrsT m Aeson.Value
|
||||||
executeField prev field (Out.Resolver fieldDefinition resolver) = do
|
executeField (Out.Resolver fieldDefinition resolver) prev fields = do
|
||||||
let Out.Field _ fieldType argumentDefinitions = fieldDefinition
|
let Out.Field _ fieldType argumentDefinitions = fieldDefinition
|
||||||
let Field _ _ arguments' _ = field
|
let (Field _ _ arguments' _ :| []) = fields
|
||||||
case coerceArgumentValues argumentDefinitions arguments' of
|
case coerceArgumentValues argumentDefinitions arguments' of
|
||||||
Nothing -> errmsg "Argument coercing failed."
|
Nothing -> errmsg "Argument coercing failed."
|
||||||
Just argumentValues -> do
|
Just argumentValues -> do
|
||||||
answer <- lift $ resolveFieldValue prev argumentValues resolver
|
answer <- lift $ resolveFieldValue prev argumentValues resolver
|
||||||
case answer of
|
case answer of
|
||||||
Right result -> completeValue fieldType field result
|
Right result -> completeValue fieldType fields result
|
||||||
Left errorMessage -> errmsg errorMessage
|
Left errorMessage -> errmsg errorMessage
|
||||||
|
|
||||||
completeValue :: Monad m
|
completeValue :: Monad m
|
||||||
=> Out.Type m
|
=> Out.Type m
|
||||||
-> Field m
|
-> NonEmpty (Field m)
|
||||||
-> Definition.Value
|
-> Definition.Value
|
||||||
-> CollectErrsT m Aeson.Value
|
-> CollectErrsT m Aeson.Value
|
||||||
completeValue _ _ Definition.Null = pure Aeson.Null
|
completeValue _ _ Definition.Null = pure Aeson.Null
|
||||||
@ -124,24 +124,32 @@ completeValue _ _ (Definition.Boolean boolean') = pure $ Aeson.Bool boolean'
|
|||||||
completeValue _ _ (Definition.Float float') = pure $ Aeson.toJSON float'
|
completeValue _ _ (Definition.Float float') = pure $ Aeson.toJSON float'
|
||||||
completeValue _ _ (Definition.Enum enum) = pure $ Aeson.String enum
|
completeValue _ _ (Definition.Enum enum) = pure $ Aeson.String enum
|
||||||
completeValue _ _ (Definition.String string') = pure $ Aeson.String string'
|
completeValue _ _ (Definition.String string') = pure $ Aeson.String string'
|
||||||
completeValue (Out.ObjectBaseType objectType) (Field _ _ _ seqSelection) result =
|
completeValue (Out.ListBaseType listType) fields (Definition.List list) =
|
||||||
executeSelectionSet result objectType seqSelection
|
Aeson.toJSON <$> traverse (completeValue listType fields) list
|
||||||
completeValue (Out.ListBaseType listType) selectionField (Definition.List list) =
|
completeValue (Out.ObjectBaseType objectType) fields result =
|
||||||
Aeson.toJSON <$> traverse (completeValue listType selectionField) list
|
executeSelectionSet result objectType $ mergeSelectionSets fields
|
||||||
completeValue (Out.InterfaceBaseType interfaceType) (Field _ _ _ seqSelection) result
|
completeValue (Out.InterfaceBaseType interfaceType) fields result
|
||||||
| Definition.Object objectMap <- result = do
|
| Definition.Object objectMap <- result = do
|
||||||
abstractType <- resolveAbstractType (AbstractInterfaceType interfaceType) objectMap
|
abstractType <- resolveAbstractType (AbstractInterfaceType interfaceType) objectMap
|
||||||
case abstractType of
|
case abstractType of
|
||||||
Just objectType -> executeSelectionSet result objectType seqSelection
|
Just objectType -> executeSelectionSet result objectType
|
||||||
|
$ mergeSelectionSets fields
|
||||||
Nothing -> errmsg "Value completion failed."
|
Nothing -> errmsg "Value completion failed."
|
||||||
completeValue (Out.UnionBaseType unionType) (Field _ _ _ seqSelection) result
|
completeValue (Out.UnionBaseType unionType) fields result
|
||||||
| Definition.Object objectMap <- result = do
|
| Definition.Object objectMap <- result = do
|
||||||
abstractType <- resolveAbstractType (AbstractUnionType unionType) objectMap
|
abstractType <- resolveAbstractType (AbstractUnionType unionType) objectMap
|
||||||
case abstractType of
|
case abstractType of
|
||||||
Just objectType -> executeSelectionSet result objectType seqSelection
|
Just objectType -> executeSelectionSet result objectType
|
||||||
|
$ mergeSelectionSets fields
|
||||||
Nothing -> errmsg "Value completion failed."
|
Nothing -> errmsg "Value completion failed."
|
||||||
completeValue _ _ _ = errmsg "Value completion failed."
|
completeValue _ _ _ = errmsg "Value completion failed."
|
||||||
|
|
||||||
|
mergeSelectionSets :: Monad m => NonEmpty (Field m) -> Seq (Selection m)
|
||||||
|
mergeSelectionSets fields = foldr forEach mempty fields
|
||||||
|
where
|
||||||
|
forEach (Field _ _ _ fieldSelectionSet) selectionSet =
|
||||||
|
selectionSet <> fieldSelectionSet
|
||||||
|
|
||||||
errmsg :: Monad m => Text -> CollectErrsT m Aeson.Value
|
errmsg :: Monad m => Text -> CollectErrsT m Aeson.Value
|
||||||
errmsg errorMessage = addErrMsg errorMessage >> pure Aeson.Null
|
errmsg errorMessage = addErrMsg errorMessage >> pure Aeson.Null
|
||||||
|
|
||||||
@ -154,17 +162,16 @@ executeSelectionSet :: Monad m
|
|||||||
-> Seq (Selection m)
|
-> Seq (Selection m)
|
||||||
-> CollectErrsT m Aeson.Value
|
-> CollectErrsT m Aeson.Value
|
||||||
executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do
|
executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do
|
||||||
resolvedValues <- Map.traverseMaybeWithKey forEach
|
let fields = collectFields objectType selectionSet
|
||||||
$ collectFields objectType selectionSet
|
resolvedValues <- Map.traverseMaybeWithKey forEach fields
|
||||||
pure $ Aeson.toJSON resolvedValues
|
pure $ Aeson.toJSON resolvedValues
|
||||||
where
|
where
|
||||||
forEach _responseKey (field :<| _) =
|
forEach _ fields@(field :| _) =
|
||||||
let Field _ name _ _ = field
|
let Field _ name _ _ = field
|
||||||
in traverse (tryResolver field) $ lookupResolver name
|
in traverse (tryResolver fields) $ lookupResolver name
|
||||||
forEach _ _ = pure Nothing
|
|
||||||
lookupResolver = flip HashMap.lookup resolvers
|
lookupResolver = flip HashMap.lookup resolvers
|
||||||
tryResolver typeField field =
|
tryResolver fields resolver =
|
||||||
executeField result typeField field >>= lift . pure
|
executeField resolver result fields >>= lift . pure
|
||||||
|
|
||||||
coerceArgumentValues
|
coerceArgumentValues
|
||||||
:: HashMap Name In.Argument
|
:: HashMap Name In.Argument
|
||||||
|
@ -22,21 +22,54 @@ schema = Schema {query = queryType, mutation = Nothing}
|
|||||||
|
|
||||||
queryType :: Out.ObjectType Identity
|
queryType :: Out.ObjectType Identity
|
||||||
queryType = Out.ObjectType "Query" Nothing []
|
queryType = Out.ObjectType "Query" Nothing []
|
||||||
$ HashMap.singleton "count"
|
$ HashMap.singleton "philosopher"
|
||||||
$ Out.Resolver countField
|
$ Out.Resolver philosopherField
|
||||||
$ pure
|
$ pure
|
||||||
$ Int 8
|
$ Object mempty
|
||||||
where
|
where
|
||||||
countField = Out.Field Nothing (Out.NonNullScalarType int) HashMap.empty
|
philosopherField =
|
||||||
|
Out.Field Nothing (Out.NonNullObjectType philosopherType) HashMap.empty
|
||||||
|
|
||||||
|
philosopherType :: Out.ObjectType Identity
|
||||||
|
philosopherType = Out.ObjectType "Philosopher" Nothing []
|
||||||
|
$ HashMap.fromList resolvers
|
||||||
|
where
|
||||||
|
resolvers =
|
||||||
|
[ ("firstName", firstNameResolver)
|
||||||
|
, ("lastName", lastNameResolver)
|
||||||
|
]
|
||||||
|
firstNameResolver = Out.Resolver firstNameField $ pure $ String "Friedrich"
|
||||||
|
lastNameResolver = Out.Resolver lastNameField $ pure $ String "Nietzsche"
|
||||||
|
firstNameField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty
|
||||||
|
lastNameField = Out.Field Nothing (Out.NonNullScalarType string) HashMap.empty
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec =
|
spec =
|
||||||
describe "execute" $
|
describe "execute" $ do
|
||||||
it "skips unknown fields" $
|
it "skips unknown fields" $
|
||||||
let expected = Aeson.object
|
let expected = Aeson.object
|
||||||
["data" .= Aeson.object ["count" .= (8 :: Int)]]
|
[ "data" .= Aeson.object
|
||||||
|
[ "philosopher" .= Aeson.object
|
||||||
|
[ "firstName" .= ("Friedrich" :: String)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
execute' = execute schema (mempty :: HashMap Name Aeson.Value)
|
execute' = execute schema (mempty :: HashMap Name Aeson.Value)
|
||||||
actual = runIdentity
|
actual = runIdentity
|
||||||
$ either parseError execute'
|
$ either parseError execute'
|
||||||
$ parse document "" "{ count number }"
|
$ parse document "" "{ philosopher { firstName surname } }"
|
||||||
|
in actual `shouldBe` expected
|
||||||
|
it "merges selections" $
|
||||||
|
let expected = Aeson.object
|
||||||
|
[ "data" .= Aeson.object
|
||||||
|
[ "philosopher" .= Aeson.object
|
||||||
|
[ "firstName" .= ("Friedrich" :: String)
|
||||||
|
, "lastName" .= ("Nietzsche" :: String)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
execute' = execute schema (mempty :: HashMap Name Aeson.Value)
|
||||||
|
actual = runIdentity
|
||||||
|
$ either parseError execute'
|
||||||
|
$ parse document "" "{ philosopher { firstName } philosopher { lastName } }"
|
||||||
in actual `shouldBe` expected
|
in actual `shouldBe` expected
|
||||||
|
Loading…
Reference in New Issue
Block a user