Write documentation out of the source tree

In a Wiki.
This commit is contained in:
Eugen Wissner 2020-11-02 08:24:48 +01:00
parent 6e8d8a34a1
commit afcf9aaa14
6 changed files with 4 additions and 340 deletions

View File

@ -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
View File

@ -1,5 +1,8 @@
# GraphQL implementation in Haskell
[![Simple Haskell](https://www.simplehaskell.org/badges/badge.svg)](https://www.simplehaskell.org)
[![CI/CD](https://img.shields.io/badge/CI-CD-brightgreen)](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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -23,9 +23,7 @@ license-file:
- LICENSE.MPL
extra-source-files:
- CHANGELOG.md
- CONTRIBUTING.md
- README.md
- docs/tutorial/tutorial.lhs
dependencies:
- aeson