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 | ||||
|  | ||||
| [](https://www.simplehaskell.org) | ||||
| [](https://build.caraus.tech/go/pipelines) | ||||
|  | ||||
| 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 | ||||
| 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 | ||||
| [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 | ||||
| -- | ||||
| -- hash: 64b8d806f87030d33d1f8d505887d4913689ee48bc6e79b1c24b5226ffd07ee8 | ||||
| -- hash: ddb79ddbd13b917f320fff372b4a29b63b6eb0ed113ca732c1d779b4e6a296d8 | ||||
|  | ||||
| name:           graphql | ||||
| version:        0.10.0.0 | ||||
| @@ -25,9 +25,7 @@ license-files:  LICENSE, | ||||
| build-type:     Simple | ||||
| extra-source-files: | ||||
|     CHANGELOG.md | ||||
|     CONTRIBUTING.md | ||||
|     README.md | ||||
|     docs/tutorial/tutorial.lhs | ||||
|  | ||||
| source-repository head | ||||
|   type: git | ||||
|   | ||||
| @@ -23,9 +23,7 @@ license-file: | ||||
| - LICENSE.MPL | ||||
| extra-source-files: | ||||
| - CHANGELOG.md | ||||
| - CONTRIBUTING.md | ||||
| - README.md | ||||
| - docs/tutorial/tutorial.lhs | ||||
|  | ||||
| dependencies: | ||||
| - aeson | ||||
|   | ||||
		Reference in New Issue
	
	Block a user