Skip to content

Commit

Permalink
Feature/Generalized Wrap (#90)
Browse files Browse the repository at this point in the history
* update readme

* remove redundant todos

* clean api

* clean Morpheus Main Api

* Organize api

* categorize api

* move types

* enum with pure

* wrapper

* unwrap
  • Loading branch information
nalchevanidze authored Apr 25, 2019
1 parent b13f882 commit 0de6d74
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 40 deletions.
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ all your resolvers are regular haskell functions, morpheus graphql will convert

# Getting Started

Starting point in morpheus GraphQL is the definition of your API function with the morpheus interpreter. according to your query and mutation type, a GQL scheme and introspection will be generated. for simplicity, we won't define mutation, we'll just define query.
Starting point in morpheus GraphQL is the definition of your API function with the morpheus interpreter.
according to your query and mutation type a GQL scheme and introspection will be generated.
for simplicity, we won't define mutation, we'll just define query.

```haskell
gqlApi :: ByteString -> IO ByteString
Expand All @@ -21,29 +23,28 @@ gqlApi = interpreter

data Query = Query
{ user :: () ::-> User -- Field With No Arguments and IO interaction
} deriving (Show, Data, Genneric , GQLQuery)
} deriving (Genneric , GQLQuery)
```

as you can see query type is just Haskell record, we derive it with **GQLQuery** as as Graphql Query. it has only one field user with no argument **"()"** and output Type **"User"**

anotation **"::->"** is inline haskell data Type with Constructor
where in **(Either string value)**

- **string** is for error messages
- **value** value of field
notation **"::->"** is inline haskell data Type with Constructor **Resolver**

```haskell
Resolver (argument -> IO (Either String value))
```
where
- **string** is for error messages
- **value** value of field

arguments are just Haskell record with GQLArgs derivation, as default all fields are required. only field with type Maybe is optional
arguments are Haskell record with GQLArgs derivation, as default all fields are required. only field with type Maybe is optional

```haskell
-- Query Arguments
data Location = Location
{ zipCode :: Maybe Int -- Optional Argument
, name :: Text -- Required Argument
} deriving (Show, Data, Generic , GQLArgs)
} deriving (Generic , GQLArgs)
```

for the GQL object, define the data record and derive it as a **GQLKind,GQLObject**.
Expand All @@ -54,7 +55,7 @@ data User = User
{ name :: Text -- not Null Field
, email :: Maybe Text -- Nullable Field
, address :: Location ::-> Address -- Field With Arguments and IO interaction
} deriving (Show, Data, Generic, GQLKind, GQLObject)
} deriving (Typeable, Generic, GQLKind, GQLObject)
```

now we can write resolvers for your schema
Expand All @@ -78,7 +79,7 @@ resolveUser = Resolver $ const (jsonUser >>= \x -> return (buildResolverBy <$> x
}
```

for more details you cann See your Example on https://github.com/nalchevanidze/morpheus-graphql/tree/master/example
for more details you can See your Example on https://github.com/nalchevanidze/morpheus-graphql/tree/master/example

## Enum

Expand Down Expand Up @@ -139,7 +140,7 @@ data Coordinates = Coordinates

## Descriptions

if you need description for your GQL Type you can define GQL instance manualy and assign them description
if you need description for your GQL Type you can define GQL instance manually and assign them description

```haskell
data Person = Person
Expand All @@ -151,10 +152,9 @@ instance GQLKind Person where

```

# Mutation
## Mutation

```haskell

newtype Mutation = Mutation
{ createUser :: Form ::-> User
} deriving (Show, Generic, Data, GQLMutation)
Expand All @@ -173,15 +173,15 @@ gqlApi = interpreter
}
```

## Exisiting Features
# Existing Features

- Introspection
- Enum
- Scalar
- InputObject
- Mutation

## Roadmap
# Roadmap

- Medium future:
- stabile API
Expand Down
7 changes: 4 additions & 3 deletions example/src/Example/Schema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ module Example.Schema

import qualified Data.ByteString.Lazy.Char8 as B
import Data.Maybe (fromMaybe)
import Data.Morpheus ((::->) (..), EnumOf (unpackEnum), GQLRoot (..), ScalarOf (..), interpreter)
import Data.Morpheus (interpreter)
import Data.Morpheus.Kind (GQLArgs, GQLEnum, GQLInput, GQLKind (..), GQLMutation, GQLObject, GQLQuery,
GQLScalar (..))
import Data.Morpheus.Types.JSType (ScalarValue (..))
import Data.Morpheus.Wrapper ((::->) (..), EnumOf, GQLRoot (..), ScalarOf, unwrap)
import Data.Text (Text, pack)
import qualified Data.Text as T (concat)
import Data.Typeable (Typeable)
Expand Down Expand Up @@ -101,7 +102,7 @@ fetchAddress (Modulo7 x y) streetName = do
resolveAddress :: LocationByCoordinates ::-> Address
resolveAddress = Resolver res
where
res args = fetchAddress (unpackScalar $ latitude $ coordinates args) (pack $ show $ longitude $ coordinates args)
res args = fetchAddress (unwrap $ latitude $ coordinates args) (pack $ show $ longitude $ coordinates args)

addressByCityID :: CityID -> Int -> IO (Either String Address)
addressByCityID Paris code = fetchAddress (Modulo7 75 code) "Paris"
Expand All @@ -111,7 +112,7 @@ addressByCityID HH code = fetchAddress (Modulo7 20 code) "Hamburg"
resolveOffice :: M.JSONUser -> Location ::-> Address
resolveOffice _ = Resolver resolve'
where
resolve' args = addressByCityID (unpackEnum $ cityID args) (fromMaybe 101 (zipCode args))
resolve' args = addressByCityID (unwrap $ cityID args) (fromMaybe 101 (zipCode args))

resolveUser :: () ::-> User
resolveUser = Resolver $ const (M.jsonUser >>= \x -> return (buildResolverBy <$> x))
Expand Down
13 changes: 1 addition & 12 deletions morpheus/src/Data/Morpheus.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@

module Data.Morpheus
( interpreter
, GQLResponse
, (::->)(..)
, ResolveIO
, EnumOf(unpackEnum)
, GQLRoot(..)
, ScalarOf(..)
) where

import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
Expand All @@ -20,19 +14,14 @@ import Data.Morpheus.Kind.GQLQuery (GQLQuery (..))
import Data.Morpheus.Parser.Parser (parseGQL, parseLineBreaks)
import Data.Morpheus.PreProcess.PreProcess (preProcessQuery)
import Data.Morpheus.Schema.Internal.Types (TypeLib)
import Data.Morpheus.Types.Describer ((::->) (Resolver), EnumOf (unpackEnum), ScalarOf (..))
import Data.Morpheus.Types.Error (ResolveIO, failResolveIO)
import Data.Morpheus.Types.JSType (JSType)
import Data.Morpheus.Types.Query.Operator (Operator (..))
import Data.Morpheus.Types.Request (GQLRequest)
import Data.Morpheus.Types.Response (GQLResponse (..))
import Data.Morpheus.Types.Types (GQLRoot (..))
import Data.Text (pack)

data GQLRoot a b = GQLRoot
{ query :: a
, mutation :: b
}

schema :: (GQLQuery a, GQLMutation b) => a -> b -> TypeLib
schema queryRes mutationRes = mutationSchema mutationRes $ querySchema queryRes

Expand Down
2 changes: 1 addition & 1 deletion morpheus/src/Data/Morpheus/Error/Arguments.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ argumentNameCollision = map keyToError
undefinedArgument :: EnhancedKey -> GQLErrors
undefinedArgument (EnhancedKey key' position') = errorMessage position' text
where
text = T.concat ["Required Argument: \"", key', "\" was not Defined"] -- TODO: real message
text = T.concat ["Required Argument: \"", key', "\" was not Defined"]
7 changes: 2 additions & 5 deletions morpheus/src/Data/Morpheus/Error/Variable.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Data.Morpheus.Types.Error (GQLError (..), GQLErrors)
import Data.Morpheus.Types.MetaInfo (Position)
import Data.Text (Text)
import qualified Data.Text as T (concat)
-- query M ( $v : String ) { a } -> "Variable \"$bla\" is never used in operation \"MyMutation\".",

{-|
VARIABLES:
Expand All @@ -33,18 +34,14 @@ case type mismatch
- { "v" : "v1" } -> "Variable \"$v\" got invalid value \"v1\"; Expected type LANGUAGE."
- { "v": 1 } "Variable \"$v\" got invalid value 1; Expected type LANGUAGE."
TODO: unused variable
- query M ( $v : String ) { a } -> "Variable \"$bla\" is never used in operation \"MyMutation\".",
TODO: variable does not match to argument type
- query M ( $v : String ) { a(p:$v) } -> "Variable \"$v\" of type \"String\" used in position expecting type \"LANGUAGE\"."
|-}
unusedVariables :: [EnhancedKey] -> GQLErrors
unusedVariables = map keyToError
where
keyToError (EnhancedKey key' position') = GQLError {desc = text key', posIndex = [position']}
text key' = T.concat ["unused Variable \"$", key', "\"."]
text key' = T.concat ["Variable \"$", key', "\" is never used in operation \"Query\"."]

variableGotInvalidValue :: Text -> Text -> Position -> GQLErrors
variableGotInvalidValue name' inputMessage' position' = errorMessage position' text
Expand Down
2 changes: 1 addition & 1 deletion morpheus/src/Data/Morpheus/Kind/GQLQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class GQLQuery a where
a -> TypeLib -> SelectionSet -> ResolveIO JSType
encodeQuery rootResolver types sel = resolveBySelection sel (schemaResolver ++ resolvers)
where
schemaResolver = [("__schema", (`encode` initSchema types))] -- TODO : lazy schema derivation
schemaResolver = [("__schema", (`encode` initSchema types))]
resolvers = deriveResolvers initialMeta $ from rootResolver
querySchema :: a -> TypeLib
default querySchema :: (Selectors (Rep a) (Text, ObjectField)) =>
Expand Down
2 changes: 1 addition & 1 deletion morpheus/src/Data/Morpheus/Types/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Collection a = [(Key, a)]
data EnhancedKey = EnhancedKey
{ uid :: Text
, location :: Int
} deriving (Ord) -- TODO: derive manual Ord
}

instance Eq EnhancedKey where
(EnhancedKey id1 _) == (EnhancedKey id2 _) = id1 == id2
Expand Down
7 changes: 7 additions & 0 deletions morpheus/src/Data/Morpheus/Types/Describer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ newtype EnumOf a = EnumOf
{ unpackEnum :: a
} deriving (Show, Generic, Data)

instance Functor EnumOf where
fmap f (EnumOf x) = EnumOf (f x)

instance Applicative EnumOf where
pure = EnumOf
(<*>) (EnumOf x) y = x <$> y

newtype ScalarOf a = ScalarOf
{ unpackScalar :: a
} deriving (Show, Generic, Data)
Expand Down
8 changes: 7 additions & 1 deletion morpheus/src/Data/Morpheus/Types/Types.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Data.Morpheus.Types.Types
( GQLQueryRoot(..)
, Variables
, GQLRoot(..)
) where

import Data.Map (Map)
Expand All @@ -14,5 +15,10 @@ type Variables = Map Key JSType
data GQLQueryRoot = GQLQueryRoot
{ fragments :: FragmentLib
, queryBody :: RawOperator
, inputVariables :: [(Key,JSType)]
, inputVariables :: [(Key, JSType)]
}

data GQLRoot a b = GQLRoot
{ query :: a
, mutation :: b
}
25 changes: 25 additions & 0 deletions morpheus/src/Data/Morpheus/Wrapper.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{-# LANGUAGE MultiParamTypeClasses #-}

module Data.Morpheus.Wrapper
( GQLRoot(..)
, EnumOf
, ScalarOf
, (::->)(..)
, wrap
, unwrap
) where

import Data.Morpheus.Types.Describer ((::->) (Resolver), EnumOf (..), ScalarOf (..))
import Data.Morpheus.Types.Types (GQLRoot (..))

class Wrapper m where
wrap :: a -> m a
unwrap :: m a -> a

instance Wrapper EnumOf where
wrap = EnumOf
unwrap (EnumOf x) = x

instance Wrapper ScalarOf where
wrap = ScalarOf
unwrap (ScalarOf x) = x

0 comments on commit 0de6d74

Please sign in to comment.