From b74278cd19d900d1397e35b85f7b80d70cd574f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matth=C3=ADas=20P=C3=A1ll=20Gissurarson?= Date: Wed, 9 Mar 2016 01:07:57 +0100 Subject: [PATCH] Added a tutorial, based on graphql-js and servant documentation. --- docs/tutorial/Makefile | 4 + docs/tutorial/tutorial.css | 3 + docs/tutorial/tutorial.lhs | 150 +++++++++++++++++++++++++++++++ docs/tutorial/tutorial.rst | 176 +++++++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+) create mode 100644 docs/tutorial/Makefile create mode 100644 docs/tutorial/tutorial.css create mode 100644 docs/tutorial/tutorial.lhs create mode 100644 docs/tutorial/tutorial.rst diff --git a/docs/tutorial/Makefile b/docs/tutorial/Makefile new file mode 100644 index 0000000..04d8d71 --- /dev/null +++ b/docs/tutorial/Makefile @@ -0,0 +1,4 @@ +default: + pandoc -f markdown+lhs+yaml_metadata_block --highlight-style=haddock -S -c "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" --section-divs -c tutorial.css --toc --standalone -t html5 -o tutorial.html tutorial.lhs + pandoc -f markdown+lhs+yaml_metadata_block --highlight-style=haddock --toc --standalone -t rst -o tutorial.rst tutorial.lhs + pandoc -f markdown+lhs+yaml_metadata_block --highlight-style=haddock --toc --standalone -t latex -o tutorial.pdf tutorial.lhs diff --git a/docs/tutorial/tutorial.css b/docs/tutorial/tutorial.css new file mode 100644 index 0000000..831b73d --- /dev/null +++ b/docs/tutorial/tutorial.css @@ -0,0 +1,3 @@ +body { + padding: 0 20px; +} diff --git a/docs/tutorial/tutorial.lhs b/docs/tutorial/tutorial.lhs new file mode 100644 index 0000000..387d14d --- /dev/null +++ b/docs/tutorial/tutorial.lhs @@ -0,0 +1,150 @@ +--- +title: GraphQL Haskell Tutorial +--- + + +== Getting started == + +Welcome to graphql-haskell! + +We have written a small tutorial to help you (and ourselves) understand the graphql package. + +Since this file is a literate haskell file, we start by importing some dependencies. + +> {-# LANGUAGE OverloadedStrings #-} +> {-# LANGUAGE LambdaCase #-} +> module Main where +> +> import Prelude hiding (empty, putStrLn) +> import Data.GraphQL +> import Data.GraphQL.Schema +> import qualified Data.GraphQL.Schema as Schema +> +> import Control.Applicative +> import Data.Text hiding (empty) +> import Data.Aeson +> import Data.ByteString.Lazy.Char8 (putStrLn) +> +> import Data.Time +> +> import Debug.Trace + +=== First example === + +Now, as our first example, we are going to look at the +example from [graphql.js](https://github.com/graphql/graphql-js). + +First we build a GraphQL schema. + +> schema1 :: Alternative f => Schema f +> schema1 = Schema [hello] +> +> hello :: Alternative f => Resolver f +> hello = Schema.scalar "hello" ("it's me" :: Text) + +This defines a simple schema with one type and one field, that resolves to a fixed value. + +Next we define our query. + +> query1 :: Text +> query1 = "{ hello }" + + +To run the query, we call the `graphql` with the schema and the query. + +> main1 :: IO () +> main1 = putStrLn =<< encode <$> graphql schema1 query1 + +This runs the query by fetching the one field defined, +returning + +```{"data" : {"hello":"it's me"}}``` + + + +=== Monadic actions === + +For this example, we're going to be using time. + +> schema2 :: Schema IO +> schema2 = Schema [time] +> +> time :: Resolver IO +> time = Schema.scalarA "time" $ \case +> [] -> do t <- getCurrentTime +> return $ show t +> _ -> empty + +This defines a simple schema with one type and one field, +which resolves to the current time. + +Next we define our query. + +> query2 :: Text +> query2 = "{ time }" +> +> main2 :: IO () +> main2 = putStrLn =<< encode <$> graphql schema2 query2 + +This runs the query, returning the current time + +```{"data": {"time":"2016-03-08 23:28:14.546899 UTC"}}``` + + +=== Errors === + +Errors are handled according to the spec, +with fields that cause erros being resolved to `null`, +and an error being added to the error list. + +An example of this is the following query: + +> queryShouldFail :: Text +> queryShouldFail = "{ boyhowdy }" + +Since there is no `boyhowdy` field in our schema, it will not resolve, +and the query will fail, as we can see in the following example. + +> mainShouldFail :: IO () +> mainShouldFail = do +> r <- graphql schema1 query1 +> putStrLn $ encode r +> putStrLn "This will fail" +> r <- graphql schema1 queryShouldFail +> putStrLn $ encode r +> + +This outputs: + +``` +{"data": {"hello": "it's me"}} +This will fail +{"data": {"boyhowdy": null}, "errors":[{"message": "the field boyhowdy did not resolve."}]} +``` + +=== Combining resolvers === + +Now that we have two resolvers, we can define a schema which uses them both. + +> schema3 :: Schema IO +> schema3 = Schema [hello, time] +> +> query3 :: Text +> query3 = "query timeAndHello { time hello }" +> +> main3 :: IO () +> main3 = putStrLn =<< encode <$> graphql schema3 query3 + +This queries for both time and hello, returning + +```{ "data": {"hello":"it's me","time":"2016-03-08 23:29:11.62108 UTC"}}``` + +Notice that we can name our queries, as we did with `timeAndHello`. Since we have only been using single queries, we can use the shorthand `{ time hello}`, as we have been doing in the previous examples. + +In GraphQL there can only be one operation per query. + + +== Further examples == + +More examples on queries and a more complex schema can be found in the test directory, +in the [Test.StarWars](../../tests/Test/StarWars) module. This includes a more complex schema, and more complex queries. diff --git a/docs/tutorial/tutorial.rst b/docs/tutorial/tutorial.rst new file mode 100644 index 0000000..1c8b5ff --- /dev/null +++ b/docs/tutorial/tutorial.rst @@ -0,0 +1,176 @@ +======================== +GraphQL Haskell Tutorial +======================== + +.. contents:: + :depth: 3 +.. + +Getting started +=============== + +Welcome to graphql-haskell! + +We have written a small tutorial to help you (and ourselves) understand +the graphql package. + +Since this file is a literate haskell file, we start by importing some +dependencies. + +.. code:: haskell + + {-# LANGUAGE OverloadedStrings #-} + {-# LANGUAGE LambdaCase #-} + module Main where + + import Prelude hiding (empty, putStrLn) + import Data.GraphQL + import Data.GraphQL.Schema + import qualified Data.GraphQL.Schema as Schema + + import Control.Applicative + import Data.Text hiding (empty) + import Data.Aeson + import Data.ByteString.Lazy.Char8 (putStrLn) + + import Data.Time + + import Debug.Trace + +First example +------------- + +Now, as our first example, we are going to look at the example from +`graphql.js `__. + +First we build a GraphQL schema. + +.. code:: haskell + + schema1 :: Alternative f => Schema f + schema1 = Schema [hello] + + hello :: Alternative f => Resolver f + hello = Schema.scalar "hello" ("it's me" :: Text) + +This defines a simple schema with one type and one field, that resolves +to a fixed value. + +Next we define our query. + +.. code:: haskell + + query1 :: Text + query1 = "{ hello }" + +To run the query, we call the ``graphql`` with the schema and the query. + +.. code:: haskell + + main1 :: IO () + main1 = putStrLn =<< encode <$> graphql schema1 query1 + +This runs the query by fetching the one field defined, returning + +``{"data" : {"hello":"it's me"}}`` + +Monadic actions +--------------- + +For this example, we're going to be using time. + +.. code:: haskell + + schema2 :: Schema IO + schema2 = Schema [time] + + time :: Resolver IO + time = Schema.scalarA "time" $ \case + [] -> do t <- getCurrentTime + return $ show t + _ -> empty + +This defines a simple schema with one type and one field, which resolves +to the current time. + +Next we define our query. + +.. code:: haskell + + query2 :: Text + query2 = "{ time }" + + main2 :: IO () + main2 = putStrLn =<< encode <$> graphql schema2 query2 + +This runs the query, returning the current time + +``{"data": {"time":"2016-03-08 23:28:14.546899 UTC"}}`` + +Errors +------ + +Errors are handled according to the spec, with fields that cause erros +being resolved to ``null``, and an error being added to the error list. + +An example of this is the following query: + +.. code:: haskell + + queryShouldFail :: Text + queryShouldFail = "{ boyhowdy }" + +Since there is no ``boyhowdy`` field in our schema, it will not resolve, +and the query will fail, as we can see in the following example. + +.. code:: haskell + + mainShouldFail :: IO () + mainShouldFail = do + r <- graphql schema1 query1 + putStrLn $ encode r + putStrLn "This will fail" + r <- graphql schema1 queryShouldFail + putStrLn $ encode r + +This outputs: + +:: + + {"data": {"hello": "it's me"}} + This will fail + {"data": {"boyhowdy": null}, "errors":[{"message": "the field boyhowdy did not resolve."}]} + +Combining resolvers +------------------- + +Now that we have two resolvers, we can define a schema which uses them +both. + +.. code:: haskell + + schema3 :: Schema IO + schema3 = Schema [hello, time] + + query3 :: Text + query3 = "query timeAndHello { time hello }" + + main3 :: IO () + main3 = putStrLn =<< encode <$> graphql schema3 query3 + +This queries for both time and hello, returning + +``{ "data": {"hello":"it's me","time":"2016-03-08 23:29:11.62108 UTC"}}`` + +Notice that we can name our queries, as we did with ``timeAndHello``. +Since we have only been using single queries, we can use the shorthand +``{ time hello}``, as we have been doing in the previous examples. + +In GraphQL there can only be one operation per query. + +Further examples +================ + +More examples on queries and a more complex schema can be found in the +test directory, in the `Test.StarWars <../../tests/Test/StarWars>`__ +module. This includes a more complex schema, and more complex queries.