Write documentation out of the source tree
In a Wiki.
This commit is contained in:
parent
6e8d8a34a1
commit
afcf9aaa14
@ -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
|
||||
|
||||
[![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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user