forked from OSS/graphql
		
	Write documentation out of the source tree
In a Wiki.
This commit is contained in:
		| @@ -1,31 +0,0 @@ | |||||||
| # Contributing guidelines |  | ||||||
|  |  | ||||||
| ## Testing |  | ||||||
|  |  | ||||||
| To ensure all code changes adhere to existing code quality standards, some |  | ||||||
| automatic checks can be run locally. |  | ||||||
|  |  | ||||||
| Ensure that the code builds without warnings and passes the tests: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| stack test --pedantic |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| And also run the linter on your code: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| stack build hlint |  | ||||||
| stack exec hlint -- src tests |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Build the documentation and check if you get any warnings: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| stack haddock |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Validate that literate Haskell (tutorials) files compile without any warnings: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| stack ghc -- -Wall -fno-code docs/tutorial/*.lhs |  | ||||||
| ``` |  | ||||||
							
								
								
									
										103
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,5 +1,8 @@ | |||||||
| # GraphQL implementation in Haskell | # GraphQL implementation in Haskell | ||||||
|  |  | ||||||
|  | [](https://www.simplehaskell.org) | ||||||
|  | [](https://build.caraus.tech/go/pipelines) | ||||||
|  |  | ||||||
| This implementation is relatively low-level by design, it doesn't provide any | This implementation is relatively low-level by design, it doesn't provide any | ||||||
| mappings between the GraphQL types and Haskell's type system and avoids | mappings between the GraphQL types and Haskell's type system and avoids | ||||||
| compile-time magic. It focuses on flexibility instead, so other solutions can | compile-time magic. It focuses on flexibility instead, so other solutions can | ||||||
| @@ -29,103 +32,3 @@ API documentation is available through | |||||||
|  |  | ||||||
| Further documentation will be made available in the | Further documentation will be made available in the | ||||||
| [Wiki](https://www.caraus.tech/projects/pub-graphql/wiki). | [Wiki](https://www.caraus.tech/projects/pub-graphql/wiki). | ||||||
|  |  | ||||||
| ### 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 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. |  | ||||||
| citeSchema :: Schema IO |  | ||||||
| citeSchema = schema queryType Nothing Nothing mempty |  | ||||||
|  |  | ||||||
| -- 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 citeSchema "{ cite }" |  | ||||||
|     ByteString.Lazy.Char8.putStrLn $ Aeson.encode result |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Executing this query produces the following JSON: |  | ||||||
|  |  | ||||||
| ```json |  | ||||||
| { |  | ||||||
|   "data": { |  | ||||||
|     "cite": "Piscis primum a capite foetat" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Contact |  | ||||||
|  |  | ||||||
| Suggestions, patches and bug reports are welcome. |  | ||||||
|  |  | ||||||
| Should you have questions on usage, please open an issue and ask – this helps |  | ||||||
| to write useful documentation. |  | ||||||
|   | |||||||
| @@ -1,55 +0,0 @@ | |||||||
| {-# LANGUAGE OverloadedStrings #-} |  | ||||||
|  |  | ||||||
| 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. |  | ||||||
| citeSchema :: Schema IO |  | ||||||
| citeSchema = schema queryType |  | ||||||
|  |  | ||||||
| -- 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 citeSchema "{ cite }" |  | ||||||
|     ByteString.Lazy.Char8.putStrLn $ Aeson.encode result |  | ||||||
|  |  | ||||||
| @@ -1,149 +0,0 @@ | |||||||
| --- |  | ||||||
| title: GraphQL Haskell Tutorial |  | ||||||
| --- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| == Getting started == |  | ||||||
|  |  | ||||||
| Welcome to GraphQL! |  | ||||||
|  |  | ||||||
| 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 #-} |  | ||||||
| > module Main where |  | ||||||
| > |  | ||||||
| > import Control.Monad.IO.Class (liftIO) |  | ||||||
| > import Data.Aeson (encode) |  | ||||||
| > import Data.ByteString.Lazy.Char8 (putStrLn) |  | ||||||
| > import qualified Data.HashMap.Strict as HashMap |  | ||||||
| > import Data.Text (Text) |  | ||||||
| > import qualified Data.Text as Text |  | ||||||
| > import Data.Time (getCurrentTime) |  | ||||||
| > |  | ||||||
| > import Language.GraphQL |  | ||||||
| > import Language.GraphQL.Type |  | ||||||
| > import qualified Language.GraphQL.Type.Out as Out |  | ||||||
| > |  | ||||||
| > import Prelude hiding (putStrLn) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| === 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 :: Schema IO |  | ||||||
| > schema1 = schema queryType Nothing Nothing mempty |  | ||||||
| > |  | ||||||
| > queryType :: ObjectType IO |  | ||||||
| > queryType = ObjectType "Query" Nothing [] |  | ||||||
| >   $ HashMap.singleton "hello" |  | ||||||
| >   $ ValueResolver helloField hello |  | ||||||
| > |  | ||||||
| > helloField :: Field IO |  | ||||||
| > helloField = Field Nothing (Out.NamedScalarType string) mempty |  | ||||||
| > |  | ||||||
| > hello :: Resolve IO |  | ||||||
| > hello = pure $ String "it's me" |  | ||||||
|  |  | ||||||
| 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 = graphql schema1 query1 |  | ||||||
| >   >>= either (const $ pure ()) (putStrLn . encode) |  | ||||||
|  |  | ||||||
| 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 queryType2 Nothing Nothing mempty |  | ||||||
| > |  | ||||||
| > queryType2 :: ObjectType IO |  | ||||||
| > queryType2 = ObjectType "Query" Nothing [] |  | ||||||
| >   $ HashMap.singleton "time" |  | ||||||
| >   $ ValueResolver timeField time |  | ||||||
| > |  | ||||||
| > timeField :: Field IO |  | ||||||
| > timeField = Field Nothing (Out.NamedScalarType string) mempty |  | ||||||
| > |  | ||||||
| > time :: Resolve IO |  | ||||||
| > time = do |  | ||||||
| >   t <- liftIO getCurrentTime |  | ||||||
| >   pure $ String $ Text.pack $ show t |  | ||||||
|  |  | ||||||
| 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 = graphql schema2 query2 |  | ||||||
| >   >>= either (const $ pure ()) (putStrLn . encode) |  | ||||||
|  |  | ||||||
| This runs the query, returning the current time |  | ||||||
|  |  | ||||||
| ```{"data": {"time":"2016-03-08 23:28:14.546899 UTC"}}``` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| === Combining resolvers === |  | ||||||
|  |  | ||||||
| Now that we have two resolvers, we can define a schema which uses them both. |  | ||||||
|  |  | ||||||
| > schema3 :: Schema IO |  | ||||||
| > schema3 = schema queryType3 Nothing Nothing mempty |  | ||||||
| > |  | ||||||
| > queryType3 :: ObjectType IO |  | ||||||
| > queryType3 = ObjectType "Query" Nothing [] $ HashMap.fromList |  | ||||||
| >   [ ("hello", ValueResolver helloField hello) |  | ||||||
| >   , ("time", ValueResolver timeField time) |  | ||||||
| >   ] |  | ||||||
| > |  | ||||||
| > query3 :: Text |  | ||||||
| > query3 = "query timeAndHello { time hello }" |  | ||||||
| > |  | ||||||
| > main3 :: IO () |  | ||||||
| > main3 = graphql schema3 query3 |  | ||||||
| >   >>= either (const $ pure ()) (putStrLn . encode) |  | ||||||
|  |  | ||||||
| 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. |  | ||||||
|  |  | ||||||
| > main :: IO () |  | ||||||
| > main = main1 >> main2 >> main3 |  | ||||||
| @@ -4,7 +4,7 @@ cabal-version: 2.2 | |||||||
| -- | -- | ||||||
| -- see: https://github.com/sol/hpack | -- see: https://github.com/sol/hpack | ||||||
| -- | -- | ||||||
| -- hash: 64b8d806f87030d33d1f8d505887d4913689ee48bc6e79b1c24b5226ffd07ee8 | -- hash: ddb79ddbd13b917f320fff372b4a29b63b6eb0ed113ca732c1d779b4e6a296d8 | ||||||
|  |  | ||||||
| name:           graphql | name:           graphql | ||||||
| version:        0.10.0.0 | version:        0.10.0.0 | ||||||
| @@ -25,9 +25,7 @@ license-files:  LICENSE, | |||||||
| build-type:     Simple | build-type:     Simple | ||||||
| extra-source-files: | extra-source-files: | ||||||
|     CHANGELOG.md |     CHANGELOG.md | ||||||
|     CONTRIBUTING.md |  | ||||||
|     README.md |     README.md | ||||||
|     docs/tutorial/tutorial.lhs |  | ||||||
|  |  | ||||||
| source-repository head | source-repository head | ||||||
|   type: git |   type: git | ||||||
|   | |||||||
| @@ -23,9 +23,7 @@ license-file: | |||||||
| - LICENSE.MPL | - LICENSE.MPL | ||||||
| extra-source-files: | extra-source-files: | ||||||
| - CHANGELOG.md | - CHANGELOG.md | ||||||
| - CONTRIBUTING.md |  | ||||||
| - README.md | - README.md | ||||||
| - docs/tutorial/tutorial.lhs |  | ||||||
|  |  | ||||||
| dependencies: | dependencies: | ||||||
| - aeson | - aeson | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user