diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a1441f5..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -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 -``` diff --git a/README.md b/README.md index 74c0ee3..aa83027 100644 --- a/README.md +++ b/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. diff --git a/docs/tutorial/test.hs b/docs/tutorial/test.hs deleted file mode 100644 index 631407c..0000000 --- a/docs/tutorial/test.hs +++ /dev/null @@ -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 - diff --git a/docs/tutorial/tutorial.lhs b/docs/tutorial/tutorial.lhs deleted file mode 100644 index 1024251..0000000 --- a/docs/tutorial/tutorial.lhs +++ /dev/null @@ -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 diff --git a/graphql.cabal b/graphql.cabal index bdd7532..337e397 100644 --- a/graphql.cabal +++ b/graphql.cabal @@ -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 diff --git a/package.yaml b/package.yaml index 3e84a76..b050492 100644 --- a/package.yaml +++ b/package.yaml @@ -23,9 +23,7 @@ license-file: - LICENSE.MPL extra-source-files: - CHANGELOG.md -- CONTRIBUTING.md - README.md -- docs/tutorial/tutorial.lhs dependencies: - aeson