diff --git a/CHANGELOG.md b/CHANGELOG.md index ae76a2c..ecc03af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org/). ## [Unreleased] + +## [0.9.0.0] - 2020-07-24 ## Fixed - Location of a parse error is returned in a singleton array with key `locations`. @@ -21,7 +23,7 @@ and this project adheres to - `Error.Error` is an error representation with a message and source location. - `Error.Response` represents a result of running a GraphQL query. - `Type.Schema` exports `Type` which lists all types possible in the schema. -- Parsing subscriptions (the execution always fails yet). +- Parsing subscriptions. - `Error.ResponseEventStream`, `Type.Out.Resolve`, `Type.Out.Subscribe` and `Type.Out.SourceEventStream` define subscription resolvers. - `Error.ResolverException` is an exception that can be thrown by (field value @@ -57,8 +59,6 @@ and this project adheres to ## Removed - `Trans.ActionT` is an unneeded layer of complexity. `Type.Out.Resolver` represents possible resolver configurations. -- `Type.Out.Resolver`: It . Resolvers are a - part of the fields and are called `Trans.ResolverT`. - `Execute.executeWithName`. `Execute.execute` takes the operation name and completely replaces `executeWithName`. @@ -323,7 +323,8 @@ and this project adheres to ### Added - Data types for the GraphQL language. -[Unreleased]: https://github.com/caraus-ecms/graphql/compare/v0.8.0.0...HEAD +[Unreleased]: https://github.com/caraus-ecms/graphql/compare/v0.9.0.0...HEAD +[0.9.0.0]: https://github.com/caraus-ecms/graphql/compare/v0.8.0.0...v0.9.0.0 [0.8.0.0]: https://github.com/caraus-ecms/graphql/compare/v0.7.0.0...v0.8.0.0 [0.7.0.0]: https://github.com/caraus-ecms/graphql/compare/v0.6.1.0...v0.7.0.0 [0.6.1.0]: https://github.com/caraus-ecms/graphql/compare/v0.6.0.0...v0.6.1.0 diff --git a/README.md b/README.md index 3b33ad7..917da40 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,100 @@ API documentation is available through You'll also find a small tutorial with some examples under [docs/tutorial](https://github.com/caraus-ecms/graphql/tree/master/docs/tutorial). +### Getting started + +We start with a simple GraphQL API that provides us with some famous and less +famous cites. + +```graphql +""" +Root Query type. +""" +type Query { + """ + Provides a cite. + """ + cite: String! +} +``` + +This is called a GraphQL schema, it defines all queries supported by the API. +`Query` is the root query type. Every GraphQL API should define a query type. + +`Query` has a single field `cite` that returns a `String`. The `!` after the +type denotes that the returned value cannot be `Null`. GraphQL fields are +nullable by default. + +To be able to work with this schema, we are going to implement it in Haskell. + +```haskell +{-# LANGUAGE OverloadedStrings #-} + +import Control.Exception (SomeException) +import qualified Data.Aeson as Aeson +import qualified Data.ByteString.Lazy.Char8 as ByteString.Lazy.Char8 +import qualified Data.HashMap.Strict as HashMap +import Language.GraphQL +import Language.GraphQL.Type +import qualified Language.GraphQL.Type.Out as Out + +-- GraphQL supports 3 kinds of operations: queries, mutations and subscriptions. +-- Our first schema supports only queries. +schema :: Schema IO +schema = Schema + { query = queryType, mutation = Nothing, subscription = Nothing } + +-- GraphQL distinguishes between input and output types. Input types are field +-- argument types and they are defined in Language.GraphQL.Type.In. Output types +-- are result types, they are defined in Language.GraphQL.Type.Out. Root types +-- are always object types. +-- +-- Here we define a type "Query". The second argument is an optional +-- description, the third one is the list of interfaces implemented by the +-- object type. The last argument is a field map. Keys are field names, values +-- are field definitions and resolvers. Resolvers are the functions, where the +-- actual logic lives, they return values for the respective fields. +queryType :: Out.ObjectType IO +queryType = Out.ObjectType "Query" (Just "Root Query type.") [] + $ HashMap.singleton "cite" citeResolver + where + -- 'ValueResolver' is a 'Resolver' data constructor, it combines a field + -- definition with its resolver function. This function resolves a value for + -- a field (as opposed to the 'EventStreamResolver' used by subscriptions). + -- Our resolver just returns a constant value. + citeResolver = ValueResolver citeField + $ pure "Piscis primum a capite foetat" + -- The first argument is an optional field description. The second one is + -- the field type and the third one is for arguments (we have none in this + -- example). + -- + -- GraphQL has named and wrapping types. String is a scalar, named type. + -- Named types are nullable by default. To make our "cite" field + -- non-nullable, we wrap it in the wrapping type, Non-Null. + citeField = Out.Field + (Just "Provides a cite.") (Out.NonNullScalarType string) HashMap.empty + +-- Now we can execute a query. Since our schema defines only one field, +-- everything we can do is to ask to resolve it and give back the result. +-- Since subscriptions don't return plain values, the 'graphql' function returns +-- an 'Either'. 'Left' is for subscriptions, 'Right' is for queries and +-- mutations. +main :: IO () +main = do + Right result <- graphql schema "{ cite }" + ByteString.Lazy.Char8.putStrLn $ Aeson.encode result +``` + +Executing this query produces the following JSON: + +```json +{ + "data": { + "cite": "Piscis primum a capite foetat" + } +} +``` + ## Further information - [Contributing guidelines](CONTRIBUTING.md). diff --git a/src/Test/Hspec/GraphQL.hs b/src/Test/Hspec/GraphQL.hs index 093685b..253b366 100644 --- a/src/Test/Hspec/GraphQL.hs +++ b/src/Test/Hspec/GraphQL.hs @@ -11,6 +11,7 @@ module Test.Hspec.GraphQL , shouldResolveTo ) where +import Control.Monad.Catch (MonadCatch) import qualified Data.Aeson as Aeson import qualified Data.HashMap.Strict as HashMap import Data.Text (Text) @@ -18,8 +19,8 @@ import Language.GraphQL.Error import Test.Hspec.Expectations (Expectation, expectationFailure, shouldBe, shouldNotSatisfy) -- | Asserts that a query resolves to some value. -shouldResolveTo - :: Either (ResponseEventStream IO Aeson.Value) Aeson.Object +shouldResolveTo :: MonadCatch m + => Either (ResponseEventStream m Aeson.Value) Aeson.Object -> Aeson.Object -> Expectation shouldResolveTo (Right actual) expected = actual `shouldBe` expected @@ -27,8 +28,8 @@ shouldResolveTo _ _ = expectationFailure "the query is expected to resolve to a value, but it resolved to an event stream" -- | Asserts that the response doesn't contain any errors. -shouldResolve - :: (Text -> IO (Either (ResponseEventStream IO Aeson.Value) Aeson.Object)) +shouldResolve :: MonadCatch m + => (Text -> IO (Either (ResponseEventStream m Aeson.Value) Aeson.Object)) -> Text -> Expectation shouldResolve executor query = do diff --git a/stack.yaml b/stack.yaml index a494b98..042ce94 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,4 +1,4 @@ -resolver: lts-16.6 +resolver: lts-16.10 packages: - .