From f6f7b2cfa6823495433f7cbddd48da0cbbb991c1 Mon Sep 17 00:00:00 2001 From: Tom Hunger Date: Tue, 24 Jan 2017 15:22:27 +0000 Subject: [PATCH 1/7] Explain combining field handlers. --- docs/source/tutorial/Introduction.lhs | 41 +++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index 5de6775..da62b93 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -10,6 +10,7 @@ module Introduction where import Protolude +import GraphQL import GraphQL.API (Object, Field, Argument, (:>)) import GraphQL.Resolver (Handler, (:<>)(..)) ``` @@ -40,11 +41,11 @@ And if we had a code to handle that type (more later) we could query it like thi { me(greeting: "hello") } ``` -## The handler +## Implementing a handler -We defined a corresponding handler via the `Handler m a` which takes +We define a corresponding handler via the `Handler m a` which takes the monad to run in (`IO` in this case) and the actual API definition -(`HelloWorld`). +(`HelloWorld`): ```haskell handler :: Handler IO HelloWorld @@ -54,10 +55,40 @@ handler = pure (\greeting -> pure (greeting <> " to me")) The implementation looks slightly weird, but it's weird for good reasons. In order: -* The first `pure` allows us to run actions in the base monad (`IO` -here) before returning anything. This is useful to allocate a resource +* The first `pure` allows us to run actions in the base monad (here `IO` + before returning anything. This is useful to allocate a resource like a database connection. * The `pure` in the function call allows us to **avoid running actions** when the field hasn't been requested: Each handler is a separate monadic action so we only perform the side effects for fields present in the query. + + +## Combining field handlers with :<> + +Let's implement a simple calculator that cann add and subtract integers: + +``` haskell +type Calculator = Object "Calculator" '[] + '[ Argument "a" Int32 :> Argument "b" Int32 :> Field "add" Int32 + , Argument "a" Int32 :> Argument "b" Int32 :> Field "subtract" Int32 + ] +``` + +Every element in a list in Haskell has the same type, so we can't +really return a list of different handlers. Instead we compose the +different handlers with a new operator, `:<>`. This operator, commonly +called birdface, is based on the operator for monoids, `<>`. + +``` haskell +calculator :: Handler IO Calculator +calculator = pure (add :<> subtract) + where + add a b = pure (a + b) + subtract a b = pure (a - b) +``` + +Note that we still need `pure` for each individual handler. + + +## From 10fee46287205ee93f238bf35db0c89e7dc2cac8 Mon Sep 17 00:00:00 2001 From: Tom Hunger Date: Tue, 24 Jan 2017 15:39:42 +0000 Subject: [PATCH 2/7] Unions. --- docs/source/tutorial/Introduction.lhs | 65 +++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index da62b93..beffa93 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -6,13 +6,15 @@ First some imports: {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} + module Introduction where import Protolude import GraphQL -import GraphQL.API (Object, Field, Argument, (:>)) -import GraphQL.Resolver (Handler, (:<>)(..)) +import GraphQL.API (Object, Field, Argument, (:>), Union, Enum) +import GraphQL.Resolver (Handler, (:<>)(..), unionValue) ``` The core idea for this library is that we define a composite type that @@ -66,7 +68,7 @@ present in the query. ## Combining field handlers with :<> -Let's implement a simple calculator that cann add and subtract integers: +Let's implement a simple calculator that can add and subtract integers: ``` haskell type Calculator = Object "Calculator" '[] @@ -91,4 +93,59 @@ calculator = pure (add :<> subtract) Note that we still need `pure` for each individual handler. -## +## Nesting Objects + +Objects can be used as a type in fields. This allows us to implement a +server for the classic GraphQL example query: + + +``` +{ + me { name } +} +``` + +The Haskell schema for that looks like this: + +``` haskell +type User = Object "User" '[] '[Field "name" Text] +type Query = Object "Query" '[] '[Field "me" User] +``` + +Note the type `User` for `me`. + + +We write nested handlers the same way we write the top-level handler: + +``` haskell +user :: Handler IO User +user = pure (pure "mort") + +query :: Handler IO Query +query = pure user +``` + +## Unions + +Union handlers require special treatment in Haskell because we need to +return the same type for each possible, different type in the union. + +Let's define a union: + +``` haskell +type UserOrCalcualtor = Union "UserOrCalcualtor" '[User, Calculator] +type UnionQuery = Object "UnionQuery" '[] '[Field "union" UserOrCalcualtor] +``` + +and a handler that returns a user: + +``` haskell +unionQuery :: Handler IO UnionQuery +unionQuery = pure (unionValue @User user) +``` + +Note that, while `unionValue` looks a bit like `unsafeCoerce` by +forcing one type to become another type, it's actually type-safe +because we use a *type-index* to pick the correct type from the +union. Using e.g. `unionValue @HelloWorld handler` will not compile +because `HelloWorld` is not in the union. From ebe03f76f6464d9496bf796bdaa6e99a6ca85ab5 Mon Sep 17 00:00:00 2001 From: Tom Hunger Date: Tue, 24 Jan 2017 15:40:06 +0000 Subject: [PATCH 3/7] Add TODO items. --- docs/source/tutorial/Introduction.lhs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index beffa93..6455e3c 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -149,3 +149,11 @@ forcing one type to become another type, it's actually type-safe because we use a *type-index* to pick the correct type from the union. Using e.g. `unionValue @HelloWorld handler` will not compile because `HelloWorld` is not in the union. + +## Enums, Lists + +TODO + +## What next? + +TODO From 89f0f1222573d2af7e705a8222a182b2161e698f Mon Sep 17 00:00:00 2001 From: Tom Hunger Date: Tue, 24 Jan 2017 18:02:48 +0000 Subject: [PATCH 4/7] Document running of queries. --- docs/source/tutorial/Introduction.lhs | 40 ++++++++++++++++++++++----- docs/source/tutorial/package.yaml | 4 +++ docs/source/tutorial/tutorial.cabal | 4 ++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index 6455e3c..a462508 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -13,7 +13,7 @@ module Introduction where import Protolude import GraphQL -import GraphQL.API (Object, Field, Argument, (:>), Union, Enum) +import GraphQL.API (Object, Field, Argument, (:>), Union) import GraphQL.Resolver (Handler, (:<>)(..), unionValue) ``` @@ -84,10 +84,10 @@ called birdface, is based on the operator for monoids, `<>`. ``` haskell calculator :: Handler IO Calculator -calculator = pure (add :<> subtract) +calculator = pure (add :<> subtract') where add a b = pure (a + b) - subtract a b = pure (a - b) + subtract' a b = pure (a - b) ``` Note that we still need `pure` for each individual handler. @@ -150,10 +150,36 @@ because we use a *type-index* to pick the correct type from the union. Using e.g. `unionValue @HelloWorld handler` will not compile because `HelloWorld` is not in the union. -## Enums, Lists +## Running a query -TODO +```haskell +hello :: IO Response +hello = interpretAnonymousQuery @HelloWorld handler "{ me(greeting: \"hey ho\") }" +``` + +But our output is pretty long: + +``` +λ hello +Success (Object' (OrderedMap {keys = [Name {unName = "me"}], toMap = fromList [(Name {unName = "me"},ValueScalar' (ConstString (String "hey ho to me")))]})) +``` + +The output object `Object'` has a `ToJSON` instance: -## What next? +``` +λ map (\(Success o) -> Aeson.encode o) hello +"{\"me\":\"hey ho to me\"}" +``` + + +## Where next? -TODO +We have an +[examples](https://github.com/jml/graphql-api/tree/master/tests/Examples) +directory showing full code examples. + +If you want to try the examples in this tutorial you can run: + +```bash +stack repl tutorial +``` diff --git a/docs/source/tutorial/package.yaml b/docs/source/tutorial/package.yaml index ef0b642..448c683 100644 --- a/docs/source/tutorial/package.yaml +++ b/docs/source/tutorial/package.yaml @@ -7,6 +7,9 @@ maintainer: tehunger@gmail.com, Jonathan M. Lange ghc-options: -Wall -pgmL markdown-unlit +default-extensions: + - NoImplicitPrelude + library: exposed-modules: - Introduction @@ -15,3 +18,4 @@ library: - protolude - graphql-api - markdown-unlit >= 0.4 + - aeson \ No newline at end of file diff --git a/docs/source/tutorial/tutorial.cabal b/docs/source/tutorial/tutorial.cabal index 9038e94..bee574c 100644 --- a/docs/source/tutorial/tutorial.cabal +++ b/docs/source/tutorial/tutorial.cabal @@ -1,4 +1,4 @@ --- This file has been generated from package.yaml by hpack version 0.14.1. +-- This file has been generated from package.yaml by hpack version 0.15.0. -- -- see: https://github.com/sol/hpack @@ -12,6 +12,7 @@ build-type: Simple cabal-version: >= 1.10 library + default-extensions: NoImplicitPrelude exposed-modules: Introduction other-modules: @@ -21,5 +22,6 @@ library , protolude , graphql-api , markdown-unlit >= 0.4 + , aeson default-language: Haskell2010 ghc-options: -Wall -pgmL markdown-unlit From c463415d32acaa14acfbcdb9e4d443f6cecde383 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 29 Jan 2017 18:02:03 +0000 Subject: [PATCH 5/7] Substantial revision to the tutorial --- docs/source/tutorial/Introduction.lhs | 270 ++++++++++++++++++-------- docs/source/tutorial/package.yaml | 3 +- docs/source/tutorial/tutorial.cabal | 1 + src/GraphQL/Internal/Output.hs | 4 + 4 files changed, 201 insertions(+), 77 deletions(-) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index a462508..068fe0e 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -12,63 +12,146 @@ module Introduction where import Protolude +import System.Random + import GraphQL import GraphQL.API (Object, Field, Argument, (:>), Union) import GraphQL.Resolver (Handler, (:<>)(..), unionValue) ``` -The core idea for this library is that we define a composite type that -specifies the whole API, and then implement a matching handler. +## A simple GraphQL service -The main GraphQL entities we care about are Objects and Fields. Each -Field can have arguments. +A [GraphQL](http://graphql.org/) service is made up of two things: -``` haskell -type HelloWorld = Object "HelloWorld" '[] - '[ Argument "greeting" Text :> Field "me" Text - ] -``` + 1. A schema that defines the service + 2. Some code that implements the service's behavior -The example above is equivalent to the following GraphQL type: +We're going to build a very simple service that says hello to +people. Our GraphQL schema for this looks like: +```graphql +type Hello { + greeting(who: String!): String! +} ``` -type HelloWorld { - me(greeting: String!): String! + +Which means we have base type, an _object_ called `Hello`, which has a single +_field_ `greeting`, which takes a non-nullable `String` called `who` and +returns a `String`. + +And we want to be able to send queries that look like: + +```graphql +{ + greeting(who: "world") } ``` -And if we had a code to handle that type (more later) we could query it like this: +And get responses like: +```json +{ + data: { + greeting: "Hello world!" + } +} ``` -{ me(greeting: "hello") } + +### Defining the schema + +Here's how we would define the schema in Haskell: + +```haskell +type Hello = Object "Hello" '[] + '[ Argument "who" Text :> Field "greeting" Text + ] ``` -## Implementing a handler +Breaking this down, we define a new Haskell type `Hello`, which is a GraphQL +object (also named `"Hello"`) that implements no interfaces (hence `'[]`). It +has one field, called `"greeting"` which returns some `Text` and takes a +single named argument `"who"`, which is also `Text`. + +There are some noteworthy differences between this schema and the GraphQL +schema: + +* The GraphQL schema requires a special annotation to say that a value cannot + be null, `!`. In Haskell, we instead assume that everything can't be null. +* In the GraphQL schema, the argument appears *after* the field name. In + Haskell, it appears *before*. +* In Haskell, we name the top-level type twice, once on left hand side of the + type definition and once on the right. + +### Implementing the handlers + +Once we have the schema, we need to define the corresponding handlers, which +are `Handler` values. -We define a corresponding handler via the `Handler m a` which takes -the monad to run in (`IO` in this case) and the actual API definition -(`HelloWorld`): +Here's a `Handler` for `Hello`: ```haskell -handler :: Handler IO HelloWorld -handler = pure (\greeting -> pure (greeting <> " to me")) +hello :: Handler IO Hello +hello = pure greeting + where + greeting who = pure ("Hello " <> who <> "!") ``` -The implementation looks slightly weird, but it's weird for good -reasons. In order: +The type signature, `Handler IO Hello` shows that it's a `Handler` for +`Hello`, and that it runs in the `IO` monad. (Note: nothing about this example +code requires the `IO` monad, it's just a monad that lots of people has heard +of.) + +The implementation looks slightly weird, but it's weird for good reasons. + +The first layer of the handler, `pure greeting`, produces the `Hello` object. +The `pure` might seem redundant here, but making this step monadic allows us +to run actions in the base monad. + +The second layer of the handler, the implementation of `greeting`, produces +the value of the `greeting` field. It is monadic so that it will only be +executed when the field was requested. + +Each handler is a separate monadic action so we only perform the side effects +for fields present in the query. -* The first `pure` allows us to run actions in the base monad (here `IO` - before returning anything. This is useful to allocate a resource -like a database connection. -* The `pure` in the function call allows us to **avoid running -actions** when the field hasn't been requested: Each handler is a -separate monadic action so we only perform the side effects for fields -present in the query. +This handler is in `Identity` because it doesn't do anything particularly +monadic. It could be in `IO` or `STM` or `ExceptT Text IO` or whatever you +would like. + +### Running queries + +Defining a service isn't much point unless you can query. Here's how: + +```haskell +queryHello :: IO Response +queryHello = interpretAnonymousQuery @Hello hello "{ greeting(who: \"mort\") }" +``` +The actual `Response` type is fairly big, so we're most likely to turn it into +JSON: + +``` +λ Aeson.encode <$> queryHello +"{\"greeting\":\"Hello mort!\"}" +``` ## Combining field handlers with :<> -Let's implement a simple calculator that can add and subtract integers: +How do we define an object with more than one field? + +Let's implement a simple calculator that can add and subtract integers. First, +the schema: + +```graphql +type Calculator { + add(a: Int!, b: Int!): Int!, + sub(a: Int!, b: Int!): Int!, +} +``` + +Here, `Calculator` is an object with two fields: `add` and `sub`. + +And now the Haskell version: ``` haskell type Calculator = Object "Calculator" '[] @@ -77,100 +160,129 @@ type Calculator = Object "Calculator" '[] ] ``` -Every element in a list in Haskell has the same type, so we can't -really return a list of different handlers. Instead we compose the -different handlers with a new operator, `:<>`. This operator, commonly -called birdface, is based on the operator for monoids, `<>`. +So far, this is the same as our `Hello` example. -``` haskell +And its handler: + +```haskell calculator :: Handler IO Calculator calculator = pure (add :<> subtract') where - add a b = pure (a + b) - subtract' a b = pure (a - b) + add a b = pure (a + b) + subtract' a b = pure (a - b) ``` -Note that we still need `pure` for each individual handler. +This handler introduces a new operator, `:<>` (pronounced "birdface"), which +is used to compose two existing handlers into a new handler. It's inspired by +the operator for monoids, `<>`. +Note that we still need `pure` for each individual handler. ## Nesting Objects -Objects can be used as a type in fields. This allows us to implement a -server for the classic GraphQL example query: +How do we define objects made up other objects? +One of the great things in GraphQL is that objects can be used as types for +fields. Take this classic GraphQL schema as an example: +```graphql +type Query { + me: User! +} + +type User { + name: Text! +} ``` + +We would query this schema with something like: + +```graphql { - me { name } + me { + name + } } ``` -The Haskell schema for that looks like this: +Which would produce output like: -``` haskell +```json +{ + data: { + me: { + name: "Mort" + } + } +} +``` + +The Haskell type for this schema looks like: + +```haskell type User = Object "User" '[] '[Field "name" Text] type Query = Object "Query" '[] '[Field "me" User] ``` -Note the type `User` for `me`. - +Note that `Query` refers to the type `User` when it defines the field `me`. We write nested handlers the same way we write the top-level handler: -``` haskell +```haskell user :: Handler IO User -user = pure (pure "mort") +user = pure name + where + name = pure "Mort" query :: Handler IO Query query = pure user ``` +And that's it. + ## Unions -Union handlers require special treatment in Haskell because we need to -return the same type for each possible, different type in the union. +GraphQL has [support for union +types](http://graphql.org/learn/schema/#union-types). These require special +treatment in Haskell. -Let's define a union: +Let's define a union, first in GraphQL: -``` haskell -type UserOrCalcualtor = Union "UserOrCalcualtor" '[User, Calculator] -type UnionQuery = Object "UnionQuery" '[] '[Field "union" UserOrCalcualtor] +```graphql +union UserOrCalculator = User | Calculator ``` -and a handler that returns a user: +And now in Haskell: -``` haskell -unionQuery :: Handler IO UnionQuery -unionQuery = pure (unionValue @User user) +```haskell +type UserOrCalcualtor = Union "UserOrCalcualtor" '[User, Calculator] ``` -Note that, while `unionValue` looks a bit like `unsafeCoerce` by -forcing one type to become another type, it's actually type-safe -because we use a *type-index* to pick the correct type from the -union. Using e.g. `unionValue @HelloWorld handler` will not compile -because `HelloWorld` is not in the union. - -## Running a query +And let's define a very simple top-level object that uses `UserOrCalcualtor`: ```haskell -hello :: IO Response -hello = interpretAnonymousQuery @HelloWorld handler "{ me(greeting: \"hey ho\") }" +type UnionQuery = Object "UnionQuery" '[] '[Field "union" UserOrCalcualtor] ``` -But our output is pretty long: +and a handler that randomly returns either a user or a calculator: -``` -λ hello -Success (Object' (OrderedMap {keys = [Name {unName = "me"}], toMap = fromList [(Name {unName = "me"},ValueScalar' (ConstString (String "hey ho to me")))]})) +```haskell +unionQuery :: Handler IO UnionQuery +unionQuery = do + returnUser <- randomIO + if returnUser + then pure (unionValue @User user) + else pure (unionValue @Calculator calculator) ``` -The output object `Object'` has a `ToJSON` instance: - -``` -λ map (\(Success o) -> Aeson.encode o) hello -"{\"me\":\"hey ho to me\"}" -``` +The important thing here is that we have to wrap the actual objects we return +using `unionValue`. +Note that while `unionValue` looks a bit like `unsafeCoerce` by forcing one +type to become another type, it's actually type-safe because we use a +*type-index* to pick the correct type from the union. Using e.g. `unionValue +@HelloWorld handler` will not compile because `HelloWorld` is not in the +union. ## Where next? @@ -178,6 +290,12 @@ We have an [examples](https://github.com/jml/graphql-api/tree/master/tests/Examples) directory showing full code examples. +We also have a fair number of [end-to-end +tests](https://github.com/jml/graphql-api/tree/master/tests/EndToEndTests.hs) +based on an [example +schema](https://github.com/jml/graphql-api/tree/master/tests/ExampleSchema.hs) +that you might find interesting. + If you want to try the examples in this tutorial you can run: ```bash diff --git a/docs/source/tutorial/package.yaml b/docs/source/tutorial/package.yaml index 448c683..c11f65f 100644 --- a/docs/source/tutorial/package.yaml +++ b/docs/source/tutorial/package.yaml @@ -17,5 +17,6 @@ library: - base >= 4.9 && < 5 - protolude - graphql-api + - random - markdown-unlit >= 0.4 - - aeson \ No newline at end of file + - aeson diff --git a/docs/source/tutorial/tutorial.cabal b/docs/source/tutorial/tutorial.cabal index bee574c..6f3ded8 100644 --- a/docs/source/tutorial/tutorial.cabal +++ b/docs/source/tutorial/tutorial.cabal @@ -21,6 +21,7 @@ library base >= 4.9 && < 5 , protolude , graphql-api + , random , markdown-unlit >= 0.4 , aeson default-language: Haskell2010 diff --git a/src/GraphQL/Internal/Output.hs b/src/GraphQL/Internal/Output.hs index 59734fc..c49eded 100644 --- a/src/GraphQL/Internal/Output.hs +++ b/src/GraphQL/Internal/Output.hs @@ -11,6 +11,7 @@ module GraphQL.Internal.Output ) where import Protolude hiding (Location, Map) +import Data.Aeson (ToJSON(..)) import Data.List.NonEmpty (NonEmpty(..)) import GraphQL.Value ( Object @@ -75,6 +76,9 @@ instance ToValue Response where ,("errors", toValue e) ] +instance ToJSON Response where + toJSON = toJSON . toValue + type Errors = NonEmpty Error data Error = Error Text [Location] deriving (Eq, Ord, Show) From 5a2a6917c54a9045e443fbf453bc1f73b26828da Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 29 Jan 2017 18:22:43 +0000 Subject: [PATCH 6/7] Review feedback --- docs/source/tutorial/Introduction.lhs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index 068fe0e..2ba85b5 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -39,6 +39,9 @@ Which means we have base type, an _object_ called `Hello`, which has a single _field_ `greeting`, which takes a non-nullable `String` called `who` and returns a `String`. +Note that all the types here are GraphQL types, not Haskell types. `String` +here is a GraphQL `String`, not a Haskell one. + And we want to be able to send queries that look like: ```graphql @@ -72,11 +75,14 @@ object (also named `"Hello"`) that implements no interfaces (hence `'[]`). It has one field, called `"greeting"` which returns some `Text` and takes a single named argument `"who"`, which is also `Text`. +Note that the GraphQL `String` from above got translated into a Haskell +`Text`. + There are some noteworthy differences between this schema and the GraphQL schema: * The GraphQL schema requires a special annotation to say that a value cannot - be null, `!`. In Haskell, we instead assume that everything can't be null. + be null, `!`. In Haskell, we instead assume that nothing can be null. * In the GraphQL schema, the argument appears *after* the field name. In Haskell, it appears *before*. * In Haskell, we name the top-level type twice, once on left hand side of the @@ -111,8 +117,8 @@ The second layer of the handler, the implementation of `greeting`, produces the value of the `greeting` field. It is monadic so that it will only be executed when the field was requested. -Each handler is a separate monadic action so we only perform the side effects -for fields present in the query. +Each field handler is a separate monadic action so we only perform the side +effects for fields present in the query. This handler is in `Identity` because it doesn't do anything particularly monadic. It could be in `IO` or `STM` or `ExceptT Text IO` or whatever you @@ -127,8 +133,8 @@ queryHello :: IO Response queryHello = interpretAnonymousQuery @Hello hello "{ greeting(who: \"mort\") }" ``` -The actual `Response` type is fairly big, so we're most likely to turn it into -JSON: +The actual `Response` type is fairly verbose, so we're most likely to turn it +into JSON: ``` λ Aeson.encode <$> queryHello @@ -255,13 +261,13 @@ union UserOrCalculator = User | Calculator And now in Haskell: ```haskell -type UserOrCalcualtor = Union "UserOrCalcualtor" '[User, Calculator] +type UserOrCalculator = Union "UserOrCalculator" '[User, Calculator] ``` -And let's define a very simple top-level object that uses `UserOrCalcualtor`: +And let's define a very simple top-level object that uses `UserOrCalculator`: ```haskell -type UnionQuery = Object "UnionQuery" '[] '[Field "union" UserOrCalcualtor] +type UnionQuery = Object "UnionQuery" '[] '[Field "union" UserOrCalculator] ``` and a handler that randomly returns either a user or a calculator: From 0768c380e28c0f538de4a6df7b4f3dc2e11cfc74 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Sun, 29 Jan 2017 18:27:06 +0000 Subject: [PATCH 7/7] Correct JSON --- docs/source/tutorial/Introduction.lhs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/tutorial/Introduction.lhs b/docs/source/tutorial/Introduction.lhs index 2ba85b5..1b609cd 100644 --- a/docs/source/tutorial/Introduction.lhs +++ b/docs/source/tutorial/Introduction.lhs @@ -54,8 +54,8 @@ And get responses like: ```json { - data: { - greeting: "Hello world!" + "data": { + "greeting": "Hello world!" } } ``` @@ -215,9 +215,9 @@ Which would produce output like: ```json { - data: { - me: { - name: "Mort" + "data": { + "me": { + "name": "Mort" } } }