2015-09-21 18:26:22 +02:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2019-08-02 13:52:51 +02:00
|
|
|
{-# LANGUAGE ExplicitForAll #-}
|
|
|
|
|
2019-08-05 09:00:11 +02:00
|
|
|
-- | This module defines a minifier and a printer for the @GraphQL@ language.
|
2019-07-14 05:58:05 +02:00
|
|
|
module Language.GraphQL.Encoder
|
2019-08-03 23:57:27 +02:00
|
|
|
( Formatter
|
2019-07-31 05:40:17 +02:00
|
|
|
, definition
|
2019-08-14 08:49:07 +02:00
|
|
|
, directive
|
2019-07-27 07:19:21 +02:00
|
|
|
, document
|
2019-08-03 23:57:27 +02:00
|
|
|
, minified
|
|
|
|
, pretty
|
2019-08-13 07:24:05 +02:00
|
|
|
, type'
|
|
|
|
, value
|
2019-07-14 05:58:05 +02:00
|
|
|
) where
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2017-01-28 18:15:14 +01:00
|
|
|
import Data.Foldable (fold)
|
2015-09-21 18:26:22 +02:00
|
|
|
import Data.Monoid ((<>))
|
2017-01-28 18:15:14 +01:00
|
|
|
import qualified Data.List.NonEmpty as NonEmpty (toList)
|
2019-08-05 09:00:11 +02:00
|
|
|
import Data.Text.Lazy (Text)
|
|
|
|
import qualified Data.Text.Lazy as Text.Lazy
|
|
|
|
import Data.Text.Lazy.Builder (toLazyText)
|
|
|
|
import Data.Text.Lazy.Builder.Int (decimal)
|
|
|
|
import Data.Text.Lazy.Builder.RealFloat (realFloat)
|
2019-07-07 06:31:53 +02:00
|
|
|
import Language.GraphQL.AST
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
-- | Instructs the encoder whether a GraphQL should be minified or pretty
|
|
|
|
-- printed.
|
2019-08-03 23:57:27 +02:00
|
|
|
--
|
|
|
|
-- Use 'pretty' and 'minified' to construct the formatter.
|
2019-07-31 05:40:17 +02:00
|
|
|
data Formatter
|
|
|
|
= Minified
|
2019-08-03 23:57:27 +02:00
|
|
|
| Pretty Word
|
|
|
|
|
|
|
|
-- Constructs a formatter for pretty printing.
|
|
|
|
pretty :: Formatter
|
|
|
|
pretty = Pretty 0
|
|
|
|
|
|
|
|
-- Constructs a formatter for minifying.
|
|
|
|
minified :: Formatter
|
|
|
|
minified = Minified
|
2019-07-31 05:40:17 +02:00
|
|
|
|
2019-07-27 07:19:21 +02:00
|
|
|
-- | Converts a 'Document' into a string.
|
2019-07-31 05:40:17 +02:00
|
|
|
document :: Formatter -> Document -> Text
|
|
|
|
document formatter defs
|
2019-08-05 09:00:11 +02:00
|
|
|
| Pretty _ <- formatter = Text.Lazy.intercalate "\n" encodeDocument
|
|
|
|
| Minified <-formatter = Text.Lazy.snoc (mconcat encodeDocument) '\n'
|
2019-07-31 05:40:17 +02:00
|
|
|
where
|
|
|
|
encodeDocument = NonEmpty.toList $ definition formatter <$> defs
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-27 07:19:21 +02:00
|
|
|
-- | Converts a 'Definition' into a string.
|
2019-07-31 05:40:17 +02:00
|
|
|
definition :: Formatter -> Definition -> Text
|
|
|
|
definition formatter x
|
2019-08-05 09:00:11 +02:00
|
|
|
| Pretty _ <- formatter = Text.Lazy.snoc (encodeDefinition x) '\n'
|
2019-07-31 05:40:17 +02:00
|
|
|
| Minified <- formatter = encodeDefinition x
|
2019-07-27 07:19:21 +02:00
|
|
|
where
|
|
|
|
encodeDefinition (DefinitionOperation operation)
|
2019-07-31 05:40:17 +02:00
|
|
|
= operationDefinition formatter operation
|
2019-07-27 07:19:21 +02:00
|
|
|
encodeDefinition (DefinitionFragment fragment)
|
2019-07-31 05:40:17 +02:00
|
|
|
= fragmentDefinition formatter fragment
|
|
|
|
|
|
|
|
operationDefinition :: Formatter -> OperationDefinition -> Text
|
|
|
|
operationDefinition formatter (OperationSelectionSet sels)
|
|
|
|
= selectionSet formatter sels
|
|
|
|
operationDefinition formatter (OperationDefinition Query name vars dirs sels)
|
2019-08-02 13:52:51 +02:00
|
|
|
= "query " <> node formatter name vars dirs sels
|
2019-07-31 05:40:17 +02:00
|
|
|
operationDefinition formatter (OperationDefinition Mutation name vars dirs sels)
|
2019-08-02 13:52:51 +02:00
|
|
|
= "mutation " <> node formatter name vars dirs sels
|
2019-07-31 05:40:17 +02:00
|
|
|
|
|
|
|
node :: Formatter
|
2019-08-02 13:52:51 +02:00
|
|
|
-> Maybe Name
|
2019-07-31 05:40:17 +02:00
|
|
|
-> VariableDefinitions
|
|
|
|
-> Directives
|
|
|
|
-> SelectionSet
|
|
|
|
-> Text
|
|
|
|
node formatter name vars dirs sels
|
2019-08-05 09:00:11 +02:00
|
|
|
= Text.Lazy.fromStrict (fold name)
|
2019-08-02 13:52:51 +02:00
|
|
|
<> optempty (variableDefinitions formatter) vars
|
|
|
|
<> optempty (directives formatter) dirs
|
|
|
|
<> eitherFormat formatter " " mempty
|
2019-07-31 05:40:17 +02:00
|
|
|
<> selectionSet formatter sels
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
variableDefinitions :: Formatter -> [VariableDefinition] -> Text
|
|
|
|
variableDefinitions formatter
|
|
|
|
= parensCommas formatter $ variableDefinition formatter
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
variableDefinition :: Formatter -> VariableDefinition -> Text
|
|
|
|
variableDefinition formatter (VariableDefinition var ty dv)
|
|
|
|
= variable var
|
|
|
|
<> eitherFormat formatter ": " ":"
|
2019-08-13 07:24:05 +02:00
|
|
|
<> type' ty
|
2019-08-02 13:52:51 +02:00
|
|
|
<> maybe mempty (defaultValue formatter) dv
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
defaultValue :: Formatter -> Value -> Text
|
|
|
|
defaultValue formatter val
|
|
|
|
= eitherFormat formatter " = " "="
|
|
|
|
<> value formatter val
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-18 05:10:02 +02:00
|
|
|
variable :: Name -> Text
|
2019-08-05 09:00:11 +02:00
|
|
|
variable var = "$" <> Text.Lazy.fromStrict var
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
selectionSet :: Formatter -> SelectionSet -> Text
|
2019-08-03 23:57:27 +02:00
|
|
|
selectionSet formatter
|
|
|
|
= bracesList formatter (selection formatter)
|
|
|
|
. NonEmpty.toList
|
2017-01-28 18:15:14 +01:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
selectionSetOpt :: Formatter -> SelectionSetOpt -> Text
|
2019-08-03 23:57:27 +02:00
|
|
|
selectionSetOpt formatter = bracesList formatter $ selection formatter
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
selection :: Formatter -> Selection -> Text
|
2019-08-05 09:00:11 +02:00
|
|
|
selection formatter = Text.Lazy.append indent . f
|
2019-08-03 23:57:27 +02:00
|
|
|
where
|
|
|
|
f (SelectionField x) = field incrementIndent x
|
|
|
|
f (SelectionInlineFragment x) = inlineFragment incrementIndent x
|
|
|
|
f (SelectionFragmentSpread x) = fragmentSpread incrementIndent x
|
|
|
|
incrementIndent
|
|
|
|
| Pretty n <- formatter = Pretty $ n + 1
|
|
|
|
| otherwise = Minified
|
|
|
|
indent
|
2019-08-05 09:00:11 +02:00
|
|
|
| Pretty n <- formatter = Text.Lazy.replicate (fromIntegral $ n + 1) " "
|
2019-08-03 23:57:27 +02:00
|
|
|
| otherwise = mempty
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
field :: Formatter -> Field -> Text
|
2019-08-02 13:52:51 +02:00
|
|
|
field formatter (Field alias name args dirs selso)
|
2019-08-05 09:00:11 +02:00
|
|
|
= optempty (`Text.Lazy.append` colon) (Text.Lazy.fromStrict $ fold alias)
|
|
|
|
<> Text.Lazy.fromStrict name
|
2019-08-02 13:52:51 +02:00
|
|
|
<> optempty (arguments formatter) args
|
|
|
|
<> optempty (directives formatter) dirs
|
|
|
|
<> selectionSetOpt'
|
|
|
|
where
|
|
|
|
colon = eitherFormat formatter ": " ":"
|
|
|
|
selectionSetOpt'
|
|
|
|
| null selso = mempty
|
|
|
|
| otherwise = eitherFormat formatter " " mempty <> selectionSetOpt formatter selso
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
arguments :: Formatter -> [Argument] -> Text
|
|
|
|
arguments formatter = parensCommas formatter $ argument formatter
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
argument :: Formatter -> Argument -> Text
|
|
|
|
argument formatter (Argument name v)
|
2019-08-05 09:00:11 +02:00
|
|
|
= Text.Lazy.fromStrict name
|
2019-08-02 13:52:51 +02:00
|
|
|
<> eitherFormat formatter ": " ":"
|
|
|
|
<> value formatter v
|
2015-09-21 18:26:22 +02:00
|
|
|
|
|
|
|
-- * Fragments
|
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
fragmentSpread :: Formatter -> FragmentSpread -> Text
|
2019-08-03 23:57:27 +02:00
|
|
|
fragmentSpread formatter (FragmentSpread name ds)
|
2019-08-05 09:00:11 +02:00
|
|
|
= "..." <> Text.Lazy.fromStrict name <> optempty (directives formatter) ds
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
inlineFragment :: Formatter -> InlineFragment -> Text
|
2019-08-02 13:52:51 +02:00
|
|
|
inlineFragment formatter (InlineFragment tc dirs sels)
|
2019-08-05 09:00:11 +02:00
|
|
|
= "... on "
|
|
|
|
<> Text.Lazy.fromStrict (fold tc)
|
2019-08-02 13:52:51 +02:00
|
|
|
<> directives formatter dirs
|
|
|
|
<> eitherFormat formatter " " mempty
|
|
|
|
<> selectionSet formatter sels
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-07-31 05:40:17 +02:00
|
|
|
fragmentDefinition :: Formatter -> FragmentDefinition -> Text
|
2019-08-02 13:52:51 +02:00
|
|
|
fragmentDefinition formatter (FragmentDefinition name tc dirs sels)
|
2019-08-05 09:00:11 +02:00
|
|
|
= "fragment " <> Text.Lazy.fromStrict name
|
|
|
|
<> " on " <> Text.Lazy.fromStrict tc
|
2019-08-02 13:52:51 +02:00
|
|
|
<> optempty (directives formatter) dirs
|
|
|
|
<> eitherFormat formatter " " mempty
|
|
|
|
<> selectionSet formatter sels
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-14 08:49:07 +02:00
|
|
|
-- * Miscellaneous
|
2019-08-13 07:24:05 +02:00
|
|
|
|
2019-08-14 08:49:07 +02:00
|
|
|
-- | Converts a 'Directive' into a string.
|
2019-08-13 07:24:05 +02:00
|
|
|
directive :: Formatter -> Directive -> Text
|
|
|
|
directive formatter (Directive name args)
|
|
|
|
= "@" <> Text.Lazy.fromStrict name <> optempty (arguments formatter) args
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-14 08:49:07 +02:00
|
|
|
directives :: Formatter -> Directives -> Text
|
|
|
|
directives formatter@(Pretty _) = Text.Lazy.cons ' ' . spaces (directive formatter)
|
|
|
|
directives Minified = spaces (directive Minified)
|
2019-08-13 07:24:05 +02:00
|
|
|
|
|
|
|
-- | Converts a 'Value' into a string.
|
2019-08-02 13:52:51 +02:00
|
|
|
value :: Formatter -> Value -> Text
|
|
|
|
value _ (ValueVariable x) = variable x
|
2019-08-05 09:00:11 +02:00
|
|
|
value _ (ValueInt x) = toLazyText $ decimal x
|
|
|
|
value _ (ValueFloat x) = toLazyText $ realFloat x
|
2019-08-02 13:52:51 +02:00
|
|
|
value _ (ValueBoolean x) = booleanValue x
|
2019-08-05 09:00:11 +02:00
|
|
|
value _ ValueNull = mempty
|
|
|
|
value _ (ValueString x) = stringValue $ Text.Lazy.fromStrict x
|
|
|
|
value _ (ValueEnum x) = Text.Lazy.fromStrict x
|
|
|
|
value formatter (ValueList x) = listValue formatter x
|
|
|
|
value formatter (ValueObject x) = objectValue formatter x
|
2015-09-21 18:26:22 +02:00
|
|
|
|
|
|
|
booleanValue :: Bool -> Text
|
|
|
|
booleanValue True = "true"
|
|
|
|
booleanValue False = "false"
|
|
|
|
|
2016-02-22 13:59:38 +01:00
|
|
|
stringValue :: Text -> Text
|
2019-08-13 07:24:05 +02:00
|
|
|
stringValue
|
|
|
|
= quotes
|
|
|
|
. Text.Lazy.replace "\"" "\\\""
|
|
|
|
. Text.Lazy.replace "\\" "\\\\"
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
listValue :: Formatter -> [Value] -> Text
|
|
|
|
listValue formatter = bracketsCommas formatter $ value formatter
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
objectValue :: Formatter -> [ObjectField] -> Text
|
2019-08-03 23:57:27 +02:00
|
|
|
objectValue formatter = intercalate $ objectField formatter
|
|
|
|
where
|
|
|
|
intercalate f
|
|
|
|
= braces
|
2019-08-05 09:00:11 +02:00
|
|
|
. Text.Lazy.intercalate (eitherFormat formatter ", " ",")
|
2019-08-03 23:57:27 +02:00
|
|
|
. fmap f
|
|
|
|
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
objectField :: Formatter -> ObjectField -> Text
|
2019-08-05 09:00:11 +02:00
|
|
|
objectField formatter (ObjectField name v)
|
|
|
|
= Text.Lazy.fromStrict name <> colon <> value formatter v
|
2019-08-02 13:52:51 +02:00
|
|
|
where
|
|
|
|
colon
|
|
|
|
| Pretty _ <- formatter = ": "
|
|
|
|
| Minified <- formatter = ":"
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2019-08-13 07:24:05 +02:00
|
|
|
-- | Converts a 'Type' a type into a string.
|
|
|
|
type' :: Type -> Text
|
|
|
|
type' (TypeNamed x) = Text.Lazy.fromStrict x
|
|
|
|
type' (TypeList x) = listType x
|
|
|
|
type' (TypeNonNull x) = nonNullType x
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2017-01-28 18:15:14 +01:00
|
|
|
listType :: Type -> Text
|
2019-08-13 07:24:05 +02:00
|
|
|
listType x = brackets (type' x)
|
2015-09-21 18:26:22 +02:00
|
|
|
|
|
|
|
nonNullType :: NonNullType -> Text
|
2019-08-05 09:00:11 +02:00
|
|
|
nonNullType (NonNullTypeNamed x) = Text.Lazy.fromStrict x <> "!"
|
2015-09-21 18:26:22 +02:00
|
|
|
nonNullType (NonNullTypeList x) = listType x <> "!"
|
|
|
|
|
|
|
|
-- * Internal
|
|
|
|
|
2015-09-22 10:45:14 +02:00
|
|
|
between :: Char -> Char -> Text -> Text
|
2019-08-05 09:00:11 +02:00
|
|
|
between open close = Text.Lazy.cons open . (`Text.Lazy.snoc` close)
|
2015-09-21 18:26:22 +02:00
|
|
|
|
|
|
|
parens :: Text -> Text
|
2015-09-22 10:45:14 +02:00
|
|
|
parens = between '(' ')'
|
2015-09-21 18:26:22 +02:00
|
|
|
|
|
|
|
brackets :: Text -> Text
|
2015-09-22 10:45:14 +02:00
|
|
|
brackets = between '[' ']'
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2015-09-22 10:45:14 +02:00
|
|
|
braces :: Text -> Text
|
|
|
|
braces = between '{' '}'
|
2015-09-21 18:26:22 +02:00
|
|
|
|
2015-09-22 13:53:37 +02:00
|
|
|
quotes :: Text -> Text
|
|
|
|
quotes = between '"' '"'
|
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
spaces :: forall a. (a -> Text) -> [a] -> Text
|
2019-08-05 09:00:11 +02:00
|
|
|
spaces f = Text.Lazy.intercalate "\SP" . fmap f
|
2015-09-22 10:45:14 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
parensCommas :: forall a. Formatter -> (a -> Text) -> [a] -> Text
|
|
|
|
parensCommas formatter f
|
|
|
|
= parens
|
2019-08-05 09:00:11 +02:00
|
|
|
. Text.Lazy.intercalate (eitherFormat formatter ", " ",")
|
2019-08-02 13:52:51 +02:00
|
|
|
. fmap f
|
2015-09-22 10:45:14 +02:00
|
|
|
|
2019-08-02 13:52:51 +02:00
|
|
|
bracketsCommas :: Formatter -> (a -> Text) -> [a] -> Text
|
|
|
|
bracketsCommas formatter f
|
|
|
|
= brackets
|
2019-08-05 09:00:11 +02:00
|
|
|
. Text.Lazy.intercalate (eitherFormat formatter ", " ",")
|
2019-08-02 13:52:51 +02:00
|
|
|
. fmap f
|
2015-09-22 10:45:14 +02:00
|
|
|
|
2019-08-03 23:57:27 +02:00
|
|
|
bracesList :: forall a. Formatter -> (a -> Text) -> [a] -> Text
|
|
|
|
bracesList (Pretty intendation) f xs
|
2019-08-05 09:00:11 +02:00
|
|
|
= Text.Lazy.snoc (Text.Lazy.intercalate "\n" content) '\n'
|
|
|
|
<> (Text.Lazy.snoc $ Text.Lazy.replicate (fromIntegral intendation) " ") '}'
|
2019-08-03 23:57:27 +02:00
|
|
|
where
|
|
|
|
content = "{" : fmap f xs
|
2019-08-05 09:00:11 +02:00
|
|
|
bracesList Minified f xs = braces $ Text.Lazy.intercalate "," $ fmap f xs
|
2019-07-31 05:40:17 +02:00
|
|
|
|
2015-09-21 18:26:22 +02:00
|
|
|
optempty :: (Eq a, Monoid a, Monoid b) => (a -> b) -> a -> b
|
|
|
|
optempty f xs = if xs == mempty then mempty else f xs
|
2019-08-02 13:52:51 +02:00
|
|
|
|
|
|
|
eitherFormat :: forall a. Formatter -> a -> a -> a
|
2019-08-03 23:57:27 +02:00
|
|
|
eitherFormat (Pretty _) x _ = x
|
|
|
|
eitherFormat Minified _ x = x
|