diff options
Diffstat (limited to 'Data/GraphQL')
| -rw-r--r-- | Data/GraphQL/AST.hs | 22 | ||||
| -rw-r--r-- | Data/GraphQL/Encoder.hs | 246 | ||||
| -rw-r--r-- | Data/GraphQL/Parser.hs | 107 |
3 files changed, 317 insertions, 58 deletions
diff --git a/Data/GraphQL/AST.hs b/Data/GraphQL/AST.hs index 0a09671..cc631e6 100644 --- a/Data/GraphQL/AST.hs +++ b/Data/GraphQL/AST.hs @@ -1,5 +1,6 @@ module Data.GraphQL.AST where +import Data.Int (Int32) import Data.Text (Text) -- * Name @@ -15,12 +16,12 @@ data Definition = DefinitionOperation OperationDefinition | DefinitionType TypeDefinition deriving (Eq,Show) -data OperationDefinition = - Query Name [VariableDefinition] [Directive] SelectionSet - | Mutation Name [VariableDefinition] [Directive] SelectionSet - -- Not official yet - -- -- | Subscription Name [VariableDefinition] [Directive] SelectionSet - deriving (Eq,Show) +data OperationDefinition = Query Node + | Mutation Node + deriving (Eq,Show) + +data Node = Node Name [VariableDefinition] [Directive] SelectionSet + deriving (Eq,Show) data VariableDefinition = VariableDefinition Variable Type (Maybe DefaultValue) deriving (Eq,Show) @@ -61,15 +62,18 @@ type TypeCondition = NamedType -- * Values data Value = ValueVariable Variable - | ValueInt Int -- TODO: Should this be `Integer`? - | ValueFloat Double -- TODO: Should this be `Scientific`? + | ValueInt Int32 + -- GraphQL Float is double precison + | ValueFloat Double | ValueBoolean Bool - | ValueString Text + | ValueString StringValue | ValueEnum Name | ValueList ListValue | ValueObject ObjectValue deriving (Eq,Show) +newtype StringValue = StringValue Text deriving (Eq,Show) + newtype ListValue = ListValue [Value] deriving (Eq,Show) newtype ObjectValue = ObjectValue [ObjectField] deriving (Eq,Show) diff --git a/Data/GraphQL/Encoder.hs b/Data/GraphQL/Encoder.hs new file mode 100644 index 0000000..9eed849 --- /dev/null +++ b/Data/GraphQL/Encoder.hs @@ -0,0 +1,246 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} +module Data.GraphQL.Encoder where + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative ((<$>)) +import Data.Monoid (Monoid, mconcat, mempty) +#endif +import Data.Monoid ((<>)) + +import Data.Text (Text, cons, intercalate, pack, snoc) + +import Data.GraphQL.AST + +-- * Document + +-- TODO: Use query shorthand +document :: Document -> Text +document (Document defs) = (`snoc` '\n') . mconcat $ definition <$> defs + +definition :: Definition -> Text +definition (DefinitionOperation x) = operationDefinition x +definition (DefinitionFragment x) = fragmentDefinition x +definition (DefinitionType x) = typeDefinition x + +operationDefinition :: OperationDefinition -> Text +operationDefinition (Query n) = "query " <> node n +operationDefinition (Mutation n) = "mutation " <> node n + +node :: Node -> Text +node (Node name vds ds ss) = + name + <> optempty variableDefinitions vds + <> optempty directives ds + <> selectionSet ss + +variableDefinitions :: [VariableDefinition] -> Text +variableDefinitions = parensCommas variableDefinition + +variableDefinition :: VariableDefinition -> Text +variableDefinition (VariableDefinition var ty dv) = + variable var <> ":" <> type_ ty <> maybe mempty defaultValue dv + +defaultValue :: DefaultValue -> Text +defaultValue val = "=" <> value val + +variable :: Variable -> Text +variable (Variable name) = "$" <> name + +selectionSet :: SelectionSet -> Text +selectionSet = bracesCommas selection + +selection :: Selection -> Text +selection (SelectionField x) = field x +selection (SelectionInlineFragment x) = inlineFragment x +selection (SelectionFragmentSpread x) = fragmentSpread x + +field :: Field -> Text +field (Field alias name args ds ss) = + optempty (`snoc` ':') alias + <> name + <> optempty arguments args + <> optempty directives ds + <> optempty selectionSet ss + +arguments :: [Argument] -> Text +arguments = parensCommas argument + +argument :: Argument -> Text +argument (Argument name v) = name <> ":" <> value v + +-- * Fragments + +fragmentSpread :: FragmentSpread -> Text +fragmentSpread (FragmentSpread name ds) = + "..." <> name <> optempty directives ds + +inlineFragment :: InlineFragment -> Text +inlineFragment (InlineFragment (NamedType tc) ds ss) = + "... on " <> tc + <> optempty directives ds + <> optempty selectionSet ss + +fragmentDefinition :: FragmentDefinition -> Text +fragmentDefinition (FragmentDefinition name (NamedType tc) ds ss) = + "fragment " <> name <> " on " <> tc + <> optempty directives ds + <> selectionSet ss + +-- * Values + +value :: Value -> Text +value (ValueVariable x) = variable x +-- TODO: This will be replaced with `decimal` Buidler +value (ValueInt x) = pack $ show x +-- TODO: This will be replaced with `decimal` Buidler +value (ValueFloat x) = pack $ show x +value (ValueBoolean x) = booleanValue x +value (ValueString x) = stringValue x +value (ValueEnum x) = x +value (ValueList x) = listValue x +value (ValueObject x) = objectValue x + +booleanValue :: Bool -> Text +booleanValue True = "true" +booleanValue False = "false" + +-- TODO: Escape characters +stringValue :: StringValue -> Text +stringValue (StringValue v) = quotes v + +listValue :: ListValue -> Text +listValue (ListValue vs) = bracketsCommas value vs + +objectValue :: ObjectValue -> Text +objectValue (ObjectValue ofs) = bracesCommas objectField ofs + +objectField :: ObjectField -> Text +objectField (ObjectField name v) = name <> ":" <> value v + +-- * Directives + +directives :: [Directive] -> Text +directives = spaces directive + +directive :: Directive -> Text +directive (Directive name args) = "@" <> name <> optempty arguments args + +-- * Type Reference + +type_ :: Type -> Text +type_ (TypeNamed (NamedType x)) = x +type_ (TypeList x) = listType x +type_ (TypeNonNull x) = nonNullType x + +namedType :: NamedType -> Text +namedType (NamedType name) = name + +listType :: ListType -> Text +listType (ListType ty) = brackets (type_ ty) + +nonNullType :: NonNullType -> Text +nonNullType (NonNullTypeNamed (NamedType x)) = x <> "!" +nonNullType (NonNullTypeList x) = listType x <> "!" + +typeDefinition :: TypeDefinition -> Text +typeDefinition (TypeDefinitionObject x) = objectTypeDefinition x +typeDefinition (TypeDefinitionInterface x) = interfaceTypeDefinition x +typeDefinition (TypeDefinitionUnion x) = unionTypeDefinition x +typeDefinition (TypeDefinitionScalar x) = scalarTypeDefinition x +typeDefinition (TypeDefinitionEnum x) = enumTypeDefinition x +typeDefinition (TypeDefinitionInputObject x) = inputObjectTypeDefinition x +typeDefinition (TypeDefinitionTypeExtension x) = typeExtensionDefinition x + +objectTypeDefinition :: ObjectTypeDefinition -> Text +objectTypeDefinition (ObjectTypeDefinition name ifaces fds) = + "type " <> name + <> optempty (spaced . interfaces) ifaces + <> optempty fieldDefinitions fds + +interfaces :: Interfaces -> Text +interfaces = ("implements " <>) . spaces namedType + +fieldDefinitions :: [FieldDefinition] -> Text +fieldDefinitions = bracesCommas fieldDefinition + +fieldDefinition :: FieldDefinition -> Text +fieldDefinition (FieldDefinition name args ty) = + name <> optempty argumentsDefinition args + <> ":" + <> type_ ty + +argumentsDefinition :: ArgumentsDefinition -> Text +argumentsDefinition = parensCommas inputValueDefinition + +interfaceTypeDefinition :: InterfaceTypeDefinition -> Text +interfaceTypeDefinition (InterfaceTypeDefinition name fds) = + "interface " <> name <> fieldDefinitions fds + +unionTypeDefinition :: UnionTypeDefinition -> Text +unionTypeDefinition (UnionTypeDefinition name ums) = + "union " <> name <> "=" <> unionMembers ums + +unionMembers :: [NamedType] -> Text +unionMembers = intercalate "|" . fmap namedType + +scalarTypeDefinition :: ScalarTypeDefinition -> Text +scalarTypeDefinition (ScalarTypeDefinition name) = "scalar " <> name + +enumTypeDefinition :: EnumTypeDefinition -> Text +enumTypeDefinition (EnumTypeDefinition name evds) = + "enum " <> name + <> bracesCommas enumValueDefinition evds + +enumValueDefinition :: EnumValueDefinition -> Text +enumValueDefinition (EnumValueDefinition name) = name + +inputObjectTypeDefinition :: InputObjectTypeDefinition -> Text +inputObjectTypeDefinition (InputObjectTypeDefinition name ivds) = + "input " <> name <> inputValueDefinitions ivds + +inputValueDefinitions :: [InputValueDefinition] -> Text +inputValueDefinitions = bracesCommas inputValueDefinition + +inputValueDefinition :: InputValueDefinition -> Text +inputValueDefinition (InputValueDefinition name ty dv) = + name <> ":" <> type_ ty <> maybe mempty defaultValue dv + +typeExtensionDefinition :: TypeExtensionDefinition -> Text +typeExtensionDefinition (TypeExtensionDefinition otd) = + "extend " <> objectTypeDefinition otd + +-- * Internal + +spaced :: Text -> Text +spaced = cons '\SP' + +between :: Char -> Char -> Text -> Text +between open close = cons open . (`snoc` close) + +parens :: Text -> Text +parens = between '(' ')' + +brackets :: Text -> Text +brackets = between '[' ']' + +braces :: Text -> Text +braces = between '{' '}' + +quotes :: Text -> Text +quotes = between '"' '"' + +spaces :: (a -> Text) -> [a] -> Text +spaces f = intercalate "\SP" . fmap f + +parensCommas :: (a -> Text) -> [a] -> Text +parensCommas f = parens . intercalate "," . fmap f + +bracketsCommas :: (a -> Text) -> [a] -> Text +bracketsCommas f = brackets . intercalate "," . fmap f + +bracesCommas :: (a -> Text) -> [a] -> Text +bracesCommas f = braces . intercalate "," . fmap f + +optempty :: (Eq a, Monoid a, Monoid b) => (a -> b) -> a -> b +optempty f xs = if xs == mempty then mempty else f xs diff --git a/Data/GraphQL/Parser.hs b/Data/GraphQL/Parser.hs index c999004..1f5b8e6 100644 --- a/Data/GraphQL/Parser.hs +++ b/Data/GraphQL/Parser.hs @@ -1,6 +1,5 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} module Data.GraphQL.Parser where import Prelude hiding (takeWhile) @@ -11,8 +10,10 @@ import Data.Monoid (Monoid, mempty) #endif import Control.Applicative ((<|>), empty, many, optional) import Control.Monad (when) -import Data.Char -import Data.Text (Text, pack) +import Data.Char (isDigit, isSpace) +import Data.Foldable (traverse_) + +import Data.Text (Text, append) import Data.Attoparsec.Text ( Parser , (<?>) @@ -20,25 +21,27 @@ import Data.Attoparsec.Text , decimal , double , endOfLine + , inClass , many1 , manyTill , option , peekChar - , satisfy , sepBy1 , signed + , takeWhile + , takeWhile1 ) import Data.GraphQL.AST -- * Name --- XXX: Handle starting `_` and no number at the beginning: --- https://facebook.github.io/graphql/#sec-Names --- TODO: Use takeWhile1 instead for efficiency. With takeWhile1 there is no --- parsing failure. name :: Parser Name -name = tok $ pack <$> many1 (satisfy isAlphaNum) +name = tok $ append <$> takeWhile1 isA_z + <*> takeWhile ((||) <$> isDigit <*> isA_z) + where + -- `isAlpha` handles many more Unicode Chars + isA_z = inClass $ '_' : ['A'..'Z'] ++ ['a'..'z'] -- * Document @@ -48,7 +51,8 @@ document = whiteSpace -- Try SelectionSet when no definition <|> (Document . pure . DefinitionOperation - . Query mempty empty empty + . Query + . Node mempty empty empty <$> selectionSet) <?> "document error!" @@ -60,14 +64,15 @@ definition = DefinitionOperation <$> operationDefinition operationDefinition :: Parser OperationDefinition operationDefinition = - op Query "query" - <|> op Mutation "mutation" + Query <$ tok "query" <*> node + <|> Mutation <$ tok "mutation" <*> node <?> "operationDefinition error!" - where - op f n = f <$ tok n <*> tok name - <*> optempty variableDefinitions - <*> optempty directives - <*> selectionSet + +node :: Parser Node +node = Node <$> name + <*> optempty variableDefinitions + <*> optempty directives + <*> selectionSet variableDefinitions :: Parser [VariableDefinition] variableDefinitions = parens (many1 variableDefinition) @@ -148,16 +153,24 @@ typeCondition = namedType -- explicit types use the `typedValue` parser. value :: Parser Value value = ValueVariable <$> variable - -- TODO: Handle arbitrary precision. - <|> ValueInt <$> tok (signed decimal) - <|> ValueFloat <$> tok (signed double) - <|> ValueBoolean <$> bool - -- TODO: Handle escape characters, unicode, etc - <|> ValueString <$> quotes name + -- TODO: Handle maxBound, Int32 in spec. + <|> ValueInt <$> tok (signed decimal) + <|> ValueFloat <$> tok (signed double) + <|> ValueBoolean <$> booleanValue + <|> ValueString <$> stringValue -- `true` and `false` have been tried before - <|> ValueEnum <$> name - <|> ValueList <$> listValue - <|> ValueObject <$> objectValue + <|> ValueEnum <$> name + <|> ValueList <$> listValue + <|> ValueObject <$> objectValue + <?> "value error!" + +booleanValue :: Parser Bool +booleanValue = True <$ tok "true" + <|> False <$ tok "false" + +-- TODO: Escape characters. Look at `jsstring_` in aeson package. +stringValue :: Parser StringValue +stringValue = StringValue <$> quotes (takeWhile (/= '"')) -- Notice it can be empty listValue :: Parser ListValue @@ -170,10 +183,6 @@ objectValue = ObjectValue <$> braces (many objectField) objectField :: Parser ObjectField objectField = ObjectField <$> name <* tok ":" <*> value -bool :: Parser Bool -bool = True <$ tok "true" - <|> False <$ tok "false" - -- * Directives directives :: Parser [Directive] @@ -188,9 +197,10 @@ directive = Directive -- * Type Reference type_ :: Parser Type -type_ = TypeNamed <$> namedType - <|> TypeList <$> listType +type_ = TypeList <$> listType <|> TypeNonNull <$> nonNullType + <|> TypeNamed <$> namedType + <?> "type_ error!" namedType :: Parser NamedType namedType = NamedType <$> name @@ -201,6 +211,7 @@ listType = ListType <$> brackets type_ nonNullType :: Parser NonNullType nonNullType = NonNullTypeNamed <$> namedType <* tok "!" <|> NonNullTypeList <$> listType <* tok "!" + <?> "nonNullType error!" -- * Type Definition @@ -221,7 +232,6 @@ objectTypeDefinition = ObjectTypeDefinition <*> name <*> optempty interfaces <*> fieldDefinitions - <?> "objectTypeDefinition error!" interfaces :: Parser Interfaces interfaces = tok "implements" *> many1 namedType @@ -237,17 +247,7 @@ fieldDefinition = FieldDefinition <*> type_ argumentsDefinition :: Parser ArgumentsDefinition -argumentsDefinition = inputValueDefinitions - -inputValueDefinitions :: Parser [InputValueDefinition] -inputValueDefinitions = parens $ many1 inputValueDefinition - -inputValueDefinition :: Parser InputValueDefinition -inputValueDefinition = InputValueDefinition - <$> name - <* tok ":" - <*> type_ - <*> optional defaultValue +argumentsDefinition = parens $ many1 inputValueDefinition interfaceTypeDefinition :: Parser InterfaceTypeDefinition interfaceTypeDefinition = InterfaceTypeDefinition @@ -288,6 +288,16 @@ inputObjectTypeDefinition = InputObjectTypeDefinition <*> name <*> inputValueDefinitions +inputValueDefinitions :: Parser [InputValueDefinition] +inputValueDefinitions = braces $ many1 inputValueDefinition + +inputValueDefinition :: Parser InputValueDefinition +inputValueDefinition = InputValueDefinition + <$> name + <* tok ":" + <*> type_ + <*> optional defaultValue + typeExtensionDefinition :: Parser TypeExtensionDefinition typeExtensionDefinition = TypeExtensionDefinition <$ tok "extend" @@ -320,8 +330,7 @@ optempty = option mempty -- ** WhiteSpace -- whiteSpace :: Parser () -whiteSpace = peekChar >>= \case - Just c -> if isSpace c || c == ',' - then anyChar *> whiteSpace - else when (c == '#') $ manyTill anyChar endOfLine *> whiteSpace - _ -> return () +whiteSpace = peekChar >>= traverse_ (\c -> + if isSpace c || c == ',' + then anyChar *> whiteSpace + else when (c == '#') $ manyTill anyChar endOfLine *> whiteSpace) |
