morpheus-graphql 0.2.1 → 0.2.2
raw patch · 57 files changed
+1786/−1153 lines, 57 files
Files
- README.md +404/−0
- assets/simple.gql +4/−0
- changelog.md +31/−0
- examples/Main.hs +3/−4
- examples/Subscription/SimpleSubscription.hs +47/−0
- examples/TH/API.hs +15/−1
- morpheus-graphql.cabal +19/−12
- src/Data/Morpheus/Client.hs +1/−1
- src/Data/Morpheus/Document.hs +27/−7
- src/Data/Morpheus/Error/Document/Interface.hs +35/−0
- src/Data/Morpheus/Execution/Client/Compile.hs +3/−2
- src/Data/Morpheus/Execution/Client/Selection.hs +15/−12
- src/Data/Morpheus/Execution/Document/Compile.hs +8/−7
- src/Data/Morpheus/Execution/Server/Resolve.hs +2/−2
- src/Data/Morpheus/Parsing/Document/DataType.hs +55/−29
- src/Data/Morpheus/Parsing/Document/Parse.hs +0/−24
- src/Data/Morpheus/Parsing/Document/Parser.hs +3/−18
- src/Data/Morpheus/Parsing/Internal/Terms.hs +9/−1
- src/Data/Morpheus/Parsing/Request/Operation.hs +3/−3
- src/Data/Morpheus/Parsing/Request/Value.hs +9/−14
- src/Data/Morpheus/Types/IO.hs +9/−0
- src/Data/Morpheus/Types/Internal/Data.hs +8/−0
- src/Data/Morpheus/Validation/Arguments.hs +0/−146
- src/Data/Morpheus/Validation/Document/Validation.hs +54/−0
- src/Data/Morpheus/Validation/Fragment.hs +0/−127
- src/Data/Morpheus/Validation/Input/Enum.hs +0/−16
- src/Data/Morpheus/Validation/Input/Object.hs +0/−97
- src/Data/Morpheus/Validation/Internal/Utils.hs +86/−0
- src/Data/Morpheus/Validation/Query/Arguments.hs +94/−0
- src/Data/Morpheus/Validation/Query/Fragment.hs +102/−0
- src/Data/Morpheus/Validation/Query/Input/Enum.hs +16/−0
- src/Data/Morpheus/Validation/Query/Input/Object.hs +70/−0
- src/Data/Morpheus/Validation/Query/Selection.hs +178/−0
- src/Data/Morpheus/Validation/Query/Utils/Selection.hs +29/−0
- src/Data/Morpheus/Validation/Query/Validation.hs +47/−0
- src/Data/Morpheus/Validation/Query/Variable.hs +101/−0
- src/Data/Morpheus/Validation/Selection.hs +0/−226
- src/Data/Morpheus/Validation/Utils/Selection.hs +0/−44
- src/Data/Morpheus/Validation/Utils/Utils.hs +0/−85
- src/Data/Morpheus/Validation/Validation.hs +0/−70
- src/Data/Morpheus/Validation/Variable.hs +0/−130
- test/Feature/Holistic/API.hs +8/−9
- test/Feature/Holistic/cases.json +8/−0
- test/Feature/Holistic/parsing/invalidFields/response.json +1/−1
- test/Feature/Holistic/parsing/invalidNotNullOperator/response.json +1/−1
- test/Feature/Holistic/parsing/missingCloseBrace/response.json +1/−1
- test/Feature/Holistic/parsing/multilineComments/query.gql +131/−0
- test/Feature/Holistic/parsing/multilineComments/response.json +15/−0
- test/Feature/Holistic/parsing/singleLineComments/query.gql +57/−0
- test/Feature/Holistic/parsing/singleLineComments/response.json +15/−0
- test/Feature/Input/Enum/API.hs +5/−6
- test/Feature/InputType/API.hs +6/−7
- test/Feature/Schema/API.hs +7/−8
- test/Feature/UnionType/API.hs +6/−8
- test/Feature/WrappedTypeName/API.hs +8/−8
- test/Lib.hs +3/−2
- test/TestFeature.hs +27/−24
+ README.md view
@@ -0,0 +1,404 @@+# Morpheus GraphQL [](https://hackage.haskell.org/package/morpheus-graphql) [](https://travis-ci.com/morpheusgraphql/morpheus-graphql)++Build GraphQL APIs with your favourite functional language!++Morpheus GraphQL (Server & Client) helps you to build GraphQL APIs in Haskell with native haskell types.+Morpheus will convert your haskell types to a GraphQL schema and all your resolvers are just native Haskell functions. Mopheus GraphQL can also convert your GraphQL Schema or Query to Haskell types and validate them in compile time.++Morpheus is still in an early stage of development, so any feedback is more than welcome, and we appreciate any contribution!+Just open an issue here on GitHub, or join [our Slack channel](https://morpheus-graphql-slack-invite.herokuapp.com/) to get in touch.++## Getting Started++### Setup++To get started with Morpheus, you first need to add it to your project's dependencies, as follows (assuming you're using hpack):++package.yml++```yaml+dependencies:+ - morpheus-graphql+```++Additionally, you should tell stack which version to pick:++stack.yml++```yaml+resolver: lts-12.0 # or greater+extra-deps:+ - megaparsec-7.0.5+ - aeson-1.4.4.0+ - time-compat-1.9.2.2+ - morpheus-graphql-0.2.2+```++As Morpheus is quite new, make sure stack can find morpheus-graphql by running `stack update`++### Building your first GrqphQL API++### with GraphQL syntax and Haskell QuasiQuotes++```haskell++[gqlDocument|+ type Query {+ deity (uid: Text!): Deity!+ }++ type Deity {+ name : Text!+ power : Text+ }+|]++rootResolver :: GQLRootResolver IO () () Query () ()+rootResolver =+ GQLRootResolver {queryResolver = return Query {deity}, mutationResolver = pure (), subscriptionResolver = pure ()}+ where+ deity DeityArgs {uid} = pure Deity {name, power}+ where+ name _ = pure "Morpheus"+ power _ = pure (Just "Shapeshifting")++gqlApi :: ByteString -> IO ByteString+gqlApi = interpreter rootResolver+```++Template Haskell Generates types: `Query` , `Deity`, `DeityArgs`, that can be used by `rootResolver`++generated types are not compatible with `Mutation`, `Subscription`,+they can be used only in `Query`, but this issue will be fixed in next release++### with Native Haskell Types++To define a GraphQL API with Morpheus we start by defining the API Schema as a native Haskell data type,+which derives the `Generic` typeclass. Lazily resolvable fields on this `Query` type are defined via `a -> IORes b`, representing resolving a set of arguments `a` to a concrete value `b`.++```haskell+data Query = Query+ { deity :: DeityArgs -> IORes Deity+ } deriving (Generic)++data Deity = Deity+ { fullName :: Text -- Non-Nullable Field+ , power :: Maybe Text -- Nullable Field+ } deriving (Generic)++instance GQLType Deity where+ type KIND Deity = OBJECT++data DeityArgs = DeityArgs+ { name :: Text -- Required Argument+ , mythology :: Maybe Text -- Optional Argument+ } deriving (Generic)+```++For each field in the `Query` type defined via `a -> IORes b` (like `deity`) we will define a resolver implementation that provides the values during runtime by referring to+some data source, e.g. a database or another API. Fields that are defined without `a -> IORes b` you can just provide a value.++In above example, the field of `DeityArgs` could also be named using reserved identities (such as: `type`, `where`, etc), in order to avoid conflict, a prime symbol (`'`) must be attached. For example, you can have:++```haskell+data DeityArgs = DeityArgs+ { name :: Text -- Required Argument+ , mythology :: Maybe Text -- Optional Argument+ , type' :: Text+ } deriving (Generic)+```++The field name in the final request will be `type` instead of `type'`. The Morpheus request parser converts each of the reserved identities in Haskell 2010 to their corresponding names internally. This also applies to selections.++```haskell+resolveDeity :: DeityArgs -> IORes Deity+resolveDeity args = gqlResolver $ askDB (name args) (mythology args)++askDB :: Text -> Maybe Text -> IO (Either String Deity)+askDB = ...+```++Note that the type `a -> IORes b` is just Synonym for `a -> ExceptT String IO b`++To make this `Query` type available as an API, we define a `GQLRootResolver` and feed it to the Morpheus `interpreter`. A `GQLRootResolver` consists of `query`, `mutation` and `subscription` definitions, while we omit the latter for this example:++```haskell+rootResolver :: GQLRootResolver IO () () Query () ()+rootResolver =+ GQLRootResolver+ { queryResolver = return Query {deity = resolveDeity}+ , mutationResolver = return ()+ , subscriptionResolver = return ()+ }++gqlApi :: ByteString -> IO ByteString+gqlApi = interpreter rootResolver+```++As you can see, the API is defined as `ByteString -> IO ByteString` which we can either invoke directly or use inside an arbitrary web framework+such as `scotty` or `serverless-haskell`. We'll go for `scotty` in this example:++```haskell+main :: IO ()+main = scotty 3000 $ post "/api" $ raw =<< (liftIO . gqlApi =<< body)+```++If we now send a POST request to `http://localhost:3000/api` with a GraphQL Query as body for example in a tool like `Insomnia`:++```GraphQL+query GetDeity {+ deity (name: "Morpheus") {+ fullName+ power+ }+}+```++our query will be resolved!++```JSON+{+ "data": {+ "deity": {+ "fullName": "Morpheus",+ "power": "Shapeshifting"+ }+ }+}+```++## Serverless Example++If you are interested in creating a `Morpheus GraphQL` API with `Serverless`, you should take a look at our example in this repository:+[_Mythology API_](https://github.com/morpheusgraphql/mythology-api) it is our example project build with `Morpheus GraphQL` and `Serverless-Haskell`,+where you can query different mythology characters with `GraphiQL`.++Mythology API is deployed on : [_api.morpheusgraphql.com_](https://api.morpheusgraphql.com) where you can test it with `GraphiQL`++++## Advanced topics++### Enums++You can use Union Types as Enums, but they're not allowed to have any parameters.++```haskell+data City+ = Athens+ | Sparta+ | Corinth+ | Delphi+ | Argos+ deriving (Generic)++instance GQLType City where+ type KIND City = ENUM+```++### Union types++To use union type, all you have to do is derive the `GQLType` class. Using GraphQL [_fragments_](https://graphql.org/learn/queries/#fragments), the arguments of each data constructor can be accessed from the GraphQL client.++```haskell+data Character+ = DEITY Deity+ | HUMAN Human+ deriving (Generic)++instance GQLType Character where+ type KIND City = UNION+```++### Scalar types++To use custom scalar types, you need to provide implementations for `parseValue` and `serialize` respectively.++```haskell+data Odd = Odd Int deriving (Generic)++instance GQLScalar Odd where+ parseValue (Int x) = pure $ Odd (... )+ parseValue (String x) = pure $ Odd (... )+ serialize (Odd value) = Int value++instance GQLType Odd where+ type KIND Odd = SCALAR+```++### Applicative and Monad instance++The `Resolver` type has `Applicative` and `Monad` instances that can be used to compose resolvers.++### Introspection++Morpheus converts your schema to a GraphQL introspection automatically. You can use tools like `Insomnia` to take a+look at the introspection and validate your schema.+If you need a description for your GQLType inside of the introspection you can define the GQLType instance manually+and provide an implementation for the `description` function:++```haskell+data Deity = Deity+{ ...+} deriving (Generic)++instance GQLType Deity where+ description = const "A supernatural being considered divine and sacred"+```++screenshots from `Insomnia`++++++### Mutations++In addition to queries, Morpheus also supports mutations. The behave just like regular queries and are defined similarly:+Just exchange deriving `GQLQuery` for `GQLMutation` and declare them separately at the `GQLRootResolver` definition++```haskell+newtype Mutation = Mutation+ { createDeity :: Form -> IOMutRes Deity+ } deriving (Generic)++createDeityMutation :: Form -> IOMutRes Deity+createDeityMutation = ...++rootResolver :: GQLRootResolver IO Query Mutation ()+rootResolver =+ GQLRootResolver+ { queryResolver = return Query {...}+ , mutationResolver = return Mutation {+ createDeity = createDeityMutation+ }+ , subscriptionResolver = return ()+ }++gqlApi :: ByteString -> IO ByteString+gqlApi = interpreter rootResolver+```++### Subscriptions++im morpheus subscription and mutation communicating with Events,+`Event` consists with user defined `Channel` and `Content`.++every subscription has own Channel by which will be triggered++```haskell++data Channel+ = ChannelA+ | ChannelB++data Content+ = ContentA Int+ | ContentB Text++newtype Query = Query+ { deity :: () -> IORes Deity+ } deriving (Generic)++newtype Mutation = Mutation+ { createDeity :: () -> IOMutRes Channel Content Deity+ } deriving (Generic)++newtype Subscription = Subscription+ { newDeity :: () -> IOSubRes Channel Content Deity+ } deriving (Generic)++rootResolver :: GQLRootResolver IO Channel Content Query Mutation Subscription+rootResolver =+ GQLRootResolver+ { queryResolver = return Query {deity = const fetchDeity}+ , mutationResolver = return Mutation {createDeity}+ , subscriptionResolver = return Subscription {newDeity}+ }+ where+ fetchDeity = resolver $ dbDeity "" Nothing+ createDeity _args = toMutResolver [Event {channels = [ChannelA], content = ContentA 1}] fetchDeity+ newDeity _args = Event {channels = [ChannelA], content}+ where+ content (Event [ChannelA] (ContentA _value)) = resolver $ dbDeity "" Nothing -- resolve New State+ content (Event [ChannelA] (ContentB value)) = resolver $ dbDeity value Nothing -- resolve New State+ content _ = fetchDeity -- Resolve Old State+```++## Morpheus `GraphQL Client` with Template haskell QuasiQuotes++```haskell+defineByDocumentFile+ "./schema.gql"+ [gql|+ query GetHero ($byRealm: Realm)+ {+ deity (realm:$byRealm) {+ power+ fullName+ }+ }+ |]+```++will validate query and Generate:++- response type `GetHero`, `Deity` with `Lens` Instances+- input types: `GetHeroArgs` , `Realm`+- instance for `Fetch` typeClass++so that++```haskell+ fetchHero :: Args GetHero -> m (Either String GetHero)+ fetchHero = fetch jsonRes args+ where+ args = GetHeroArgs {byRealm = Just Realm {owner = "Zeus", surface = Just 10}}+ jsonRes :: ByteString -> m ByteString+ jsonRes = <GraphQL APi>+```++resolves well typed response `GetHero`.++except: `defineByDocumentFile` you can use:++```haskell+defineByIntrospectionFile "./introspection.json"+```++or++`defineByIntrospection` where you can directly connect it to server++## Morpheus CLI for Code Generating++Generating dummy Morpheus Api from `schema.gql`++```+morpheus build src/schem.gql src/GQLApi.hs+```++this command will generate Haskell API and resolvers,+resolvers will resolve default values for every object++# About++## The name++_Morpheus_ is the greek god of sleep and dreams whose name comes from the greek word _μορφή_ meaning form or shape.+He is said to be able to mimic different forms and GraphQL is good at doing exactly that: Transforming data in the shape+of many different APIs.++## Team++Morpheus is written and maintained by [_nalchevanidze_](https://github.com/nalchevanidze)++## Roadmap++- Medium future:+ - Stabilize API+ - Specification-isomorphic introspection+ - Specification-isomorphic error handling+- Long term:+ - Support all possible GQL features+ - Performance optimization
assets/simple.gql view
@@ -4,6 +4,10 @@ hero: Human! } +interface MyInterface {+ name: String+}+ type Mutation { createDeity (deityName: [[[[[String!]]!]]], deityMythology: String): Deity! createCharacter (charRealm: Realm! , charMutID: String! ): Character!
changelog.md view
@@ -1,3 +1,34 @@+## [0.2.2] - 30.08.2019++### Fixed+- Parser Supports GraphQL multiline comments+- Morpheus GraphQL Client: Support GraphQL Alias+- Support of GraphQL Interfaces on GraphQL Document:+ ```gql+ # simple.gql+ interface Node {+ nodeId: ID!+ }++ type SimpleType implements Node {+ nodeId: ID!+ name: String!+ }+ ```++ morpheus compiler will read interfaces and validate implements.+ template haskell will generate haskell types only for types not for interfaces.++ haskell type from `simple.gql`:+ ```haskell+ data SimpleType = SimpleType {+ nodeId :: ID!+ name :: Text!+ } deriving (Generic)+ ```++ at the time compiler does not validates field Arguments by interface+ ## [0.2.1] - 23.08.2019 - assets are added to cabal source files
examples/Main.hs view
@@ -22,12 +22,11 @@ import Data.Morpheus.Types (ScalarValue (..)) import Deprecated.API (Channel, Content, gqlRoot) import Mythology.API (mythologyApi)-import TH.API (thApi)- import qualified Network.Wai as Wai import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.WebSockets as WaiWs import Network.WebSockets (defaultConnectionOptions)+import TH.API (thApi) import Web.Scotty (body, file, get, post, raw, scottyApp) ioRes :: ByteString -> IO ByteString@@ -42,8 +41,8 @@ # Query Hero with Compile time Validation query GetUser ($userCoordinates: Coordinates!) {- user {- name+ myUser: user {+ boo3: name email address (coordinates: $userCoordinates ){ city
+ examples/Subscription/SimpleSubscription.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}++module Subscription.SimpleSubscription where++import Data.Morpheus.Types (Event (..), GQLRootResolver (..), IOMutRes, IORes, IOSubRes, resolver,+ toMutResolver)+import Data.Text (Text)+import GHC.Generics (Generic)+import Mythology.Character.Deity (Deity (..), dbDeity)++data Channel+ = ChannelA+ | ChannelB++data Content+ = ContentA Int+ | ContentB Text++newtype Query = Query+ { deity :: () -> IORes Deity+ } deriving (Generic)++newtype Mutation = Mutation+ { createDeity :: () -> IOMutRes Channel Content Deity+ } deriving (Generic)++newtype Subscription = Subscription+ { newDeity :: () -> IOSubRes Channel Content Deity+ } deriving (Generic)++rootResolver :: GQLRootResolver IO Channel Content Query Mutation Subscription+rootResolver =+ GQLRootResolver+ { queryResolver = return Query {deity = const fetchDeity}+ , mutationResolver = return Mutation {createDeity}+ , subscriptionResolver = return Subscription {newDeity}+ }+ where+ fetchDeity = resolver $ dbDeity "" Nothing+ createDeity _args = toMutResolver [Event {channels = [ChannelA], content = ContentA 1}] fetchDeity+ newDeity _args = Event {channels = [ChannelA], content}+ where+ content (Event [ChannelA] (ContentA _value)) = resolver $ dbDeity "" Nothing -- resolve New State+ content (Event [ChannelA] (ContentB value)) = resolver $ dbDeity value Nothing -- resolve New State+ content _ = fetchDeity -- Resolve Old State
examples/TH/API.hs view
@@ -16,7 +16,7 @@ import Data.Morpheus (interpreter) import Data.Morpheus.Document (gqlDocument) import Data.Morpheus.Kind (SCALAR)-import Data.Morpheus.Types (GQLRootResolver (..), GQLScalar (..), GQLType (..), IORes,+import Data.Morpheus.Types (GQLRootResolver (..), GQLScalar (..), GQLType (..), ID, IORes, ScalarValue (..)) newtype Euro =@@ -37,6 +37,10 @@ user : User! } ++ """+ GraphQL Multiline Comment+ """ type User { name : Text! email : Text!@@ -62,6 +66,16 @@ input Coordinates { latitude : Euro! longitude : [[UID!]]!+ }++ # simple.gql+ interface Node {+ nodeId: ID!+ }++ type SimpleType implements Node {+ nodeId: ID!+ interfaceName: String! } input UID {
morpheus-graphql.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 5dffd2e1cb2921b0bc958c61fb047928492b4b4ac933979f6f3cf12e5f2d49b9+-- hash: e5802f5202e1f3d85dbcbbfa5bbeccbe45394f9039335d2315915df0cee9d49a name: morpheus-graphql-version: 0.2.1+version: 0.2.2 synopsis: Morpheus GraphQL description: Build GraphQL APIs with your favourite functional language! category: web, graphql@@ -24,6 +24,7 @@ assets/pubsub.gql assets/simple.gql changelog.md+ README.md data-files: test/Feature/Holistic/API.gql test/Feature/Holistic/arguments/nameConflict/query.gql@@ -97,9 +98,13 @@ test/Feature/Holistic/parsing/invalidNotNullOperator/response.json test/Feature/Holistic/parsing/missingCloseBrace/query.gql test/Feature/Holistic/parsing/missingCloseBrace/response.json+ test/Feature/Holistic/parsing/multilineComments/query.gql+ test/Feature/Holistic/parsing/multilineComments/response.json test/Feature/Holistic/parsing/notNullSpacing/query.gql test/Feature/Holistic/parsing/notNullSpacing/response.json test/Feature/Holistic/parsing/notNullSpacing/variables.json+ test/Feature/Holistic/parsing/singleLineComments/query.gql+ test/Feature/Holistic/parsing/singleLineComments/response.json test/Feature/Holistic/selection/AliasNameConflict/query.gql test/Feature/Holistic/selection/AliasNameConflict/response.json test/Feature/Holistic/selection/AliasResolve/query.gql@@ -223,6 +228,7 @@ other-modules: Data.Morpheus.Error.Arguments Data.Morpheus.Error.Client.Client+ Data.Morpheus.Error.Document.Interface Data.Morpheus.Error.Fragment Data.Morpheus.Error.Input Data.Morpheus.Error.Internal@@ -254,7 +260,6 @@ Data.Morpheus.Parsing.Client.ParseMeta Data.Morpheus.Parsing.Client.Parser Data.Morpheus.Parsing.Document.DataType- Data.Morpheus.Parsing.Document.Parse Data.Morpheus.Parsing.Document.Parser Data.Morpheus.Parsing.Internal.Create Data.Morpheus.Parsing.Internal.Internal@@ -300,15 +305,16 @@ Data.Morpheus.Types.IO Data.Morpheus.Types.Resolver Data.Morpheus.Types.Types- Data.Morpheus.Validation.Arguments- Data.Morpheus.Validation.Fragment- Data.Morpheus.Validation.Input.Enum- Data.Morpheus.Validation.Input.Object- Data.Morpheus.Validation.Selection- Data.Morpheus.Validation.Utils.Selection- Data.Morpheus.Validation.Utils.Utils- Data.Morpheus.Validation.Validation- Data.Morpheus.Validation.Variable+ Data.Morpheus.Validation.Document.Validation+ Data.Morpheus.Validation.Internal.Utils+ Data.Morpheus.Validation.Query.Arguments+ Data.Morpheus.Validation.Query.Fragment+ Data.Morpheus.Validation.Query.Input.Enum+ Data.Morpheus.Validation.Query.Input.Object+ Data.Morpheus.Validation.Query.Selection+ Data.Morpheus.Validation.Query.Utils.Selection+ Data.Morpheus.Validation.Query.Validation+ Data.Morpheus.Validation.Query.Variable Paths_morpheus_graphql hs-source-dirs: src@@ -344,6 +350,7 @@ Mythology.Character.Deity Mythology.Character.Human Mythology.Place.Places+ Subscription.SimpleSubscription TH.API TH.Simple Paths_morpheus_graphql
src/Data/Morpheus/Client.hs view
@@ -17,11 +17,11 @@ import Language.Haskell.TH import Language.Haskell.TH.Quote +import Data.Morpheus.Document (parseFullGQLDocument) -- MORPHEUS import Data.Morpheus.Execution.Client.Build (defineQuery) import Data.Morpheus.Execution.Client.Compile (compileSyntax) import Data.Morpheus.Execution.Client.Fetch (Fetch (..))-import Data.Morpheus.Parsing.Document.Parse (parseFullGQLDocument) import Data.Morpheus.Parsing.JSONSchema.Parse (decodeIntrospection) import Data.Morpheus.Types.Internal.Data (DataTypeLib) import Data.Morpheus.Types.Internal.Validation (Validation)
src/Data/Morpheus/Document.hs view
@@ -5,19 +5,39 @@ ( toGraphQLDocument , toMorpheusHaskellAPi , gqlDocument+ , parseFullGQLDocument ) where -import Data.ByteString.Lazy.Char8 (ByteString, pack)+import Control.Monad ((>=>))+import Data.ByteString.Lazy.Char8 (ByteString, pack)+import Data.Text (Text)+import qualified Data.Text.Lazy as LT (toStrict)+import Data.Text.Lazy.Encoding (decodeUtf8) import Language.Haskell.TH.Quote -- -- MORPHEUS-import Data.Morpheus.Execution.Document.Compile (compileDec, compileExp)-import Data.Morpheus.Execution.Server.Resolve (RootResCon, fullSchema)-import Data.Morpheus.Parsing.Document.Parse (parseGraphQLDocument)-import Data.Morpheus.Rendering.GQL (renderGraphQLDocument)-import Data.Morpheus.Rendering.Haskell.Render (renderHaskellDocument)-import Data.Morpheus.Types (GQLRootResolver)+import Data.Morpheus.Execution.Document.Compile (compileDec, compileExp)+import Data.Morpheus.Execution.Server.Resolve (RootResCon, fullSchema)+import Data.Morpheus.Rendering.GQL (renderGraphQLDocument)+import Data.Morpheus.Rendering.Haskell.Render (renderHaskellDocument)+import Data.Morpheus.Types (GQLRootResolver)++import Data.Morpheus.Parsing.Document.Parser (parseTypes)+import Data.Morpheus.Parsing.Internal.Create (createDataTypeLib)+import Data.Morpheus.Schema.SchemaAPI (defaultTypes)+import Data.Morpheus.Types.Internal.Data (DataTypeLib)+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Validation.Document.Validation (validatePartialDocument)++parseDocument :: Text -> Validation DataTypeLib+parseDocument doc = parseTypes doc >>= validatePartialDocument >>= createDataTypeLib++parseGraphQLDocument :: ByteString -> Validation DataTypeLib+parseGraphQLDocument x = parseDocument (LT.toStrict $ decodeUtf8 x)++parseFullGQLDocument :: ByteString -> Validation DataTypeLib+parseFullGQLDocument = parseGraphQLDocument >=> defaultTypes -- | Generates schema.gql file from 'GQLRootResolver' toGraphQLDocument :: RootResCon m e c query mut sub => proxy (GQLRootResolver m e c query mut sub) -> ByteString
+ src/Data/Morpheus/Error/Document/Interface.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}++module Data.Morpheus.Error.Document.Interface+ ( unknownInterface+ , partialImplements+ , ImplementsError(..)+ ) where++import Data.Morpheus.Error.Utils (globalErrorMessage)+import Data.Morpheus.Types.Internal.Base (Key)+import Data.Morpheus.Types.Internal.Validation (GQLError (..), GQLErrors)+import Data.Semigroup ((<>))++unknownInterface :: Key -> GQLErrors+unknownInterface name = globalErrorMessage message+ where+ message = "Unknown Interface \"" <> name <> "\"."++data ImplementsError+ = UnexpectedType { expectedType :: Key+ , foundType :: Key }+ | UndefinedField++partialImplements :: Key -> [(Key, Key, ImplementsError)] -> GQLErrors+partialImplements name = map impError+ where+ impError (interfaceName, key, errorType) = GQLError {desc = message, positions = []}+ where+ message =+ "type \"" <> name <> "\" implements Interface \"" <> interfaceName <> "\" Partially," <>+ detailedMessage errorType+ detailedMessage UnexpectedType {expectedType, foundType} =+ " on key \"" <> key <> "\" expected type \"" <> expectedType <> "\" found \"" <> foundType <> "\"."+ detailedMessage UndefinedField = " key \"" <> key <> "\" not found ."
src/Data/Morpheus/Execution/Client/Compile.hs view
@@ -17,13 +17,14 @@ import qualified Data.Morpheus.Types.Internal.AST.Operation as O (Operation (..)) import Data.Morpheus.Types.Internal.Data (DataTypeLib) import Data.Morpheus.Types.IO (GQLRequest (..))+ -- -- Morpheus import Data.Morpheus.Types.Internal.DataD (QueryD (..)) import Data.Morpheus.Types.Internal.Validation (Validation) import Data.Morpheus.Types.Types (GQLQueryRoot (..))-import Data.Morpheus.Validation.Utils.Utils (VALIDATION_MODE (..))-import Data.Morpheus.Validation.Validation (validateRequest)+import Data.Morpheus.Validation.Internal.Utils (VALIDATION_MODE (..))+import Data.Morpheus.Validation.Query.Validation (validateRequest) compileSyntax :: String -> Q Exp compileSyntax queryText =
src/Data/Morpheus/Execution/Client/Selection.hs view
@@ -13,7 +13,7 @@ -- -- MORPHEUS-import Data.Morpheus.Error.Internal (internalUnknownTypeMessage)+import Data.Morpheus.Error.Utils (globalErrorMessage) import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), ValidOperation, Variable (..), VariableDefinitions) import Data.Morpheus.Types.Internal.AST.Selection (Selection (..), SelectionRec (..))@@ -22,10 +22,10 @@ allDataTypes) import Data.Morpheus.Types.Internal.DataD (ConsD (..), FieldD (..), TypeD (..), gqlToHSWrappers) import Data.Morpheus.Types.Internal.Validation (GQLErrors, Validation)-import Data.Morpheus.Validation.Utils.Utils (lookupType)+import Data.Morpheus.Validation.Internal.Utils (lookupType) compileError :: Text -> GQLErrors-compileError x = internalUnknownTypeMessage $ " \"" <> x <> "\" ;"+compileError x = globalErrorMessage $ "Unhandled Compile Time Error: \"" <> x <> "\" ;" operationTypes :: DataTypeLib -> VariableDefinitions -> ValidOperation -> Validation ([TypeD], [TypeD]) operationTypes lib variables = genOperation@@ -92,11 +92,13 @@ genFields datatype = mapM typeNameFromField where typeNameFromField :: (Text, Selection) -> Validation FieldD- typeNameFromField (key, _) = FieldD (unpack key) <$> wrType- where- wrType = do- (newType, wrappers) <- fieldDataType datatype key- pure $ gqlToHSWrappers wrappers (unpack $ typeFrom newType)+ typeNameFromField (key, Selection {selectionRec = SelectionAlias {aliasFieldName}}) =+ FieldD (unpack key) <$> lookupFieldType aliasFieldName+ typeNameFromField (key, _) = FieldD (unpack key) <$> lookupFieldType key+ ------------------------------------------------------------+ lookupFieldType key = do+ (newType, wrappers) <- fieldDataType datatype key+ pure $ gqlToHSWrappers wrappers (unpack $ typeFrom newType) -------------------------------------------- genRecordType name dataType selectionSet = do (con, subTypes) <- getCon name dataType selectionSet@@ -105,14 +107,16 @@ newFieldTypes parentType = fmap concat <$> mapM validateSelection where validateSelection :: (Text, Selection) -> Validation [TypeD]- validateSelection (key, Selection {selectionRec = SelectionSet selectionSet}) = do- datatype <- key `typeByField` parentType- genRecordType (typeFrom datatype) datatype selectionSet validateSelection (key, Selection {selectionRec = SelectionField}) = key `typeByField` parentType >>= buildSelField where buildSelField (Leaf x) = buildLeaf x buildSelField _ = Left $ compileError "Invalid schema Expected scalar"+ validateSelection (key, Selection {selectionRec = SelectionSet selectionSet}) = do+ datatype <- key `typeByField` parentType+ genRecordType (typeFrom datatype) datatype selectionSet+ validateSelection (_, selection@Selection {selectionRec = SelectionAlias {aliasFieldName, aliasSelection}}) =+ validateSelection (aliasFieldName, selection {selectionRec = aliasSelection}) validateSelection (key, Selection {selectionRec = UnionSelection unionSelections}) = do unionTypeName <- typeFrom <$> key `typeByField` parentType (tCons, subTypes) <- unzip <$> mapM getUnionType unionSelections@@ -121,7 +125,6 @@ getUnionType (typeKey, selSet) = do conDatatype <- getType lib typeKey getCon typeKey conDatatype selSet- validateSelection _ = pure [] buildLeaf :: DataLeaf -> Validation [TypeD] buildLeaf (LeafEnum DataType {typeName, typeData}) =
src/Data/Morpheus/Execution/Document/Compile.hs view
@@ -7,24 +7,25 @@ , compileDec ) where -import qualified Data.Text as T (pack)+import qualified Data.Text as T (pack) import Language.Haskell.TH -- -- Morpheus-import Data.Morpheus.Error.Client.Client (renderGQLErrors)-import Data.Morpheus.Execution.Document.Convert (renderTHTypes)-import Data.Morpheus.Execution.Document.Declare (declareTypes)-import Data.Morpheus.Parsing.Document.Parser (parseTypes)+import Data.Morpheus.Error.Client.Client (renderGQLErrors)+import Data.Morpheus.Execution.Document.Convert (renderTHTypes)+import Data.Morpheus.Execution.Document.Declare (declareTypes)+import Data.Morpheus.Parsing.Document.Parser (parseTypes)+import Data.Morpheus.Validation.Document.Validation compileExp :: String -> Q Exp compileExp documentTXT =- case parseTypes (T.pack documentTXT) >>= renderTHTypes of+ case parseTypes (T.pack documentTXT) >>= validatePartialDocument >>= renderTHTypes of Left errors -> fail (renderGQLErrors errors) Right root -> [|root|] compileDec :: String -> Q [Dec] compileDec documentTXT =- case parseTypes (T.pack documentTXT) >>= renderTHTypes of+ case parseTypes (T.pack documentTXT) >>= validatePartialDocument >>= renderTHTypes of Left errors -> fail (renderGQLErrors errors) Right root -> declareTypes root
src/Data/Morpheus/Execution/Server/Resolve.hs view
@@ -42,8 +42,8 @@ import Data.Morpheus.Types.Internal.Value (Value (..)) import Data.Morpheus.Types.IO (GQLRequest (..), GQLResponse (..)) import Data.Morpheus.Types.Resolver (GQLRootResolver (..))-import Data.Morpheus.Validation.Utils.Utils (VALIDATION_MODE (..))-import Data.Morpheus.Validation.Validation (validateRequest)+import Data.Morpheus.Validation.Internal.Utils (VALIDATION_MODE (..))+import Data.Morpheus.Validation.Query.Validation (validateRequest) type EventCon event = Eq event
src/Data/Morpheus/Parsing/Document/DataType.hs view
@@ -11,9 +11,10 @@ import Data.Morpheus.Parsing.Internal.Terms (parseAssignment, parseMaybeTuple, parseNonNull, parseWrappedType, pipeLiteral, qualifier, setOf, spaceAndComments, token)-import Data.Morpheus.Types.Internal.Data (DataArgument, DataFullType (..), DataOutputField, Key)+import Data.Morpheus.Types.Internal.Data (DataArgument, DataFullType (..), DataOutputField, Key,+ RawDataType (..)) import Data.Text (Text)-import Text.Megaparsec (label, sepBy1, (<|>))+import Text.Megaparsec (label, sepBy1, some, (<|>)) import Text.Megaparsec.Char (char, space1, string) dataArgument :: Parser (Text, DataArgument)@@ -33,39 +34,57 @@ dataInputObject = label "inputObject" $ do typeName <- typeDef "input"- typeData <- inputEntries+ typeData <- inputObjectEntries pure (typeName, InputObject $ createType typeName typeData)++inputObjectEntries :: Parser [(Key, DataArgument)]+inputObjectEntries = label "inputEntries" $ setOf entry where- inputEntries :: Parser [(Key, DataArgument)]- inputEntries = label "inputEntries" $ setOf entry- where- entry =- label "entry" $ do- ((fieldName, _), (wrappers, fieldType)) <- parseAssignment qualifier parseWrappedType- nonNull <- parseNonNull- return (fieldName, createField () fieldName (nonNull ++ wrappers, fieldType))+ entry =+ label "entry" $ do+ ((fieldName, _), (wrappers, fieldType)) <- parseAssignment qualifier parseWrappedType+ nonNull <- parseNonNull+ return (fieldName, createField () fieldName (nonNull ++ wrappers, fieldType)) -dataObject :: Parser (Text, DataFullType)+outputObjectEntries :: Parser [(Key, DataOutputField)]+outputObjectEntries = label "entries" $ setOf entry+ where+ fieldWithArgs =+ label "fieldWithArgs" $ do+ (name, _) <- qualifier+ args <- parseMaybeTuple dataArgument+ return (name, args)+ entry =+ label "entry" $ do+ ((fieldName, fieldArgs), (wrappers, fieldType)) <- parseAssignment fieldWithArgs parseWrappedType+ nonNull <- parseNonNull+ return (fieldName, createField fieldArgs fieldName (nonNull ++ wrappers, fieldType))++dataObject :: Parser (Text, RawDataType) dataObject = label "object" $ do typeName <- typeDef "type"- typeData <- entries- pure (typeName, OutputObject $ createType typeName typeData)+ interfaces <- maybeImplements+ typeData <- outputObjectEntries+ pure (typeName, Implements interfaces $ createType typeName typeData)++maybeImplements :: Parser [Text]+maybeImplements = implements <|> pure [] where- entries :: Parser [(Key, DataOutputField)]- entries = label "entries" $ setOf entry- where- fieldWithArgs =- label "fieldWithArgs" $ do- (name, _) <- qualifier- args <- parseMaybeTuple dataArgument- return (name, args)- entry =- label "entry" $ do- ((fieldName, fieldArgs), (wrappers, fieldType)) <- parseAssignment fieldWithArgs parseWrappedType- nonNull <- parseNonNull- return (fieldName, createField fieldArgs fieldName (nonNull ++ wrappers, fieldType))+ implements =+ label "implements" $ do+ _ <- string "implements"+ space1+ spaceAndComments+ some token +dataInterface :: Parser (Text, RawDataType)+dataInterface =+ label "interface" $ do+ typeName <- typeDef "interface"+ typeData <- outputObjectEntries+ pure (typeName, Interface $ createType typeName typeData)+ dataScalar :: Parser (Text, DataFullType) dataScalar = label "scalar" $ do@@ -91,5 +110,12 @@ where unionsParser = token `sepBy1` pipeLiteral -parseDataType :: Parser (Text, DataFullType)-parseDataType = label "dataType" $ dataObject <|> dataInputObject <|> dataUnion <|> dataEnum <|> dataScalar+parseFinalDataType :: Parser (Text, DataFullType)+parseFinalDataType = label "dataType" $ dataInputObject <|> dataUnion <|> dataEnum <|> dataScalar++parseDataType :: Parser (Text, RawDataType)+parseDataType = label "dataType" $ dataInterface <|> dataObject <|> finalDataT+ where+ finalDataT = do+ (name, datatype) <- parseFinalDataType+ pure (name, FinalDataType datatype)
− src/Data/Morpheus/Parsing/Document/Parse.hs
@@ -1,24 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE NamedFieldPuns #-}--module Data.Morpheus.Parsing.Document.Parse- ( parseGraphQLDocument- , parseFullGQLDocument- ) where--import Control.Monad ((>=>))-import Data.ByteString.Lazy.Char8 (ByteString)-import qualified Data.Text.Lazy as LT (toStrict)-import Data.Text.Lazy.Encoding (decodeUtf8)---- MORPHEUS-import Data.Morpheus.Parsing.Document.Parser (parseDocument)-import Data.Morpheus.Schema.SchemaAPI (defaultTypes)-import Data.Morpheus.Types.Internal.Data (DataTypeLib)-import Data.Morpheus.Types.Internal.Validation (Validation)--parseGraphQLDocument :: ByteString -> Validation DataTypeLib-parseGraphQLDocument x = parseDocument (LT.toStrict $ decodeUtf8 x)--parseFullGQLDocument :: ByteString -> Validation DataTypeLib-parseFullGQLDocument = parseGraphQLDocument >=> defaultTypes
src/Data/Morpheus/Parsing/Document/Parser.hs view
@@ -2,8 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} module Data.Morpheus.Parsing.Document.Parser- ( parseDocument- , parseTypes+ ( parseTypes ) where import Data.Text (Text)@@ -11,13 +10,12 @@ -- MORPHEUS import Data.Morpheus.Parsing.Document.DataType (parseDataType)-import Data.Morpheus.Parsing.Internal.Create (createDataTypeLib) import Data.Morpheus.Parsing.Internal.Internal (processErrorBundle) import Data.Morpheus.Parsing.Internal.Terms (spaceAndComments)-import Data.Morpheus.Types.Internal.Data (DataFullType, DataTypeLib)+import Data.Morpheus.Types.Internal.Data (RawDataType) import Data.Morpheus.Types.Internal.Validation (Validation) -parseTypes :: Text -> Validation [(Text, DataFullType)]+parseTypes :: Text -> Validation [(Text, RawDataType)] parseTypes doc = case parseDoc of Right root -> Right root@@ -28,16 +26,3 @@ label "DocumentTypes" $ do spaceAndComments manyTill parseDataType eof--parseDocument :: Text -> Validation DataTypeLib-parseDocument doc =- case parseDoc of- Right root -> Right root- Left parseError -> Left $ processErrorBundle parseError- where- parseDoc = runParser request "<input>" doc- request =- label "Document" $ do- spaceAndComments- dataTypes <- manyTill parseDataType eof- createDataTypeLib dataTypes
src/Data/Morpheus/Parsing/Internal/Terms.hs view
@@ -6,6 +6,7 @@ , qualifier , variable , spaceAndComments+ , spaceAndComments1 , pipeLiteral ------------- , setOf@@ -59,10 +60,17 @@ varName' <- token return (varName', position') +spaceAndComments1 :: Parser ()+spaceAndComments1 = space1 *> spaceAndComments+ spaceAndComments :: Parser ()-spaceAndComments = space *> skipMany inlineComment *> space+spaceAndComments = space *> skipMany (inlineComment <|> multilineComment) *> space where inlineComment = char '#' *> skipManyTill printChar newline *> space+ ------------------------------------------------------------------------+ multilineComment = multilineIndicator *> skipManyTill (printChar *> space <|> space) multilineIndicator *> space+ --------------------+ multilineIndicator = string "\"\"\"" -- COMPLEX -----------------------------
src/Data/Morpheus/Parsing/Request/Operation.hs view
@@ -9,13 +9,13 @@ import Data.Functor (($>)) import Data.Text (Text) import Text.Megaparsec (label, (<?>), (<|>))-import Text.Megaparsec.Char (space1, string)+import Text.Megaparsec.Char (string) -- -- MORPHEUS import Data.Morpheus.Parsing.Internal.Internal (Parser, getLocation) import Data.Morpheus.Parsing.Internal.Terms (parseAssignment, parseMaybeTuple, parseNonNull,- parseWrappedType, token, variable)+ parseWrappedType, spaceAndComments1, token, variable) import Data.Morpheus.Parsing.Request.Body (entries) import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), OperationKind (..), RawOperation, Variable (..))@@ -64,5 +64,5 @@ parseOperationKind = label "operatorKind" $ do kind <- (string "query" $> QUERY) <|> (string "mutation" $> MUTATION) <|> (string "subscription" $> SUBSCRIPTION)- space1+ spaceAndComments1 return kind
src/Data/Morpheus/Parsing/Request/Value.hs view
@@ -9,23 +9,20 @@ import Data.Functor (($>)) import Data.Text (pack) import Text.Megaparsec (anySingleBut, between, choice, label, many, sepBy, (<|>))-import Text.Megaparsec.Char (char, space, string)+import Text.Megaparsec.Char (char, string) import Text.Megaparsec.Char.Lexer (scientific) -- -- MORPHEUS import Data.Morpheus.Parsing.Internal.Internal (Parser)-import Data.Morpheus.Parsing.Internal.Terms (parseAssignment, setOf, token)+import Data.Morpheus.Parsing.Internal.Terms (parseAssignment, setOf, spaceAndComments, token) import Data.Morpheus.Types.Internal.Value (ScalarValue (..), Value (..), decodeScientific) parseValue :: Parser Value parseValue = label "value" $ do- value <-- valueNull <|> booleanValue <|> valueNumber <|> enumValue <|> stringValue <|>- objectValue <|>- listValue- space+ value <- valueNull <|> booleanValue <|> valueNumber <|> enumValue <|> stringValue <|> objectValue <|> listValue+ spaceAndComments return value valueNull :: Parser Value@@ -43,7 +40,7 @@ enumValue :: Parser Value enumValue = do enum <- Enum <$> token- space+ spaceAndComments return enum escaped :: Parser Char@@ -59,18 +56,16 @@ escapeChar code replacement = char code >> return replacement stringValue :: Parser Value-stringValue =- label "stringValue" $- Scalar . String . pack <$> between (char '"') (char '"') (many escaped)+stringValue = label "stringValue" $ Scalar . String . pack <$> between (char '"') (char '"') (many escaped) listValue :: Parser Value listValue = label "listValue" $ List <$> between- (char '[' *> space)- (char ']' *> space)- (parseValue `sepBy` (char ',' *> space))+ (char '[' *> spaceAndComments)+ (char ']' *> spaceAndComments)+ (parseValue `sepBy` (char ',' *> spaceAndComments)) objectValue :: Parser Value objectValue = label "objectValue" $ Object <$> setOf entry
src/Data/Morpheus/Types/IO.hs view
@@ -10,6 +10,7 @@ import Data.Aeson (FromJSON (..), ToJSON (..), pairs, withObject, (.:?), (.=)) import qualified Data.Aeson as Aeson (Value (..))+import qualified Data.HashMap.Lazy as LH (toList) import GHC.Generics (Generic) -- MORPHEUS@@ -39,6 +40,14 @@ = Data Value | Errors [JSONError] deriving (Show, Generic)++instance FromJSON GQLResponse where+ parseJSON (Aeson.Object hm) =+ case LH.toList hm of+ [("data", value)] -> Data <$> parseJSON value+ [("errors", value)] -> Errors <$> parseJSON value+ _ -> fail "Invalid GraphQL Response"+ parseJSON _ = fail "Invalid GraphQL Response" instance ToJSON GQLResponse where toEncoding (Data _data) = pairs $ "data" .= _data
src/Data/Morpheus/Types/Internal/Data.hs view
@@ -25,6 +25,7 @@ , DataValidator(..) , DataTypeKind(..) , DataFingerprint(..)+ , RawDataType(..) , isTypeDefined , initTypeLib , defineType@@ -127,6 +128,13 @@ | EnumKind DataEnum | ObjectKind (DataObject a) | UnionKind DataUnion+ deriving (Show)++data RawDataType+ = FinalDataType DataFullType+ | Interface DataOutputObject+ | Implements { implementsInterfaces :: [Key]+ , unImplements :: DataOutputObject } deriving (Show) data DataFullType
− src/Data/Morpheus/Validation/Arguments.hs
@@ -1,146 +0,0 @@-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE TupleSections #-}--module Data.Morpheus.Validation.Arguments- ( validateArguments- ) where--import Data.Morpheus.Error.Arguments (argumentGotInvalidValue, argumentNameCollision,- undefinedArgument, unknownArguments)-import Data.Morpheus.Error.Input (InputValidation, inputErrorMessage)-import Data.Morpheus.Error.Internal (internalUnknownTypeMessage)-import Data.Morpheus.Error.Variable (incompatibleVariableType, undefinedVariable)-import Data.Morpheus.Types.Internal.AST.Operation (ValidVariables, Variable (..))-import Data.Morpheus.Types.Internal.AST.RawSelection (RawArgument (..), RawArguments, Reference (..))-import Data.Morpheus.Types.Internal.AST.Selection (Argument (..), ArgumentOrigin (..), Arguments)-import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Position)-import Data.Morpheus.Types.Internal.Data (DataArgument, DataField (..), DataInputField,- DataOutputField, DataTypeLib, DataTypeWrapper (..),- isFieldNullable, showWrappedType)-import Data.Morpheus.Types.Internal.Validation (Validation)-import Data.Morpheus.Types.Internal.Value (Value (Null))-import Data.Morpheus.Validation.Input.Object (validateInputValue)-import Data.Morpheus.Validation.Utils.Utils (checkForUnknownKeys, checkNameCollision, getInputType)-import Data.Text (Text)--resolveArgumentVariables ::- Text- -> ValidVariables- -> DataOutputField- -> RawArguments- -> Validation Arguments-resolveArgumentVariables operatorName variables DataField {fieldName, fieldArgs} =- mapM resolveVariable- where- resolveVariable :: (Text, RawArgument) -> Validation (Text, Argument)- resolveVariable (key, RawArgument argument) = pure (key, argument)- resolveVariable (key, VariableReference Reference { referenceName- , referencePosition- }) =- (key, ) . toArgument <$> lookupVar- where- toArgument argumentValue =- Argument- { argumentValue- , argumentOrigin = VARIABLE- , argumentPosition = referencePosition- }- stricter [] [] = True- stricter (NonNullType:xs1) (NonNullType:xs2) = stricter xs1 xs2- stricter (NonNullType:xs1) xs2 = stricter xs1 xs2- stricter (ListType:xs1) (ListType:xs2) = stricter xs1 xs2- stricter _ _ = False- lookupVar =- case lookup referenceName variables of- Nothing ->- Left $- undefinedVariable operatorName referencePosition referenceName- Just Variable {variableValue, variableType, variableTypeWrappers} ->- case lookup key fieldArgs of- Nothing ->- Left $- unknownArguments fieldName [EnhancedKey key referencePosition]- Just DataField {fieldType, fieldTypeWrappers} ->- if variableType == fieldType &&- stricter variableTypeWrappers fieldTypeWrappers- then return variableValue- else Left $- incompatibleVariableType- referenceName- varSignature- fieldSignature- referencePosition- where varSignature =- showWrappedType variableTypeWrappers variableType- fieldSignature =- showWrappedType fieldTypeWrappers fieldType--handleInputError :: Text -> Position -> InputValidation a -> Validation ()-handleInputError key position' (Left error') =- Left $ argumentGotInvalidValue key (inputErrorMessage error') position'-handleInputError _ _ _ = pure ()--validateArgumentValue ::- DataTypeLib- -> DataField a- -> (Text, Argument)- -> Validation (Text, Argument)-validateArgumentValue lib DataField {fieldType, fieldTypeWrappers} arg@(key, Argument { argumentValue- , argumentPosition- }) =- getInputType fieldType lib (internalUnknownTypeMessage fieldType) >>=- checkType >>- pure arg- where- checkType type' =- handleInputError- key- argumentPosition- (validateInputValue lib [] fieldTypeWrappers type' (key, argumentValue))--validateArgument ::- DataTypeLib- -> Position- -> Arguments- -> (Text, DataArgument)- -> Validation (Text, Argument)-validateArgument types argumentPosition requestArgs (key, arg) =- case lookup key requestArgs of- Nothing -> handleNullable- Just argument@Argument {argumentOrigin = VARIABLE} -> pure (key, argument) -- Variables are already checked in Variable Validation- Just Argument {argumentValue = Null} -> handleNullable- Just argument -> validateArgumentValue types arg (key, argument)- where- handleNullable- | isFieldNullable arg =- pure- ( key- , Argument- {argumentValue = Null, argumentOrigin = INLINE, argumentPosition})- | otherwise = Left $ undefinedArgument (EnhancedKey key argumentPosition)--checkForUnknownArguments ::- (Text, DataOutputField) -> Arguments -> Validation [(Text, DataInputField)]-checkForUnknownArguments (key, DataField {fieldArgs}) args =- checkForUnknownKeys enhancedKeys fieldKeys argError >>- checkNameCollision enhancedKeys argumentNameCollision >>- pure fieldArgs- where- argError = unknownArguments key- enhancedKeys = map argToKey args- argToKey (key', Argument {argumentPosition}) =- EnhancedKey key' argumentPosition- fieldKeys = map fst fieldArgs--validateArguments ::- DataTypeLib- -> Text- -> ValidVariables- -> (Text, DataOutputField)- -> Position- -> RawArguments- -> Validation Arguments-validateArguments typeLib operatorName variables inputs pos rawArgs = do- args <- resolveArgumentVariables operatorName variables (snd inputs) rawArgs- dataArgs <- checkForUnknownArguments inputs args- mapM (validateArgument typeLib pos args) dataArgs
+ src/Data/Morpheus/Validation/Document/Validation.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE NamedFieldPuns #-}++module Data.Morpheus.Validation.Document.Validation+ ( validatePartialDocument+ ) where++import Data.Maybe++--+-- Morpheus+import Data.Morpheus.Error.Document.Interface (ImplementsError (..), partialImplements, unknownInterface)+import Data.Morpheus.Types.Internal.Base (Location (..))+import Data.Morpheus.Types.Internal.Data (DataField (..), DataFullType (..), DataOutputField,+ DataOutputObject, DataType (..), Key, RawDataType (..),+ showWrappedType)+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Validation.Internal.Utils (isEqOrStricter)++validatePartialDocument :: [(Key, RawDataType)] -> Validation [(Key, DataFullType)]+validatePartialDocument lib = catMaybes <$> traverse validateType lib+ where+ validateType :: (Key, RawDataType) -> Validation (Maybe (Key, DataFullType))+ validateType (name, FinalDataType x) = pure $ Just (name, x)+ validateType (name, Implements interfaces object) = asTuple name <$> object `mustImplement` interfaces+ validateType _ = pure Nothing+ -----------------------------------+ asTuple name x = Just (name, x)+ -----------------------------------+ mustImplement :: DataOutputObject -> [Key] -> Validation DataFullType+ mustImplement object interfaceKey = do+ interface <- traverse getInterfaceByKey interfaceKey+ case concatMap (mustBeSubset object) interface of+ [] -> pure $ OutputObject object+ errors -> Left $ partialImplements (typeName object) errors+ -------------------------------+ mustBeSubset :: DataOutputObject -> DataOutputObject -> [(Key, Key, ImplementsError)]+ mustBeSubset DataType {typeData = objFields} DataType {typeName, typeData = interfaceFields} =+ concatMap checkField interfaceFields+ where+ checkField :: (Key, DataOutputField) -> [(Key, Key, ImplementsError)]+ checkField (key, DataField {fieldType = interfaceTypeName, fieldTypeWrappers = interfaceWrappers}) =+ case lookup key objFields of+ Just DataField {fieldType, fieldTypeWrappers}+ | fieldType == interfaceTypeName && isEqOrStricter fieldTypeWrappers interfaceWrappers -> []+ | otherwise -> [(typeName, key, UnexpectedType {expectedType, foundType})]+ where expectedType = showWrappedType interfaceWrappers interfaceTypeName+ foundType = showWrappedType fieldTypeWrappers fieldType+ Nothing -> [(typeName, key, UndefinedField)]+ -------------------------------+ getInterfaceByKey :: Key -> Validation DataOutputObject+ getInterfaceByKey key =+ case lookup key lib of+ Just (Interface x) -> pure x+ _ -> Left $ unknownInterface key
− src/Data/Morpheus/Validation/Fragment.hs
@@ -1,127 +0,0 @@-{-# LANGUAGE NamedFieldPuns #-}--module Data.Morpheus.Validation.Fragment- ( validateFragments- , castFragmentType- , resolveSpread- , getFragment- ) where--import Data.List ((\\))-import Data.Semigroup ((<>))-import Data.Text (Text)-import qualified Data.Text as T (concat)---- MORPHEUS-import Data.Morpheus.Error.Fragment (cannotBeSpreadOnType, cannotSpreadWithinItself,- fragmentNameCollision, unknownFragment, unusedFragment)-import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawSelection (..),- RawSelection' (..), Reference (..))-import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Position)-import Data.Morpheus.Types.Internal.Data (DataTypeLib)-import Data.Morpheus.Types.Internal.Validation (Validation)-import Data.Morpheus.Validation.Utils.Utils (checkNameCollision, existsObjectType)--validateFragments ::- DataTypeLib -> FragmentLib -> [(Text, RawSelection)] -> Validation ()-validateFragments lib fragments operatorSel =- validateNameCollision >> checkLoop >> checkUnusedFragments- where- validateNameCollision =- checkNameCollision fragmentsKeys fragmentNameCollision- checkUnusedFragments =- case fragmentsKeys \\ usedFragments fragments operatorSel of- [] -> return ()- unused -> Left $ unusedFragment unused- checkLoop = mapM (validateFragment lib) fragments >>= detectLoopOnFragments- fragmentsKeys = map toEnhancedKey fragments- where- toEnhancedKey (key, Fragment {fragmentPosition}) =- EnhancedKey key fragmentPosition--type Node = EnhancedKey--type NodeEdges = (Node, [Node])--type Graph = [NodeEdges]--getFragment :: Reference -> FragmentLib -> Validation Fragment-getFragment Reference {referenceName, referencePosition} lib =- case lookup referenceName lib of- Nothing -> Left $ unknownFragment referenceName referencePosition- Just fragment -> pure fragment--castFragmentType ::- Maybe Text -> Position -> [Text] -> Fragment -> Validation Fragment-castFragmentType key' position' targets' fragment@Fragment {fragmentType} =- if fragmentType `elem` targets'- then pure fragment- else Left $- cannotBeSpreadOnType key' fragmentType position' (T.concat targets')--resolveSpread :: FragmentLib -> [Text] -> Reference -> Validation Fragment-resolveSpread fragments allowedTargets reference@Reference { referenceName- , referencePosition- } =- getFragment reference fragments >>=- castFragmentType (Just referenceName) referencePosition allowedTargets--usedFragments :: FragmentLib -> [(Text, RawSelection)] -> [Node]-usedFragments fragments = concatMap findAllUses- where- findAllUses :: (Text, RawSelection) -> [Node]- findAllUses (_, RawSelectionSet RawSelection' {rawSelectionRec}) =- concatMap findAllUses rawSelectionRec- findAllUses (_, RawAlias {rawAliasSelection}) =- concatMap findAllUses [rawAliasSelection]- findAllUses (_, InlineFragment Fragment {fragmentSelection}) =- concatMap findAllUses fragmentSelection- findAllUses (_, RawSelectionField {}) = []- findAllUses (_, Spread Reference {referenceName, referencePosition}) =- [EnhancedKey referenceName referencePosition] <> searchInFragment- where- searchInFragment =- maybe- []- (concatMap findAllUses . fragmentSelection)- (lookup referenceName fragments)--scanForSpread :: (Text, RawSelection) -> [Node]-scanForSpread (_, RawSelectionSet RawSelection' {rawSelectionRec = selection'}) =- concatMap scanForSpread selection'-scanForSpread (_, RawAlias {rawAliasSelection = selection'}) =- concatMap scanForSpread [selection']-scanForSpread (_, InlineFragment Fragment {fragmentSelection = selection'}) =- concatMap scanForSpread selection'-scanForSpread (_, RawSelectionField {}) = []-scanForSpread (_, Spread Reference { referenceName = name'- , referencePosition = position'- }) = [EnhancedKey name' position']--validateFragment :: DataTypeLib -> (Text, Fragment) -> Validation NodeEdges-validateFragment lib (fName, Fragment { fragmentSelection- , fragmentType- , fragmentPosition- }) =- existsObjectType fragmentPosition fragmentType lib >>- pure- ( EnhancedKey fName fragmentPosition- , concatMap scanForSpread fragmentSelection)--detectLoopOnFragments :: Graph -> Validation ()-detectLoopOnFragments lib = mapM_ checkFragment lib- where- checkFragment (fragmentID, _) = checkForCycle lib fragmentID [fragmentID]--checkForCycle :: Graph -> Node -> [Node] -> Validation Graph-checkForCycle lib parentNode history =- case lookup parentNode lib of- Just node -> concat <$> mapM checkNode node- Nothing -> pure []- where- checkNode x =- if x `elem` history- then cycleError x- else recurse x- recurse node = checkForCycle lib node $ history ++ [node]- cycleError n = Left $ cannotSpreadWithinItself $ history ++ [n]
− src/Data/Morpheus/Validation/Input/Enum.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE TypeOperators #-}--module Data.Morpheus.Validation.Input.Enum- ( validateEnum- ) where--import Data.List (elem)-import Data.Morpheus.Types.Internal.Value (Value (..))-import Data.Text (Text)--validateEnum :: error -> [Text] -> Value -> Either error Value-validateEnum error' tags' (Enum enumValue) =- if enumValue `elem` tags'- then pure (Enum enumValue)- else Left error'-validateEnum error' _ _ = Left error'
− src/Data/Morpheus/Validation/Input/Object.hs
@@ -1,97 +0,0 @@-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}--module Data.Morpheus.Validation.Input.Object- ( validateInputValue- ) where--import Data.Morpheus.Error.Input (InputError (..), InputValidation, Prop (..))-import Data.Morpheus.Types.Internal.Data (DataField (..), DataInputType, DataKind (..), DataType (..),- DataTypeLib (..), DataTypeWrapper (..), DataValidator (..),- showFullAstType)-import Data.Morpheus.Types.Internal.Value (Value (..))-import Data.Morpheus.Validation.Input.Enum (validateEnum)-import Data.Morpheus.Validation.Utils.Utils (getInputType, lookupField)-import Data.Text (Text)--typeMismatch :: Value -> Text -> [Prop] -> InputError-typeMismatch jsType expected' path' =- UnexpectedType path' expected' jsType Nothing---- Validate Variable Argument or all Possible input Values-validateInputValue ::- DataTypeLib- -> [Prop]- -> [DataTypeWrapper]- -> DataInputType- -> (Text, Value)- -> InputValidation Value-validateInputValue lib' prop' = validate- where- throwError ::- [DataTypeWrapper] -> DataInputType -> Value -> InputValidation Value- throwError wrappers' type' value' =- Left $- UnexpectedType prop' (showFullAstType wrappers' type') value' Nothing- {-- VALIDATION --}- {-- 1. VALIDATE WRAPPERS -}- validate ::- [DataTypeWrapper]- -> DataInputType- -> (Text, Value)- -> InputValidation Value- -- throw error on not nullable type if value = null- validate (NonNullType:wrappers') type' (_, Null) =- throwError wrappers' type' Null- -- resolves nullable value as null- validate _ _ (_, Null) = return Null- -- ignores NonNUllTypes if value /= null- validate (NonNullType:wrappers') type' value' =- validateInputValue lib' prop' wrappers' type' value'- {-- VALIDATE LIST -}- validate (ListType:wrappers') type' (key', List list') =- List <$> mapM validateElement list'- where- validateElement element' =- validateInputValue lib' prop' wrappers' type' (key', element')- {-- 2. VALIDATE TYPES, all wrappers are already Processed --}- {-- VALIDATE OBJECT--}- validate [] (ObjectKind DataType {typeData = parentFields'}) (_, Object fields) =- Object <$> mapM validateField fields- where- validateField (_name, value') = do- (type', currentProp') <- validationData value'- wrappers' <- fieldTypeWrappers <$> getField- value'' <-- validateInputValue lib' currentProp' wrappers' type' (_name, value')- return (_name, value'')- where- validationData x = do- fieldTypeName' <- fieldType <$> getField- let currentProp = prop' ++ [Prop _name fieldTypeName']- type' <-- getInputType- fieldTypeName'- lib'- (typeMismatch x fieldTypeName' currentProp)- return (type', currentProp)- getField =- lookupField _name parentFields' (UnknownField prop' _name)- -- VALIDATE INPUT UNION- -- TODO: Validate Union- validate [] (UnionKind DataType {typeData}) (_, Object fields) =- return (Object fields)- {-- VALIDATE SCALAR --}- validate [] (EnumKind DataType {typeData = tags', typeName = name'}) (_, value') =- validateEnum (UnexpectedType prop' name' value' Nothing) tags' value'- {-- VALIDATE ENUM --}- validate [] (ScalarKind DataType { typeName = name'- , typeData = DataValidator {validateValue = validator'}- }) (_, value') =- case validator' value' of- Right _ -> return value'- Left "" -> Left $ UnexpectedType prop' name' value' Nothing- Left errorMessage ->- Left $ UnexpectedType prop' name' value' (Just errorMessage)- {-- 3. THROW ERROR: on invalid values --}- validate wrappers' type' (_, value') = throwError wrappers' type' value'
+ src/Data/Morpheus/Validation/Internal/Utils.hs view
@@ -0,0 +1,86 @@+module Data.Morpheus.Validation.Internal.Utils+ ( differKeys+ , existsObjectType+ , lookupType+ , getInputType+ , lookupField+ , checkNameCollision+ , checkForUnknownKeys+ , isEqOrStricter+ , VALIDATION_MODE(..)+ ) where++import Data.List ((\\))+import Data.Morpheus.Error.Variable (unknownType)+import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Key, Position, enhanceKeyWithNull)+import Data.Morpheus.Types.Internal.Data (DataInputType, DataKind (..), DataLeaf (..), DataOutputObject,+ DataTypeLib (..), DataTypeWrapper (..))+import Data.Morpheus.Types.Internal.Validation (Validation)+import qualified Data.Set as S+import Data.Text (Text)++type GenError error a = error -> Either error a++data VALIDATION_MODE+ = WITHOUT_VARIABLES+ | FULL_VALIDATION+ deriving (Eq, Show)++lookupType :: error -> [(Text, a)] -> Text -> Either error a+lookupType error' lib' typeName' =+ case lookup typeName' lib' of+ Nothing -> Left error'+ Just x -> pure x++lookupField :: Text -> [(Text, fType)] -> GenError error fType+lookupField id' lib' error' =+ case lookup id' lib' of+ Nothing -> Left error'+ Just field -> pure field++getInputType :: Text -> DataTypeLib -> GenError error DataInputType+getInputType typeName' lib error' =+ case lookup typeName' (inputObject lib) of+ Just x -> pure (ObjectKind x)+ Nothing ->+ case lookup typeName' (inputUnion lib) of+ Just x -> pure (UnionKind x)+ Nothing ->+ case lookup typeName' (leaf lib) of+ Nothing -> Left error'+ Just (BaseScalar x) -> pure (ScalarKind x)+ Just (CustomScalar x) -> pure (ScalarKind x)+ Just (LeafEnum x) -> pure (EnumKind x)++existsObjectType :: Position -> Text -> DataTypeLib -> Validation DataOutputObject+existsObjectType position' typeName' lib = lookupType error' (object lib) typeName'+ where+ error' = unknownType typeName' position'++differKeys :: [EnhancedKey] -> [Key] -> [EnhancedKey]+differKeys enhanced keys = enhanced \\ map enhanceKeyWithNull keys++removeDuplicates :: Ord a => [a] -> [a]+removeDuplicates = S.toList . S.fromList++elementOfKeys :: [Text] -> EnhancedKey -> Bool+elementOfKeys keys' EnhancedKey {uid = id'} = id' `elem` keys'++checkNameCollision :: [EnhancedKey] -> ([EnhancedKey] -> error) -> Either error [EnhancedKey]+checkNameCollision enhancedKeys errorGenerator =+ case enhancedKeys \\ removeDuplicates enhancedKeys of+ [] -> pure enhancedKeys+ duplicates -> Left $ errorGenerator duplicates++checkForUnknownKeys :: [EnhancedKey] -> [Text] -> ([EnhancedKey] -> error) -> Either error [EnhancedKey]+checkForUnknownKeys enhancedKeys' keys' errorGenerator' =+ case filter (not . elementOfKeys keys') enhancedKeys' of+ [] -> pure enhancedKeys'+ unknownKeys' -> Left $ errorGenerator' unknownKeys'++isEqOrStricter :: [DataTypeWrapper] -> [DataTypeWrapper] -> Bool+isEqOrStricter [] [] = True+isEqOrStricter (NonNullType:xs1) (NonNullType:xs2) = isEqOrStricter xs1 xs2+isEqOrStricter (NonNullType:xs1) xs2 = isEqOrStricter xs1 xs2+isEqOrStricter (ListType:xs1) (ListType:xs2) = isEqOrStricter xs1 xs2+isEqOrStricter _ _ = False
+ src/Data/Morpheus/Validation/Query/Arguments.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TupleSections #-}++module Data.Morpheus.Validation.Query.Arguments+ ( validateArguments+ ) where++import Data.Morpheus.Error.Arguments (argumentGotInvalidValue, argumentNameCollision,+ undefinedArgument, unknownArguments)+import Data.Morpheus.Error.Input (InputValidation, inputErrorMessage)+import Data.Morpheus.Error.Internal (internalUnknownTypeMessage)+import Data.Morpheus.Error.Variable (incompatibleVariableType, undefinedVariable)+import Data.Morpheus.Types.Internal.AST.Operation (ValidVariables, Variable (..))+import Data.Morpheus.Types.Internal.AST.RawSelection (RawArgument (..), RawArguments, Reference (..))+import Data.Morpheus.Types.Internal.AST.Selection (Argument (..), ArgumentOrigin (..), Arguments)+import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Position)+import Data.Morpheus.Types.Internal.Data (DataArgument, DataField (..), DataInputField,+ DataOutputField, DataTypeLib, isFieldNullable,+ showWrappedType)+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Types.Internal.Value (Value (Null))+import Data.Morpheus.Validation.Internal.Utils (checkForUnknownKeys, checkNameCollision, getInputType,+ isEqOrStricter)+import Data.Morpheus.Validation.Query.Input.Object (validateInputValue)+import Data.Text (Text)++resolveArgumentVariables :: Text -> ValidVariables -> DataOutputField -> RawArguments -> Validation Arguments+resolveArgumentVariables operatorName variables DataField {fieldName, fieldArgs} = mapM resolveVariable+ where+ resolveVariable :: (Text, RawArgument) -> Validation (Text, Argument)+ resolveVariable (key, RawArgument argument) = pure (key, argument)+ resolveVariable (key, VariableReference Reference {referenceName, referencePosition}) =+ (key, ) . toArgument <$> lookupVar+ where+ toArgument argumentValue =+ Argument {argumentValue, argumentOrigin = VARIABLE, argumentPosition = referencePosition}+ lookupVar =+ case lookup referenceName variables of+ Nothing -> Left $ undefinedVariable operatorName referencePosition referenceName+ Just Variable {variableValue, variableType, variableTypeWrappers} ->+ case lookup key fieldArgs of+ Nothing -> Left $ unknownArguments fieldName [EnhancedKey key referencePosition]+ Just DataField {fieldType, fieldTypeWrappers} ->+ if variableType == fieldType && isEqOrStricter variableTypeWrappers fieldTypeWrappers+ then return variableValue+ else Left $ incompatibleVariableType referenceName varSignature fieldSignature referencePosition+ where varSignature = showWrappedType variableTypeWrappers variableType+ fieldSignature = showWrappedType fieldTypeWrappers fieldType++handleInputError :: Text -> Position -> InputValidation a -> Validation ()+handleInputError key position' (Left error') = Left $ argumentGotInvalidValue key (inputErrorMessage error') position'+handleInputError _ _ _ = pure ()++validateArgumentValue :: DataTypeLib -> DataField a -> (Text, Argument) -> Validation (Text, Argument)+validateArgumentValue lib DataField {fieldType, fieldTypeWrappers} arg@(key, Argument {argumentValue, argumentPosition}) =+ getInputType fieldType lib (internalUnknownTypeMessage fieldType) >>= checkType >> pure arg+ where+ checkType type' =+ handleInputError key argumentPosition (validateInputValue lib [] fieldTypeWrappers type' (key, argumentValue))++validateArgument :: DataTypeLib -> Position -> Arguments -> (Text, DataArgument) -> Validation (Text, Argument)+validateArgument types argumentPosition requestArgs (key, arg) =+ case lookup key requestArgs of+ Nothing -> handleNullable+ Just argument@Argument {argumentOrigin = VARIABLE} -> pure (key, argument) -- Variables are already checked in Variable Validation+ Just Argument {argumentValue = Null} -> handleNullable+ Just argument -> validateArgumentValue types arg (key, argument)+ where+ handleNullable+ | isFieldNullable arg = pure (key, Argument {argumentValue = Null, argumentOrigin = INLINE, argumentPosition})+ | otherwise = Left $ undefinedArgument (EnhancedKey key argumentPosition)++checkForUnknownArguments :: (Text, DataOutputField) -> Arguments -> Validation [(Text, DataInputField)]+checkForUnknownArguments (key, DataField {fieldArgs}) args =+ checkForUnknownKeys enhancedKeys fieldKeys argError >> checkNameCollision enhancedKeys argumentNameCollision >>+ pure fieldArgs+ where+ argError = unknownArguments key+ enhancedKeys = map argToKey args+ argToKey (key', Argument {argumentPosition}) = EnhancedKey key' argumentPosition+ fieldKeys = map fst fieldArgs++validateArguments ::+ DataTypeLib+ -> Text+ -> ValidVariables+ -> (Text, DataOutputField)+ -> Position+ -> RawArguments+ -> Validation Arguments+validateArguments typeLib operatorName variables inputs pos rawArgs = do+ args <- resolveArgumentVariables operatorName variables (snd inputs) rawArgs+ dataArgs <- checkForUnknownArguments inputs args+ mapM (validateArgument typeLib pos args) dataArgs
+ src/Data/Morpheus/Validation/Query/Fragment.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE NamedFieldPuns #-}++module Data.Morpheus.Validation.Query.Fragment+ ( validateFragments+ , castFragmentType+ , resolveSpread+ , getFragment+ ) where++import Data.List ((\\))+import Data.Semigroup ((<>))+import Data.Text (Text)+import qualified Data.Text as T (concat)++-- MORPHEUS+import Data.Morpheus.Error.Fragment (cannotBeSpreadOnType, cannotSpreadWithinItself,+ fragmentNameCollision, unknownFragment, unusedFragment)+import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawSelection (..),+ RawSelection' (..), Reference (..))+import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Position)+import Data.Morpheus.Types.Internal.Data (DataTypeLib)+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Validation.Internal.Utils (checkNameCollision, existsObjectType)++validateFragments :: DataTypeLib -> FragmentLib -> [(Text, RawSelection)] -> Validation ()+validateFragments lib fragments operatorSel = validateNameCollision >> checkLoop >> checkUnusedFragments+ where+ validateNameCollision = checkNameCollision fragmentsKeys fragmentNameCollision+ checkUnusedFragments =+ case fragmentsKeys \\ usedFragments fragments operatorSel of+ [] -> return ()+ unused -> Left $ unusedFragment unused+ checkLoop = mapM (validateFragment lib) fragments >>= detectLoopOnFragments+ fragmentsKeys = map toEnhancedKey fragments+ where+ toEnhancedKey (key, Fragment {fragmentPosition}) = EnhancedKey key fragmentPosition++type Node = EnhancedKey++type NodeEdges = (Node, [Node])++type Graph = [NodeEdges]++getFragment :: Reference -> FragmentLib -> Validation Fragment+getFragment Reference {referenceName, referencePosition} lib =+ case lookup referenceName lib of+ Nothing -> Left $ unknownFragment referenceName referencePosition+ Just fragment -> pure fragment++castFragmentType :: Maybe Text -> Position -> [Text] -> Fragment -> Validation Fragment+castFragmentType key' position' targets' fragment@Fragment {fragmentType} =+ if fragmentType `elem` targets'+ then pure fragment+ else Left $ cannotBeSpreadOnType key' fragmentType position' (T.concat targets')++resolveSpread :: FragmentLib -> [Text] -> Reference -> Validation Fragment+resolveSpread fragments allowedTargets reference@Reference {referenceName, referencePosition} =+ getFragment reference fragments >>= castFragmentType (Just referenceName) referencePosition allowedTargets++usedFragments :: FragmentLib -> [(Text, RawSelection)] -> [Node]+usedFragments fragments = concatMap findAllUses+ where+ findAllUses :: (Text, RawSelection) -> [Node]+ findAllUses (_, RawSelectionSet RawSelection' {rawSelectionRec}) = concatMap findAllUses rawSelectionRec+ findAllUses (_, RawAlias {rawAliasSelection}) = concatMap findAllUses [rawAliasSelection]+ findAllUses (_, InlineFragment Fragment {fragmentSelection}) = concatMap findAllUses fragmentSelection+ findAllUses (_, RawSelectionField {}) = []+ findAllUses (_, Spread Reference {referenceName, referencePosition}) =+ [EnhancedKey referenceName referencePosition] <> searchInFragment+ where+ searchInFragment = maybe [] (concatMap findAllUses . fragmentSelection) (lookup referenceName fragments)++scanForSpread :: (Text, RawSelection) -> [Node]+scanForSpread (_, RawSelectionSet RawSelection' {rawSelectionRec = selection'}) = concatMap scanForSpread selection'+scanForSpread (_, RawAlias {rawAliasSelection = selection'}) = concatMap scanForSpread [selection']+scanForSpread (_, InlineFragment Fragment {fragmentSelection = selection'}) = concatMap scanForSpread selection'+scanForSpread (_, RawSelectionField {}) = []+scanForSpread (_, Spread Reference {referenceName = name', referencePosition = position'}) =+ [EnhancedKey name' position']++validateFragment :: DataTypeLib -> (Text, Fragment) -> Validation NodeEdges+validateFragment lib (fName, Fragment {fragmentSelection, fragmentType, fragmentPosition}) =+ existsObjectType fragmentPosition fragmentType lib >>+ pure (EnhancedKey fName fragmentPosition, concatMap scanForSpread fragmentSelection)++detectLoopOnFragments :: Graph -> Validation ()+detectLoopOnFragments lib = mapM_ checkFragment lib+ where+ checkFragment (fragmentID, _) = checkForCycle lib fragmentID [fragmentID]++checkForCycle :: Graph -> Node -> [Node] -> Validation Graph+checkForCycle lib parentNode history =+ case lookup parentNode lib of+ Just node -> concat <$> mapM checkNode node+ Nothing -> pure []+ where+ checkNode x =+ if x `elem` history+ then cycleError x+ else recurse x+ recurse node = checkForCycle lib node $ history ++ [node]+ cycleError n = Left $ cannotSpreadWithinItself $ history ++ [n]
+ src/Data/Morpheus/Validation/Query/Input/Enum.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE TypeOperators #-}++module Data.Morpheus.Validation.Query.Input.Enum+ ( validateEnum+ ) where++import Data.List (elem)+import Data.Morpheus.Types.Internal.Value (Value (..))+import Data.Text (Text)++validateEnum :: error -> [Text] -> Value -> Either error Value+validateEnum error' tags' (Enum enumValue) =+ if enumValue `elem` tags'+ then pure (Enum enumValue)+ else Left error'+validateEnum error' _ _ = Left error'
+ src/Data/Morpheus/Validation/Query/Input/Object.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}++module Data.Morpheus.Validation.Query.Input.Object+ ( validateInputValue+ ) where++import Data.Morpheus.Error.Input (InputError (..), InputValidation, Prop (..))+import Data.Morpheus.Types.Internal.Data (DataField (..), DataInputType, DataKind (..),+ DataType (..), DataTypeLib (..), DataTypeWrapper (..),+ DataValidator (..), showFullAstType)+import Data.Morpheus.Types.Internal.Value (Value (..))+import Data.Morpheus.Validation.Query.Input.Enum (validateEnum)+import Data.Morpheus.Validation.Internal.Utils (getInputType, lookupField)+import Data.Text (Text)++typeMismatch :: Value -> Text -> [Prop] -> InputError+typeMismatch jsType expected' path' = UnexpectedType path' expected' jsType Nothing++-- Validate Variable Argument or all Possible input Values+validateInputValue ::+ DataTypeLib -> [Prop] -> [DataTypeWrapper] -> DataInputType -> (Text, Value) -> InputValidation Value+validateInputValue lib' prop' = validate+ where+ throwError :: [DataTypeWrapper] -> DataInputType -> Value -> InputValidation Value+ throwError wrappers' type' value' = Left $ UnexpectedType prop' (showFullAstType wrappers' type') value' Nothing+ {-- VALIDATION --}+ {-- 1. VALIDATE WRAPPERS -}+ validate :: [DataTypeWrapper] -> DataInputType -> (Text, Value) -> InputValidation Value+ -- throw error on not nullable type if value = null+ validate (NonNullType:wrappers') type' (_, Null) = throwError wrappers' type' Null+ -- resolves nullable value as null+ validate _ _ (_, Null) = return Null+ -- ignores NonNUllTypes if value /= null+ validate (NonNullType:wrappers') type' value' = validateInputValue lib' prop' wrappers' type' value'+ {-- VALIDATE LIST -}+ validate (ListType:wrappers') type' (key', List list') = List <$> mapM validateElement list'+ where+ validateElement element' = validateInputValue lib' prop' wrappers' type' (key', element')+ {-- 2. VALIDATE TYPES, all wrappers are already Processed --}+ {-- VALIDATE OBJECT--}+ validate [] (ObjectKind DataType {typeData = parentFields'}) (_, Object fields) =+ Object <$> mapM validateField fields+ where+ validateField (_name, value') = do+ (type', currentProp') <- validationData value'+ wrappers' <- fieldTypeWrappers <$> getField+ value'' <- validateInputValue lib' currentProp' wrappers' type' (_name, value')+ return (_name, value'')+ where+ validationData x = do+ fieldTypeName' <- fieldType <$> getField+ let currentProp = prop' ++ [Prop _name fieldTypeName']+ type' <- getInputType fieldTypeName' lib' (typeMismatch x fieldTypeName' currentProp)+ return (type', currentProp)+ getField = lookupField _name parentFields' (UnknownField prop' _name)+ -- VALIDATE INPUT UNION+ -- TODO: Validate Union+ validate [] (UnionKind DataType {typeData}) (_, Object fields) = return (Object fields)+ {-- VALIDATE SCALAR --}+ validate [] (EnumKind DataType {typeData = tags', typeName = name'}) (_, value') =+ validateEnum (UnexpectedType prop' name' value' Nothing) tags' value'+ {-- VALIDATE ENUM --}+ validate [] (ScalarKind DataType {typeName = name', typeData = DataValidator {validateValue = validator'}}) (_, value') =+ case validator' value' of+ Right _ -> return value'+ Left "" -> Left $ UnexpectedType prop' name' value' Nothing+ Left errorMessage -> Left $ UnexpectedType prop' name' value' (Just errorMessage)+ {-- 3. THROW ERROR: on invalid values --}+ validate wrappers' type' (_, value') = throwError wrappers' type' value'
+ src/Data/Morpheus/Validation/Query/Selection.hs view
@@ -0,0 +1,178 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}++module Data.Morpheus.Validation.Query.Selection+ ( validateSelectionSet+ ) where++import Data.Morpheus.Error.Selection (cannotQueryField, duplicateQuerySelections,+ hasNoSubfields, subfieldsNotSelected)+import Data.Morpheus.Error.Variable (unknownType)+import Data.Morpheus.Types.Internal.AST.Operation (ValidVariables)+import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawSelection (..),+ RawSelection' (..), RawSelectionSet)+import Data.Morpheus.Types.Internal.AST.Selection (Selection (..), SelectionRec (..), SelectionSet)+import Data.Morpheus.Types.Internal.Base (EnhancedKey (..))+import Data.Morpheus.Types.Internal.Data (DataField (..), DataFullType (..), DataOutputObject,+ DataType (..), DataTypeLib (..), allDataTypes)+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Validation.Internal.Utils (checkNameCollision, lookupType)+import Data.Morpheus.Validation.Query.Arguments (validateArguments)+import Data.Morpheus.Validation.Query.Fragment (castFragmentType, resolveSpread)+import Data.Morpheus.Validation.Query.Utils.Selection (lookupFieldAsSelectionSet, lookupSelectionField,+ lookupUnionTypes)+import Data.Text (Text)++checkDuplicatesOn :: DataOutputObject -> SelectionSet -> Validation SelectionSet+checkDuplicatesOn DataType {typeName = name'} keys = checkNameCollision enhancedKeys selError >> pure keys+ where+ selError = duplicateQuerySelections name'+ enhancedKeys = map selToKey keys+ selToKey (key', Selection {selectionPosition = position'}) = EnhancedKey key' position'++clusterUnionSelection ::+ FragmentLib -> Text -> [DataOutputObject] -> (Text, RawSelection) -> Validation ([Fragment], SelectionSet)+clusterUnionSelection fragments type' possibleTypes' = splitFrag+ where+ packFragment fragment = return ([fragment], [])+ typeNames = map typeName possibleTypes'+ splitFrag :: (Text, RawSelection) -> Validation ([Fragment], SelectionSet)+ splitFrag (_, Spread ref) = resolveSpread fragments typeNames ref >>= packFragment+ splitFrag ("__typename", RawSelectionField RawSelection' {rawSelectionPosition = position'}) =+ return+ ( []+ , [ ( "__typename"+ , Selection {selectionRec = SelectionField, selectionArguments = [], selectionPosition = position'})+ ])+ splitFrag (key, RawSelectionSet RawSelection' {rawSelectionPosition}) =+ Left $ cannotQueryField key type' rawSelectionPosition+ splitFrag (key, RawSelectionField RawSelection' {rawSelectionPosition}) =+ Left $ cannotQueryField key type' rawSelectionPosition+ splitFrag (key', RawAlias {rawAliasPosition = position'}) = Left $ cannotQueryField key' type' position'+ splitFrag (_, InlineFragment fragment') =+ castFragmentType Nothing (fragmentPosition fragment') typeNames fragment' >>= packFragment++categorizeTypes :: [DataOutputObject] -> [Fragment] -> [(DataOutputObject, [Fragment])]+categorizeTypes types fragments = filter notEmpty $ map categorizeType types+ where+ notEmpty = (0 /=) . length . snd+ categorizeType :: DataOutputObject -> (DataOutputObject, [Fragment])+ categorizeType datatype = (datatype, filter matches fragments)+ where+ matches fragment = fragmentType fragment == typeName datatype++flatTuple :: [([a], [b])] -> ([a], [b])+flatTuple list' = (concatMap fst list', concatMap snd list')+ {-+ - all Variable and Fragment references will be: resolved and validated+ - unionTypes: will be clustered under type names+ ...A on T1 {<SelectionA>}+ ...B on T2 {<SelectionB>}+ ...C on T2 {<SelectionC>}+ will be become : [+ ("T1",[<SelectionA>]),+ ("T2",[<SelectionB>,<SelectionC>])+ ]+ -}++validateSelectionSet ::+ DataTypeLib+ -> FragmentLib+ -> Text+ -> ValidVariables+ -> DataOutputObject+ -> RawSelectionSet+ -> Validation SelectionSet+validateSelectionSet lib fragments' operatorName variables = __validate+ where+ __validate dataType'@DataType {typeName = typeName'} selectionSet' =+ concat <$> mapM validateSelection selectionSet' >>= checkDuplicatesOn dataType'+ where+ validateFragment Fragment {fragmentSelection = selection'} = __validate dataType' selection'+ {-+ get dataField and validated arguments for RawSelection+ -}+ getValidationData key RawSelection' {rawSelectionArguments, rawSelectionPosition} = do+ selectionField <- lookupSelectionField rawSelectionPosition key dataType'+ -- validate field Argument -----+ arguments <-+ validateArguments+ lib+ operatorName+ variables+ (key, selectionField)+ rawSelectionPosition+ rawSelectionArguments+ -- check field Type existence -----+ fieldDataType <-+ lookupType+ (unknownType (fieldType selectionField) rawSelectionPosition)+ (allDataTypes lib)+ (fieldType selectionField)+ return (selectionField, fieldDataType, arguments)+ -- validate single selection: InlineFragments and Spreads will Be resolved and included in SelectionSet+ --+ validateSelection :: (Text, RawSelection) -> Validation SelectionSet+ validateSelection (key', RawAlias {rawAliasSelection = rawSelection', rawAliasPosition = position'}) =+ fmap processSingleSelection <$> validateSelection rawSelection'+ where+ processSingleSelection (selKey', selection') =+ ( key'+ , selection'+ { selectionRec = SelectionAlias {aliasFieldName = selKey', aliasSelection = selectionRec selection'}+ , selectionPosition = position'+ })+ validateSelection (key', RawSelectionSet fullRawSelection'@RawSelection' { rawSelectionRec = rawSelectors+ , rawSelectionPosition = position'+ }) = do+ (dataField', dataType, arguments) <- getValidationData key' fullRawSelection'+ case dataType of+ Union _ -> do+ (categories, __typename) <- clusterTypes+ mapM (validateCluster __typename) categories >>= returnSelection arguments . UnionSelection+ where clusterTypes = do+ unionTypes <- lookupUnionTypes position' key' lib dataField'+ (spreads, __typename) <-+ flatTuple <$> mapM (clusterUnionSelection fragments' typeName' unionTypes) rawSelectors+ return (categorizeTypes unionTypes spreads, __typename)+ --+ -- second arguments will be added to every selection cluster+ validateCluster :: SelectionSet -> (DataOutputObject, [Fragment]) -> Validation (Text, SelectionSet)+ validateCluster sysSelection' (type', frags') = do+ selection' <- __validate type' (concatMap fragmentSelection frags')+ return (typeName type', sysSelection' ++ selection')+ OutputObject _ -> do+ fieldType' <- lookupFieldAsSelectionSet position' key' lib dataField'+ __validate fieldType' rawSelectors >>= returnSelection arguments . SelectionSet+ -- DataFullType+ -- = Leaf DataLeaf+ -- | InputObject DataInputObject+ -- | InputUnion DataUnion+ _ -> Left $ hasNoSubfields key' (fieldType dataField') position'+ where+ returnSelection arguments' selection' =+ pure+ [ ( key'+ , Selection+ {selectionArguments = arguments', selectionRec = selection', selectionPosition = position'})+ ]+ validateSelection (key', RawSelectionField fullRawSelection'@RawSelection' {rawSelectionPosition}) = do+ (dataField, datatype, arguments') <- getValidationData key' fullRawSelection'+ isLeaf datatype dataField+ pure+ [ ( key'+ , Selection+ { selectionArguments = arguments'+ , selectionRec = SelectionField+ , selectionPosition = rawSelectionPosition+ })+ ]+ where+ isLeaf (Leaf _) _ = Right ()+ isLeaf _ DataField {fieldType} = Left $ subfieldsNotSelected key' fieldType rawSelectionPosition+ validateSelection (_, Spread reference') = resolveSpread fragments' [typeName'] reference' >>= validateFragment+ validateSelection (_, InlineFragment fragment') =+ castFragmentType Nothing (fragmentPosition fragment') [typeName'] fragment' >>= validateFragment
+ src/Data/Morpheus/Validation/Query/Utils/Selection.hs view
@@ -0,0 +1,29 @@+module Data.Morpheus.Validation.Query.Utils.Selection+ ( lookupFieldAsSelectionSet+ , lookupSelectionField+ , lookupUnionTypes+ ) where++import Data.Morpheus.Error.Selection (cannotQueryField, hasNoSubfields)+import Data.Morpheus.Types.Internal.Base (Position)+import Data.Morpheus.Types.Internal.Data (DataField (..), DataOutputField, DataOutputObject,+ DataType (..), DataTypeLib (..))+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Validation.Internal.Utils (lookupField, lookupType)+import Data.Text (Text)++lookupUnionTypes :: Position -> Text -> DataTypeLib -> DataOutputField -> Validation [DataOutputObject]+lookupUnionTypes position' key' lib' DataField {fieldType = type'} =+ lookupType error' (union lib') type' >>= mapM (lookupType error' (object lib') . fieldType) . typeData+ where+ error' = hasNoSubfields key' type' position'++lookupFieldAsSelectionSet :: Position -> Text -> DataTypeLib -> DataOutputField -> Validation DataOutputObject+lookupFieldAsSelectionSet position' key' lib' DataField {fieldType = type'} = lookupType error' (object lib') type'+ where+ error' = hasNoSubfields key' type' position'++lookupSelectionField :: Position -> Text -> DataOutputObject -> Validation DataOutputField+lookupSelectionField position' key' DataType {typeData = fields', typeName = name'} = lookupField key' fields' error'+ where+ error' = cannotQueryField key' name' position'
+ src/Data/Morpheus/Validation/Query/Validation.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}++module Data.Morpheus.Validation.Query.Validation+ ( validateRequest+ ) where++import Data.Map (fromList)+import Data.Morpheus.Error.Mutation (mutationIsNotDefined)+import Data.Morpheus.Error.Subscription (subscriptionIsNotDefined)+import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), OperationKind (..), RawOperation,+ ValidOperation)+import Data.Morpheus.Types.Internal.Data (DataOutputObject, DataTypeLib (..))+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Types.Types (GQLQueryRoot (..))+import Data.Morpheus.Validation.Internal.Utils (VALIDATION_MODE)+import Data.Morpheus.Validation.Query.Fragment (validateFragments)+import Data.Morpheus.Validation.Query.Selection (validateSelectionSet)+import Data.Morpheus.Validation.Query.Variable (resolveOperationVariables)++getOperationDataType :: RawOperation -> DataTypeLib -> Validation DataOutputObject+getOperationDataType Operation {operationKind = QUERY} lib = pure $ snd $ query lib+getOperationDataType Operation {operationKind = MUTATION, operationPosition} lib =+ case mutation lib of+ Just (_, mutation') -> pure mutation'+ Nothing -> Left $ mutationIsNotDefined operationPosition+getOperationDataType Operation {operationKind = SUBSCRIPTION, operationPosition} lib =+ case subscription lib of+ Just (_, subscription') -> pure subscription'+ Nothing -> Left $ subscriptionIsNotDefined operationPosition++validateRequest :: DataTypeLib -> VALIDATION_MODE -> GQLQueryRoot -> Validation ValidOperation+validateRequest lib validationMode GQLQueryRoot { fragments+ , inputVariables+ , operation = rawOperation@Operation { operationName+ , operationKind+ , operationSelection+ , operationPosition+ }+ } = do+ operationDataType <- getOperationDataType rawOperation lib+ variables <- resolveOperationVariables lib fragments (fromList inputVariables) validationMode rawOperation+ validateFragments lib fragments operationSelection+ selection <- validateSelectionSet lib fragments operationName variables operationDataType operationSelection+ pure $ Operation {operationName, operationKind, operationArgs = [], operationSelection = selection, operationPosition}
+ src/Data/Morpheus/Validation/Query/Variable.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE NamedFieldPuns #-}++module Data.Morpheus.Validation.Query.Variable+ ( resolveOperationVariables+ ) where++import Data.List ((\\))+import qualified Data.Map as M (lookup)+import Data.Maybe (maybe)+import Data.Morpheus.Error.Input (InputValidation, inputErrorMessage)+import Data.Morpheus.Error.Variable (uninitializedVariable, unknownType, unusedVariables,+ variableGotInvalidValue)+import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), RawOperation, ValidVariables,+ Variable (..))+import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawArgument (..),+ RawSelection (..), RawSelection' (..), RawSelectionSet,+ Reference (..))+import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Position)+import Data.Morpheus.Types.Internal.Data (DataInputType, DataTypeLib)+import Data.Morpheus.Types.Internal.Validation (Validation)+import Data.Morpheus.Types.Internal.Value (Value (..))+import Data.Morpheus.Types.Types (Variables)+import Data.Morpheus.Validation.Internal.Utils (VALIDATION_MODE (..), getInputType)+import Data.Morpheus.Validation.Query.Fragment (getFragment)+import Data.Morpheus.Validation.Query.Input.Object (validateInputValue)+import Data.Semigroup ((<>))+import Data.Text (Text)++getVariableType :: Text -> Position -> DataTypeLib -> Validation DataInputType+getVariableType type' position' lib' = getInputType type' lib' error'+ where+ error' = unknownType type' position'++lookupVariable :: Variables -> Text -> (Text -> error) -> Either error Value+lookupVariable variables' key' error' =+ case M.lookup key' variables' of+ Nothing -> Left $ error' key'+ Just value -> pure value++handleInputError :: Text -> Position -> InputValidation Value -> Validation (Text, Value)+handleInputError key' position' (Left error') = Left $ variableGotInvalidValue key' (inputErrorMessage error') position'+handleInputError key' _ (Right value') = pure (key', value')++concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]+concatMapM f = fmap concat . mapM f++allVariableReferences :: FragmentLib -> [RawSelectionSet] -> Validation [EnhancedKey]+allVariableReferences fragmentLib = concatMapM (concatMapM searchReferences)+ where+ referencesFromArgument :: (Text, RawArgument) -> [EnhancedKey]+ referencesFromArgument (_, RawArgument {}) = []+ referencesFromArgument (_, VariableReference Reference {referenceName, referencePosition}) =+ [EnhancedKey referenceName referencePosition]+ -- | search used variables in every arguments+ searchReferences :: (Text, RawSelection) -> Validation [EnhancedKey]+ searchReferences (_, RawSelectionSet RawSelection' {rawSelectionArguments, rawSelectionRec}) =+ getArgs <$> concatMapM searchReferences rawSelectionRec+ where+ getArgs :: [EnhancedKey] -> [EnhancedKey]+ getArgs x = concatMap referencesFromArgument rawSelectionArguments <> x+ searchReferences (_, InlineFragment Fragment {fragmentSelection}) = concatMapM searchReferences fragmentSelection+ searchReferences (_, RawAlias {rawAliasSelection}) = searchReferences rawAliasSelection+ searchReferences (_, RawSelectionField RawSelection' {rawSelectionArguments}) =+ return $ concatMap referencesFromArgument rawSelectionArguments+ searchReferences (_, Spread reference) =+ getFragment reference fragmentLib >>= concatMapM searchReferences . fragmentSelection++resolveOperationVariables ::+ DataTypeLib -> FragmentLib -> Variables -> VALIDATION_MODE -> RawOperation -> Validation ValidVariables+resolveOperationVariables typeLib lib root validationMode Operation {operationName, operationSelection, operationArgs} = do+ allVariableReferences lib [operationSelection] >>= checkUnusedVariables+ mapM (lookupAndValidateValueOnBody typeLib root validationMode) operationArgs+ where+ varToKey :: (Text, Variable ()) -> EnhancedKey+ varToKey (key', Variable {variablePosition}) = EnhancedKey key' variablePosition+ --+ checkUnusedVariables :: [EnhancedKey] -> Validation ()+ checkUnusedVariables refs =+ case map varToKey operationArgs \\ refs of+ [] -> pure ()+ unused' -> Left $ unusedVariables operationName unused'++lookupAndValidateValueOnBody ::+ DataTypeLib -> Variables -> VALIDATION_MODE -> (Text, Variable ()) -> Validation (Text, Variable Value)+lookupAndValidateValueOnBody typeLib bodyVariables validationMode (key, var@Variable { variableType+ , variablePosition+ , isVariableRequired+ , variableTypeWrappers+ }) =+ toVariable <$>+ (getVariableType variableType variablePosition typeLib >>=+ checkType (validationMode /= WITHOUT_VARIABLES && isVariableRequired))+ where+ toVariable (varKey, variableValue) = (varKey, var {variableValue})+ ------------------------------------------------------------------+ checkType True varType =+ lookupVariable bodyVariables key (uninitializedVariable variablePosition variableType) >>= validator varType+ checkType False varType = maybe (pure (key, Null)) (validator varType) (M.lookup key bodyVariables)+ -----------------------------------------------------------------------------------------------+ validator varType varValue =+ handleInputError key variablePosition $ validateInputValue typeLib [] variableTypeWrappers varType (key, varValue)
− src/Data/Morpheus/Validation/Selection.hs
@@ -1,226 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}--module Data.Morpheus.Validation.Selection- ( validateSelectionSet- ) where--import Data.Morpheus.Error.Selection (cannotQueryField, duplicateQuerySelections,- hasNoSubfields, subfieldsNotSelected)-import Data.Morpheus.Error.Variable (unknownType)-import Data.Morpheus.Types.Internal.AST.Operation (ValidVariables)-import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawSelection (..),- RawSelection' (..), RawSelectionSet)-import Data.Morpheus.Types.Internal.AST.Selection (Selection (..), SelectionRec (..), SelectionSet)-import Data.Morpheus.Types.Internal.Base (EnhancedKey (..))-import Data.Morpheus.Types.Internal.Data (DataField (..), DataFullType (..), DataOutputObject,- DataType (..), DataTypeLib (..), allDataTypes)-import Data.Morpheus.Types.Internal.Validation (Validation)-import Data.Morpheus.Validation.Arguments (validateArguments)-import Data.Morpheus.Validation.Fragment (castFragmentType, resolveSpread)-import Data.Morpheus.Validation.Utils.Selection (lookupFieldAsSelectionSet, lookupSelectionField,- lookupUnionTypes)-import Data.Morpheus.Validation.Utils.Utils (checkNameCollision, lookupType)-import Data.Text (Text)--checkDuplicatesOn :: DataOutputObject -> SelectionSet -> Validation SelectionSet-checkDuplicatesOn DataType {typeName = name'} keys =- checkNameCollision enhancedKeys selError >> pure keys- where- selError = duplicateQuerySelections name'- enhancedKeys = map selToKey keys- selToKey (key', Selection {selectionPosition = position'}) =- EnhancedKey key' position'--clusterUnionSelection ::- FragmentLib- -> Text- -> [DataOutputObject]- -> (Text, RawSelection)- -> Validation ([Fragment], SelectionSet)-clusterUnionSelection fragments type' possibleTypes' = splitFrag- where- packFragment fragment = return ([fragment], [])- typeNames = map typeName possibleTypes'- splitFrag :: (Text, RawSelection) -> Validation ([Fragment], SelectionSet)- splitFrag (_, Spread ref) =- resolveSpread fragments typeNames ref >>= packFragment- splitFrag ("__typename", RawSelectionField RawSelection' {rawSelectionPosition = position'}) =- return- ( []- , [ ( "__typename"- , Selection- { selectionRec = SelectionField- , selectionArguments = []- , selectionPosition = position'- })- ])- splitFrag (key, RawSelectionSet RawSelection' {rawSelectionPosition}) =- Left $ cannotQueryField key type' rawSelectionPosition- splitFrag (key, RawSelectionField RawSelection' {rawSelectionPosition}) =- Left $ cannotQueryField key type' rawSelectionPosition- splitFrag (key', RawAlias {rawAliasPosition = position'}) =- Left $ cannotQueryField key' type' position'- splitFrag (_, InlineFragment fragment') =- castFragmentType Nothing (fragmentPosition fragment') typeNames fragment' >>=- packFragment--categorizeTypes ::- [DataOutputObject] -> [Fragment] -> [(DataOutputObject, [Fragment])]-categorizeTypes types fragments = filter notEmpty $ map categorizeType types- where- notEmpty = (0 /=) . length . snd- categorizeType :: DataOutputObject -> (DataOutputObject, [Fragment])- categorizeType datatype = (datatype, filter matches fragments)- where- matches fragment = fragmentType fragment == typeName datatype--flatTuple :: [([a], [b])] -> ([a], [b])-flatTuple list' = (concatMap fst list', concatMap snd list')- {-- - all Variable and Fragment references will be: resolved and validated- - unionTypes: will be clustered under type names- ...A on T1 {<SelectionA>}- ...B on T2 {<SelectionB>}- ...C on T2 {<SelectionC>}- will be become : [- ("T1",[<SelectionA>]),- ("T2",[<SelectionB>,<SelectionC>])- ]- -}--validateSelectionSet ::- DataTypeLib- -> FragmentLib- -> Text- -> ValidVariables- -> DataOutputObject- -> RawSelectionSet- -> Validation SelectionSet-validateSelectionSet lib fragments' operatorName variables = __validate- where- __validate dataType'@DataType {typeName = typeName'} selectionSet' =- concat <$> mapM validateSelection selectionSet' >>=- checkDuplicatesOn dataType'- where- validateFragment Fragment {fragmentSelection = selection'} =- __validate dataType' selection'- {-- get dataField and validated arguments for RawSelection- -}- getValidationData key RawSelection' { rawSelectionArguments- , rawSelectionPosition- } = do- selectionField <-- lookupSelectionField rawSelectionPosition key dataType'- -- validate field Argument ------ arguments <-- validateArguments- lib- operatorName- variables- (key, selectionField)- rawSelectionPosition- rawSelectionArguments- -- check field Type existence ------ fieldDataType <-- lookupType- (unknownType (fieldType selectionField) rawSelectionPosition)- (allDataTypes lib)- (fieldType selectionField)- return (selectionField, fieldDataType, arguments)- -- validate single selection: InlineFragments and Spreads will Be resolved and included in SelectionSet- --- validateSelection :: (Text, RawSelection) -> Validation SelectionSet- validateSelection (key', RawAlias { rawAliasSelection = rawSelection'- , rawAliasPosition = position'- }) =- fmap processSingleSelection <$> validateSelection rawSelection'- where- processSingleSelection (selKey', selection') =- ( key'- , selection'- { selectionRec =- SelectionAlias- { aliasFieldName = selKey'- , aliasSelection = selectionRec selection'- }- , selectionPosition = position'- })- validateSelection (key', RawSelectionSet fullRawSelection'@RawSelection' { rawSelectionRec = rawSelectors- , rawSelectionPosition = position'- }) = do- (dataField', dataType, arguments) <-- getValidationData key' fullRawSelection'- case dataType of- Union _ -> do- (categories, __typename) <- clusterTypes- mapM (validateCluster __typename) categories >>=- returnSelection arguments . UnionSelection- where clusterTypes = do- unionTypes <-- lookupUnionTypes position' key' lib dataField'- (spreads, __typename) <-- flatTuple <$>- mapM- (clusterUnionSelection fragments' typeName' unionTypes)- rawSelectors- return (categorizeTypes unionTypes spreads, __typename)- --- -- second arguments will be added to every selection cluster- validateCluster ::- SelectionSet- -> (DataOutputObject, [Fragment])- -> Validation (Text, SelectionSet)- validateCluster sysSelection' (type', frags') = do- selection' <-- __validate type' (concatMap fragmentSelection frags')- return (typeName type', sysSelection' ++ selection')- OutputObject _ -> do- fieldType' <-- lookupFieldAsSelectionSet position' key' lib dataField'- __validate fieldType' rawSelectors >>=- returnSelection arguments . SelectionSet- -- DataFullType- -- = Leaf DataLeaf- -- | InputObject DataInputObject- -- | InputUnion DataUnion- _ -> Left $ hasNoSubfields key' (fieldType dataField') position'- where- returnSelection arguments' selection' =- pure- [ ( key'- , Selection- { selectionArguments = arguments'- , selectionRec = selection'- , selectionPosition = position'- })- ]- validateSelection (key', RawSelectionField fullRawSelection'@RawSelection' {rawSelectionPosition}) = do- (dataField, datatype, arguments') <-- getValidationData key' fullRawSelection'- isLeaf datatype dataField- pure- [ ( key'- , Selection- { selectionArguments = arguments'- , selectionRec = SelectionField- , selectionPosition = rawSelectionPosition- })- ]- where- isLeaf (Leaf _) _ = Right ()- isLeaf _ DataField {fieldType} =- Left $ subfieldsNotSelected key' fieldType rawSelectionPosition- validateSelection (_, Spread reference') =- resolveSpread fragments' [typeName'] reference' >>= validateFragment- validateSelection (_, InlineFragment fragment') =- castFragmentType- Nothing- (fragmentPosition fragment')- [typeName']- fragment' >>=- validateFragment
− src/Data/Morpheus/Validation/Utils/Selection.hs
@@ -1,44 +0,0 @@-module Data.Morpheus.Validation.Utils.Selection- ( lookupFieldAsSelectionSet- , lookupSelectionField- , lookupUnionTypes- ) where--import Data.Morpheus.Error.Selection (cannotQueryField, hasNoSubfields)-import Data.Morpheus.Types.Internal.Base (Position)-import Data.Morpheus.Types.Internal.Data (DataField (..), DataOutputField, DataOutputObject,- DataType (..), DataTypeLib (..))-import Data.Morpheus.Types.Internal.Validation (Validation)-import Data.Morpheus.Validation.Utils.Utils (lookupField, lookupType)-import Data.Text (Text)--lookupUnionTypes ::- Position- -> Text- -> DataTypeLib- -> DataOutputField- -> Validation [DataOutputObject]-lookupUnionTypes position' key' lib' DataField {fieldType = type'} =- lookupType error' (union lib') type' >>=- mapM (lookupType error' (object lib') . fieldType) . typeData- where- error' = hasNoSubfields key' type' position'--lookupFieldAsSelectionSet ::- Position- -> Text- -> DataTypeLib- -> DataOutputField- -> Validation DataOutputObject-lookupFieldAsSelectionSet position' key' lib' DataField {fieldType = type'} =- lookupType error' (object lib') type'- where- error' = hasNoSubfields key' type' position'--lookupSelectionField ::- Position -> Text -> DataOutputObject -> Validation DataOutputField-lookupSelectionField position' key' DataType { typeData = fields'- , typeName = name'- } = lookupField key' fields' error'- where- error' = cannotQueryField key' name' position'
− src/Data/Morpheus/Validation/Utils/Utils.hs
@@ -1,85 +0,0 @@-module Data.Morpheus.Validation.Utils.Utils- ( differKeys- , existsObjectType- , lookupType- , getInputType- , lookupField- , checkNameCollision- , checkForUnknownKeys- , VALIDATION_MODE(..)- ) where--import Data.List ((\\))-import Data.Morpheus.Error.Variable (unknownType)-import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Key, Position, enhanceKeyWithNull)-import Data.Morpheus.Types.Internal.Data (DataInputType, DataKind (..), DataLeaf (..), DataOutputObject,- DataTypeLib (..))-import Data.Morpheus.Types.Internal.Validation (Validation)-import qualified Data.Set as S-import Data.Text (Text)--type GenError error a = error -> Either error a--data VALIDATION_MODE- = WITHOUT_VARIABLES- | FULL_VALIDATION- deriving (Eq, Show)--lookupType :: error -> [(Text, a)] -> Text -> Either error a-lookupType error' lib' typeName' =- case lookup typeName' lib' of- Nothing -> Left error'- Just x -> pure x--lookupField :: Text -> [(Text, fType)] -> GenError error fType-lookupField id' lib' error' =- case lookup id' lib' of- Nothing -> Left error'- Just field -> pure field--getInputType :: Text -> DataTypeLib -> GenError error DataInputType-getInputType typeName' lib error' =- case lookup typeName' (inputObject lib) of- Just x -> pure (ObjectKind x)- Nothing ->- case lookup typeName' (inputUnion lib) of- Just x -> pure (UnionKind x)- Nothing ->- case lookup typeName' (leaf lib) of- Nothing -> Left error'- Just (BaseScalar x) -> pure (ScalarKind x)- Just (CustomScalar x) -> pure (ScalarKind x)- Just (LeafEnum x) -> pure (EnumKind x)--existsObjectType ::- Position -> Text -> DataTypeLib -> Validation DataOutputObject-existsObjectType position' typeName' lib =- lookupType error' (object lib) typeName'- where- error' = unknownType typeName' position'--differKeys :: [EnhancedKey] -> [Key] -> [EnhancedKey]-differKeys enhanced keys = enhanced \\ map enhanceKeyWithNull keys--removeDuplicates :: Ord a => [a] -> [a]-removeDuplicates = S.toList . S.fromList--elementOfKeys :: [Text] -> EnhancedKey -> Bool-elementOfKeys keys' EnhancedKey {uid = id'} = id' `elem` keys'--checkNameCollision ::- [EnhancedKey] -> ([EnhancedKey] -> error) -> Either error [EnhancedKey]-checkNameCollision enhancedKeys errorGenerator =- case enhancedKeys \\ removeDuplicates enhancedKeys of- [] -> pure enhancedKeys- duplicates -> Left $ errorGenerator duplicates--checkForUnknownKeys ::- [EnhancedKey]- -> [Text]- -> ([EnhancedKey] -> error)- -> Either error [EnhancedKey]-checkForUnknownKeys enhancedKeys' keys' errorGenerator' =- case filter (not . elementOfKeys keys') enhancedKeys' of- [] -> pure enhancedKeys'- unknownKeys' -> Left $ errorGenerator' unknownKeys'
− src/Data/Morpheus/Validation/Validation.hs
@@ -1,70 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}--module Data.Morpheus.Validation.Validation- ( validateRequest- ) where--import Data.Map (fromList)-import Data.Morpheus.Error.Mutation (mutationIsNotDefined)-import Data.Morpheus.Error.Subscription (subscriptionIsNotDefined)-import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), OperationKind (..), RawOperation,- ValidOperation)-import Data.Morpheus.Types.Internal.Data (DataOutputObject, DataTypeLib (..))-import Data.Morpheus.Types.Internal.Validation (Validation)-import Data.Morpheus.Types.Types (GQLQueryRoot (..))-import Data.Morpheus.Validation.Fragment (validateFragments)-import Data.Morpheus.Validation.Selection (validateSelectionSet)-import Data.Morpheus.Validation.Utils.Utils (VALIDATION_MODE)-import Data.Morpheus.Validation.Variable (resolveOperationVariables)--getOperationDataType ::- RawOperation -> DataTypeLib -> Validation DataOutputObject-getOperationDataType Operation {operationKind = QUERY} lib =- pure $ snd $ query lib-getOperationDataType Operation {operationKind = MUTATION, operationPosition} lib =- case mutation lib of- Just (_, mutation') -> pure mutation'- Nothing -> Left $ mutationIsNotDefined operationPosition-getOperationDataType Operation {operationKind = SUBSCRIPTION, operationPosition} lib =- case subscription lib of- Just (_, subscription') -> pure subscription'- Nothing -> Left $ subscriptionIsNotDefined operationPosition--validateRequest ::- DataTypeLib -> VALIDATION_MODE -> GQLQueryRoot -> Validation ValidOperation-validateRequest lib validationMode GQLQueryRoot { fragments- , inputVariables- , operation = rawOperation@Operation { operationName- , operationKind- , operationSelection- , operationPosition- }- } = do- operationDataType <- getOperationDataType rawOperation lib- variables <-- resolveOperationVariables- lib- fragments- (fromList inputVariables)- validationMode- rawOperation- validateFragments lib fragments operationSelection- selection <-- validateSelectionSet- lib- fragments- operationName- variables- operationDataType- operationSelection- pure $- Operation- { operationName- , operationKind- , operationArgs = []- , operationSelection = selection- , operationPosition- }
− src/Data/Morpheus/Validation/Variable.hs
@@ -1,130 +0,0 @@-{-# LANGUAGE NamedFieldPuns #-}--module Data.Morpheus.Validation.Variable- ( resolveOperationVariables- ) where--import Data.List ((\\))-import qualified Data.Map as M (lookup)-import Data.Maybe (maybe)-import Data.Morpheus.Error.Input (InputValidation, inputErrorMessage)-import Data.Morpheus.Error.Variable (uninitializedVariable, unknownType, unusedVariables,- variableGotInvalidValue)-import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), RawOperation, ValidVariables,- Variable (..))-import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawArgument (..),- RawSelection (..), RawSelection' (..), RawSelectionSet,- Reference (..))-import Data.Morpheus.Types.Internal.Base (EnhancedKey (..), Position)-import Data.Morpheus.Types.Internal.Data (DataInputType, DataTypeLib)-import Data.Morpheus.Types.Internal.Validation (Validation)-import Data.Morpheus.Types.Internal.Value (Value (..))-import Data.Morpheus.Types.Types (Variables)-import Data.Morpheus.Validation.Fragment (getFragment)-import Data.Morpheus.Validation.Input.Object (validateInputValue)-import Data.Morpheus.Validation.Utils.Utils (VALIDATION_MODE (..), getInputType)-import Data.Semigroup ((<>))-import Data.Text (Text)--getVariableType :: Text -> Position -> DataTypeLib -> Validation DataInputType-getVariableType type' position' lib' = getInputType type' lib' error'- where- error' = unknownType type' position'--lookupVariable :: Variables -> Text -> (Text -> error) -> Either error Value-lookupVariable variables' key' error' =- case M.lookup key' variables' of- Nothing -> Left $ error' key'- Just value -> pure value--handleInputError ::- Text -> Position -> InputValidation Value -> Validation (Text, Value)-handleInputError key' position' (Left error') =- Left $ variableGotInvalidValue key' (inputErrorMessage error') position'-handleInputError key' _ (Right value') = pure (key', value')--concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]-concatMapM f = fmap concat . mapM f--allVariableReferences ::- FragmentLib -> [RawSelectionSet] -> Validation [EnhancedKey]-allVariableReferences fragmentLib = concatMapM (concatMapM searchReferences)- where- referencesFromArgument :: (Text, RawArgument) -> [EnhancedKey]- referencesFromArgument (_, RawArgument {}) = []- referencesFromArgument (_, VariableReference Reference { referenceName- , referencePosition- }) =- [EnhancedKey referenceName referencePosition]- -- | search used variables in every arguments- searchReferences :: (Text, RawSelection) -> Validation [EnhancedKey]- searchReferences (_, RawSelectionSet RawSelection' { rawSelectionArguments- , rawSelectionRec- }) =- getArgs <$> concatMapM searchReferences rawSelectionRec- where- getArgs :: [EnhancedKey] -> [EnhancedKey]- getArgs x = concatMap referencesFromArgument rawSelectionArguments <> x- searchReferences (_, InlineFragment Fragment {fragmentSelection}) =- concatMapM searchReferences fragmentSelection- searchReferences (_, RawAlias {rawAliasSelection}) =- searchReferences rawAliasSelection- searchReferences (_, RawSelectionField RawSelection' {rawSelectionArguments}) =- return $ concatMap referencesFromArgument rawSelectionArguments- searchReferences (_, Spread reference) =- getFragment reference fragmentLib >>=- concatMapM searchReferences . fragmentSelection--resolveOperationVariables ::- DataTypeLib- -> FragmentLib- -> Variables- -> VALIDATION_MODE- -> RawOperation- -> Validation ValidVariables-resolveOperationVariables typeLib lib root validationMode Operation { operationName- , operationSelection- , operationArgs- } = do- allVariableReferences lib [operationSelection] >>= checkUnusedVariables- mapM (lookupAndValidateValueOnBody typeLib root validationMode) operationArgs- where- varToKey :: (Text, Variable ()) -> EnhancedKey- varToKey (key', Variable {variablePosition}) =- EnhancedKey key' variablePosition- --- checkUnusedVariables :: [EnhancedKey] -> Validation ()- checkUnusedVariables refs =- case map varToKey operationArgs \\ refs of- [] -> pure ()- unused' -> Left $ unusedVariables operationName unused'--lookupAndValidateValueOnBody ::- DataTypeLib- -> Variables- -> VALIDATION_MODE- -> (Text, Variable ())- -> Validation (Text, Variable Value)-lookupAndValidateValueOnBody typeLib bodyVariables validationMode (key, var@Variable { variableType- , variablePosition- , isVariableRequired- , variableTypeWrappers- }) =- toVariable <$>- (getVariableType variableType variablePosition typeLib >>=- checkType (validationMode /= WITHOUT_VARIABLES && isVariableRequired))- where- toVariable (varKey, variableValue) = (varKey, var {variableValue})- ------------------------------------------------------------------- checkType True varType =- lookupVariable- bodyVariables- key- (uninitializedVariable variablePosition variableType) >>=- validator varType- checkType False varType =- maybe (pure (key, Null)) (validator varType) (M.lookup key bodyVariables)- ------------------------------------------------------------------------------------------------ validator varType varValue =- handleInputError key variablePosition $- validateInputValue typeLib [] variableTypeWrappers varType (key, varValue)
test/Feature/Holistic/API.hs view
@@ -7,13 +7,12 @@ ( api ) where -import Data.ByteString.Lazy.Char8 (ByteString)-import Data.Morpheus (interpreter)-import Data.Morpheus.Kind (ENUM, INPUT_OBJECT, OBJECT, SCALAR, UNION)-import Data.Morpheus.Types (Event (..), GQLRootResolver (..), GQLScalar (..), GQLType (..), ID (..),- IORes, IOSubRes, ScalarValue (..))-import Data.Text (Text)-import GHC.Generics (Generic)+import Data.Morpheus (interpreter)+import Data.Morpheus.Kind (ENUM, INPUT_OBJECT, OBJECT, SCALAR, UNION)+import Data.Morpheus.Types (Event (..), GQLRequest, GQLResponse, GQLRootResolver (..), GQLScalar (..),+ GQLType (..), ID (..), IORes, IOSubRes, ScalarValue (..))+import Data.Text (Text)+import GHC.Generics (Generic) data TestEnum = EnumA@@ -121,7 +120,7 @@ } deriving (Generic) resolveAddress :: a -> IORes Address-resolveAddress _ = return Address {city = "", houseNumber = 1, street = const $ return Nothing}+resolveAddress _ = return Address {city = "", houseNumber = 0, street = const $ return Nothing} resolveUser :: a -> IORes User resolveUser _ =@@ -137,5 +136,5 @@ , subscriptionResolver = return Subscription {newUser = const $ Event [EVENT] resolveUser} } -api :: ByteString -> IO ByteString+api :: GQLRequest -> IO GQLResponse api = interpreter rootResolver
test/Feature/Holistic/cases.json view
@@ -166,5 +166,13 @@ { "path": "introspection/kinds/UNION", "description": "test introspection of UNION kind"+ },+ {+ "path": "parsing/singleLineComments",+ "description": "parses single line comments"+ },+ {+ "path": "parsing/multilineComments",+ "description": "parses multiline comments" } ]
test/Feature/Holistic/parsing/invalidFields/response.json view
@@ -1,7 +1,7 @@ { "errors": [ {- "message": "offset=26:\nunexpected '<'\nexpecting '#', ',', '_', '}', body, digit, entry, letter, maybeArguments, or white space\n",+ "message": "offset=26:\nunexpected '<'\nexpecting \"\"\"\"\", '#', ',', '_', '}', body, digit, entry, letter, maybeArguments, or white space\n", "locations": [ { "line": 1,
test/Feature/Holistic/parsing/invalidNotNullOperator/response.json view
@@ -1,7 +1,7 @@ { "errors": [ {- "message": "offset=20:\nunexpected '@'\nexpecting '!', '#', ']', '_', digit, letter, or white space\n",+ "message": "offset=20:\nunexpected '@'\nexpecting \"\"\"\"\", '!', '#', ']', '_', digit, letter, or white space\n", "locations": [ { "line": 1,
test/Feature/Holistic/parsing/missingCloseBrace/response.json view
@@ -1,7 +1,7 @@ { "errors": [ {- "message": "offset=66:\nunexpected end of input\nexpecting '#', ',', '}', entry, or white space\n",+ "message": "offset=66:\nunexpected end of input\nexpecting \"\"\"\"\", '#', ',', '}', entry, or white space\n", "locations": [ { "line": 5,
+ test/Feature/Holistic/parsing/multilineComments/query.gql view
@@ -0,0 +1,131 @@+""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""++"""+"""++"""+++"""+++"""+ comment with tabs++"""+++query+""" +%&+%wer""g ''Comme+"""+GetUsers """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" ( """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" $v: """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ [ """ somejhgja """ Int """ 343 """ ! """ 343 """ ] # starting Comment'' # starting Comment''+) {+ user {+ """ %&+%(//+ %& ''Comme"""++ email2: """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" email """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ name """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""++ address( """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ coordinates: {+""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" longitude: 0 # starting Comment''+ latitude: "" # starting Comment'' # starting Comment'' # starting Comment''+ }+ ) """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" {+ """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ # starting Comment''+ ...City+ }+ office """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" (+ zipCode: $v # starting Comment''+ cityID: EnumA + ) """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" {+ houseNumber """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""++ ... on Address {+""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ city """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ }+ }+ """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ }+""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""}+# starting Comment''+# starting Comment''++fragment City on Address """ %&+%(//+ %& %(//+%&%& # startin""g ''Comme""" {+""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+ city""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""+}+""" %&+%(//+ %& %(//+%&%& # startin""g ''Comme"""
+ test/Feature/Holistic/parsing/multilineComments/response.json view
@@ -0,0 +1,15 @@+{+ "data": {+ "user": {+ "email2": "",+ "name": "testName",+ "address": {+ "city": ""+ },+ "office": {+ "houseNumber": 0,+ "city": ""+ }+ }+ }+}
+ test/Feature/Holistic/parsing/singleLineComments/query.gql view
@@ -0,0 +1,57 @@+# starting Comment''+# starting Comment''+query # starting Comment''+# starting Comment''+GetUsers( # starting Comment'' # starting Comment''+ $v: [# starting Comment'' # starting Comment''+ Int!] # starting Comment'' # starting Comment''+) {+ user {+ # starting Comment''+ # %&%(//+ # %&%(//+ email2: email # starting Comment'' # %&%(// # starting Comment''+ name # starting Comment''+ # starting Comment''+ # starting Comment''+ # starting Comment''++ address( # starting Comment'' # %&%(//+ coordinates: {+ # %&%(//+ longitude: 0 # starting Comment''+ latitude: "" # starting Comment'' # starting Comment'' # starting Comment''+ }+ ) {+ # starting Comment''+ ...City+ }+ office(+ zipCode: $v # starting Comment''+ cityID: EnumA # starting Comment'' # starting Comment''+ ) {+ houseNumber # starting Comment''+ # starting Comment''++ ... on Address {+ # starting Comment''++ city # starting Comment''+ # starting Comment''+ # starting Comment''+ }+ }+ # starting Comment''+ }+ # starting Comment''+}+# starting Comment''+# starting Comment''++fragment City on Address {+ # starting Comment''+ # starting Comment''+ city # starting Comment''+}+# starting Comment''+# starting Comment''
+ test/Feature/Holistic/parsing/singleLineComments/response.json view
@@ -0,0 +1,15 @@+{+ "data": {+ "user": {+ "email2": "",+ "name": "testName",+ "address": {+ "city": ""+ },+ "office": {+ "houseNumber": 0,+ "city": ""+ }+ }+ }+}
test/Feature/Input/Enum/API.hs view
@@ -7,11 +7,10 @@ ( api ) where -import Data.ByteString.Lazy.Char8 (ByteString)-import Data.Morpheus (interpreter)-import Data.Morpheus.Kind (ENUM)-import Data.Morpheus.Types (GQLRootResolver (..), GQLType (..), IORes)-import GHC.Generics (Generic)+import Data.Morpheus (interpreter)+import Data.Morpheus.Kind (ENUM)+import Data.Morpheus.Types (GQLRequest, GQLResponse, GQLRootResolver (..), GQLType (..), IORes)+import GHC.Generics (Generic) data TwoCon = LA@@ -67,5 +66,5 @@ , subscriptionResolver = return () } -api :: ByteString -> IO ByteString+api :: GQLRequest -> IO GQLResponse api = interpreter rootResolver
test/Feature/InputType/API.hs view
@@ -7,12 +7,11 @@ ( api ) where -import Data.ByteString.Lazy.Char8 (ByteString)-import Data.Morpheus (interpreter)-import Data.Morpheus.Kind (OBJECT)-import Data.Morpheus.Types (GQLRootResolver (..), GQLType (..), IORes)-import Data.Text (Text)-import GHC.Generics (Generic)+import Data.Morpheus (interpreter)+import Data.Morpheus.Kind (OBJECT)+import Data.Morpheus.Types (GQLRequest, GQLResponse, GQLRootResolver (..), GQLType (..), IORes)+import Data.Text (Text)+import GHC.Generics (Generic) data F1Args = F1Args { arg1 :: Text@@ -44,5 +43,5 @@ , subscriptionResolver = return () } -api :: ByteString -> IO ByteString+api :: GQLRequest -> IO GQLResponse api = interpreter rootResolver
test/Feature/Schema/API.hs view
@@ -7,13 +7,12 @@ ( api ) where -import Data.ByteString.Lazy.Char8 (ByteString)-import Data.Morpheus (interpreter)-import Data.Morpheus.Kind (OBJECT)-import Data.Morpheus.Types (GQLRootResolver (..), GQLType (..))-import Data.Text (Text)-import qualified Feature.Schema.A2 as A2 (A (..))-import GHC.Generics (Generic)+import Data.Morpheus (interpreter)+import Data.Morpheus.Kind (OBJECT)+import Data.Morpheus.Types (GQLRequest, GQLResponse, GQLRootResolver (..), GQLType (..))+import Data.Text (Text)+import qualified Feature.Schema.A2 as A2 (A (..))+import GHC.Generics (Generic) data A = A { aText :: Text@@ -36,5 +35,5 @@ , subscriptionResolver = return () } -api :: ByteString -> IO ByteString+api :: GQLRequest -> IO GQLResponse api = interpreter rootResolver
test/Feature/UnionType/API.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}@@ -8,12 +7,11 @@ ( api ) where -import Data.ByteString.Lazy.Char8 (ByteString)-import Data.Morpheus (interpreter)-import Data.Morpheus.Kind (OBJECT, UNION)-import Data.Morpheus.Types (GQLRootResolver (..), GQLType (..), IORes)-import Data.Text (Text)-import GHC.Generics (Generic)+import Data.Morpheus (interpreter)+import Data.Morpheus.Kind (OBJECT, UNION)+import Data.Morpheus.Types (GQLRequest, GQLResponse, GQLRootResolver (..), GQLType (..), IORes)+import Data.Text (Text)+import GHC.Generics (Generic) instance GQLType A where type KIND A = OBJECT@@ -63,5 +61,5 @@ , subscriptionResolver = return () } -api :: ByteString -> IO ByteString+api :: GQLRequest -> IO GQLResponse api = interpreter rootResolver
test/Feature/WrappedTypeName/API.hs view
@@ -7,13 +7,13 @@ ( api ) where -import Data.ByteString.Lazy.Char8 (ByteString)-import Data.Morpheus (interpreter)-import Data.Morpheus.Kind (OBJECT)-import Data.Morpheus.Types (Event (..), GQLRootResolver (..), GQLType (..), IOMutRes, IORes, IOSubRes)-import Data.Text (Text)-import Data.Typeable (Typeable)-import GHC.Generics (Generic)+import Data.Morpheus (interpreter)+import Data.Morpheus.Kind (OBJECT)+import Data.Morpheus.Types (Event (..), GQLRequest, GQLResponse, GQLRootResolver (..), GQLType (..), IOMutRes,+ IORes, IOSubRes)+import Data.Text (Text)+import Data.Typeable (Typeable)+import GHC.Generics (Generic) instance Typeable a => GQLType (WA a) where type KIND (WA a) = OBJECT@@ -67,5 +67,5 @@ } } -api :: ByteString -> IO ByteString+api :: GQLRequest -> IO GQLResponse api = interpreter rootResolver
test/Lib.hs view
@@ -12,6 +12,7 @@ import qualified Data.ByteString.Lazy as L (readFile) import Data.ByteString.Lazy.Char8 (ByteString) import Data.Maybe (fromMaybe)+import Data.Morpheus.Types (GQLResponse (..)) import Data.Text (Text, unpack) path :: Text -> String@@ -23,8 +24,8 @@ resLib :: Text -> String resLib x = path x ++ "/response.json" -maybeVariables :: Text -> IO ByteString-maybeVariables x = L.readFile (path x ++ "/variables.json") <|> return "{}"+maybeVariables :: Text -> IO (Maybe Value)+maybeVariables x = decode <$> (L.readFile (path x ++ "/variables.json") <|> return "{}") getGQLBody :: Text -> IO ByteString getGQLBody p = L.readFile (gqlLib p)
test/TestFeature.hs view
@@ -1,14 +1,19 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module TestFeature ( testFeature ) where -import Data.Aeson (FromJSON, decode, encode)+import qualified Data.Text.Lazy as LT (toStrict)+import Data.Text.Lazy.Encoding (decodeUtf8)++import Data.Aeson (FromJSON, Value, decode, encode) import Data.ByteString.Lazy.Char8 (ByteString)-import qualified Data.ByteString.Lazy.Char8 as LB (concat, pack, unpack)+import qualified Data.ByteString.Lazy.Char8 as LB (unpack)+import Data.Morpheus.Types (GQLRequest (..), GQLResponse (..)) import Data.Semigroup ((<>)) import Data.Text (Text, unpack) import qualified Data.Text as T (concat)@@ -17,33 +22,31 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertFailure, testCase) -packGQLRequest :: ByteString -> ByteString -> ByteString-packGQLRequest x variables' = LB.concat ["{\"query\":", LB.pack $ show x, ",\"variables\":", variables', "}"]+packGQLRequest :: ByteString -> Maybe Value -> GQLRequest+packGQLRequest queryBS variables =+ GQLRequest {operationName = Nothing, query = LT.toStrict $ decodeUtf8 queryBS, variables} data Case = Case { path :: Text , description :: String } deriving (Generic, FromJSON) -testFeature :: (ByteString -> IO ByteString) -> Text -> IO TestTree-testFeature api' dir' = do- cases' <- getCases (unpack dir')- test' <- sequence $ testByFiles api' <$> map (\x -> x {path = T.concat [dir', "/", path x]}) cases'- return $ testGroup (unpack dir') test'+testFeature :: (GQLRequest -> IO GQLResponse) -> Text -> IO TestTree+testFeature api dir = do+ cases' <- getCases (unpack dir)+ test' <- sequence $ testByFiles api <$> map (\x -> x {path = T.concat [dir, "/", path x]}) cases'+ return $ testGroup (unpack dir) test' -testByFiles :: (ByteString -> IO ByteString) -> Case -> IO TestTree-testByFiles testApi (Case path' description') = do- testCaseQuery <- getGQLBody path'- testCaseVariables <- maybeVariables path'- expectedValue <- getResponseBody path'- gqlResponse <- testApi $ packGQLRequest testCaseQuery testCaseVariables- - case decode gqlResponse of+testByFiles :: (GQLRequest -> IO GQLResponse) -> Case -> IO TestTree+testByFiles testApi Case {path, description} = do+ testCaseQuery <- getGQLBody path+ testCaseVariables <- maybeVariables path+ expectedResponse <- getResponseBody path+ actualResponse <- encode <$> testApi (packGQLRequest testCaseQuery testCaseVariables)+ case decode actualResponse of Nothing -> assertFailure "Bad Response"- Just response -> return $ testCase (unpack path' ++ " | " ++ description') $ customTest expectedValue response- where customTest expected value =- if expected == value- then return ()- else assertFailure generateError- where- generateError = LB.unpack $ "expected: \n " <> encode expected <> " \n but got: \n " <> gqlResponse+ Just response -> return $ testCase (unpack path ++ " | " ++ description) $ customTest expectedResponse response+ where customTest expected value+ | expected == value = return ()+ | otherwise =+ assertFailure $ LB.unpack $ "expected: \n " <> encode expected <> " \n but got: \n " <> actualResponse