{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_HADDOCK not-home #-}
module GraphQL.Internal.Value
( Value
, Value'(..)
, ConstScalar
, UnresolvedVariableValue
, pattern ValueInt
, pattern ValueFloat
, pattern ValueBoolean
, pattern ValueString
, pattern ValueEnum
, pattern ValueList
, pattern ValueObject
, pattern ValueNull
, toObject
, valueToAST
, astToVariableValue
, variableValueToAST
, List
, List'(..)
, String(..)
, Name(..)
, NameError(..)
, makeName
, Object
, Object'(..)
, ObjectField
, ObjectField'(ObjectField)
, makeObject
, objectFromList
, objectFromOrderedMap
, unionObjects
, objectFields
) where
import Protolude
import qualified Data.Aeson as Aeson
import Data.Aeson (ToJSON(..), (.=), pairs)
import qualified Data.Map as Map
import Test.QuickCheck (Arbitrary(..), Gen, oneof, listOf, sized)
import GraphQL.Internal.Arbitrary (arbitraryText)
import GraphQL.Internal.Name (Name(..), NameError(..), makeName)
import GraphQL.Internal.Syntax.AST (Variable)
import qualified GraphQL.Internal.Syntax.AST as AST
import GraphQL.Internal.OrderedMap (OrderedMap)
import qualified GraphQL.Internal.OrderedMap as OrderedMap
data Value' scalar
= ValueScalar' scalar
| ValueList' (List' scalar)
| ValueObject' (Object' scalar)
deriving (Eq, Ord, Show, Functor)
instance Foldable Value' where
foldMap f (ValueScalar' scalar) = f scalar
foldMap f (ValueList' values) = foldMap f values
foldMap f (ValueObject' obj) = foldMap f obj
instance Traversable Value' where
traverse f (ValueScalar' x) = ValueScalar' <$> f x
traverse f (ValueList' xs) = ValueList' <$> traverse f xs
traverse f (ValueObject' xs) = ValueObject' <$> traverse f xs
instance ToJSON scalar => ToJSON (Value' scalar) where
toJSON (ValueScalar' x) = toJSON x
toJSON (ValueList' x) = toJSON x
toJSON (ValueObject' x) = toJSON x
instance Arbitrary scalar => Arbitrary (Value' scalar) where
arbitrary = sized genValue
genValue :: Arbitrary scalar => Int -> Gen (Value' scalar)
genValue n
| n <= 0 = arbitrary
| otherwise = oneof [ ValueScalar' <$> arbitrary
, ValueObject' <$> genObject (n - 1)
, ValueList' . List' <$> listOf (genValue (n - 1))
]
type Value = Value' ConstScalar
type UnresolvedVariableValue = Value' UnresolvedVariableScalar
pattern ValueInt :: Int32 -> Value
pattern ValueInt x = ValueScalar' (ConstInt x)
pattern ValueFloat :: Double -> Value
pattern ValueFloat x = ValueScalar' (ConstFloat x)
pattern ValueBoolean :: Bool -> Value
pattern ValueBoolean x = ValueScalar' (ConstBoolean x)
pattern ValueString :: String -> Value
pattern ValueString x = ValueScalar' (ConstString x)
pattern ValueEnum :: Name -> Value
pattern ValueEnum x = ValueScalar' (ConstEnum x)
pattern ValueList :: forall t. List' t -> Value' t
pattern ValueList x = ValueList' x
pattern ValueObject :: forall t. Object' t -> Value' t
pattern ValueObject x = ValueObject' x
pattern ValueNull :: Value
pattern ValueNull = ValueScalar' ConstNull
toObject :: Value' scalar -> Maybe (Object' scalar)
toObject (ValueObject' o) = pure o
toObject _ = empty
data ConstScalar
= ConstInt Int32
| ConstFloat Double
| ConstBoolean Bool
| ConstString String
| ConstEnum Name
| ConstNull
deriving (Eq, Ord, Show)
instance ToJSON ConstScalar where
toJSON (ConstInt x) = toJSON x
toJSON (ConstFloat x) = toJSON x
toJSON (ConstBoolean x) = toJSON x
toJSON (ConstString x) = toJSON x
toJSON (ConstEnum x) = toJSON x
toJSON ConstNull = Aeson.Null
type UnresolvedVariableScalar = Either Variable ConstScalar
instance Arbitrary ConstScalar where
arbitrary = oneof [ ConstInt <$> arbitrary
, ConstFloat <$> arbitrary
, ConstBoolean <$> arbitrary
, ConstString <$> arbitrary
, ConstEnum <$> arbitrary
, pure ConstNull
]
constScalarToAST :: ConstScalar -> AST.Value
constScalarToAST scalar =
case scalar of
ConstInt x -> AST.ValueInt x
ConstFloat x -> AST.ValueFloat x
ConstBoolean x -> AST.ValueBoolean x
ConstString (String x) -> AST.ValueString (AST.StringValue x)
ConstEnum x -> AST.ValueEnum x
ConstNull -> AST.ValueNull
variableToAST :: UnresolvedVariableScalar -> AST.Value
variableToAST (Left variable) = AST.ValueVariable variable
variableToAST (Right constant) = constScalarToAST constant
astToScalar :: AST.Value -> Maybe UnresolvedVariableScalar
astToScalar (AST.ValueInt x) = pure $ Right $ ConstInt x
astToScalar (AST.ValueFloat x) = pure $ Right $ ConstFloat x
astToScalar (AST.ValueBoolean x) = pure $ Right $ ConstBoolean x
astToScalar (AST.ValueString (AST.StringValue x)) = pure $ Right $ ConstString (String x)
astToScalar (AST.ValueEnum x) = pure $ Right $ ConstEnum x
astToScalar AST.ValueNull = pure $ Right ConstNull
astToScalar (AST.ValueVariable x) = pure $ Left x
astToScalar _ = empty
newtype String = String Text deriving (Eq, Ord, Show)
instance Arbitrary String where
arbitrary = String <$> arbitraryText
instance ToJSON String where
toJSON (String x) = toJSON x
newtype List' scalar = List' [Value' scalar] deriving (Eq, Ord, Show, Functor)
instance Foldable List' where
foldMap f (List' values) = mconcat (map (foldMap f) values)
instance Traversable List' where
traverse f (List' xs) = List' <$> traverse (traverse f) xs
type List = List' ConstScalar
instance Arbitrary scalar => Arbitrary (List' scalar) where
arbitrary = List' <$> listOf arbitrary
instance ToJSON scalar => ToJSON (List' scalar) where
toJSON (List' x) = toJSON x
newtype Object' scalar = Object' (OrderedMap Name (Value' scalar)) deriving (Eq, Ord, Show, Functor)
instance Foldable Object' where
foldMap f (Object' fieldMap) = foldMap (foldMap f) fieldMap
instance Traversable Object' where
traverse f (Object' xs) = Object' <$> traverse (traverse f) xs
type Object = Object' ConstScalar
objectFields :: Object' scalar -> [ObjectField' scalar]
objectFields (Object' object) = map (uncurry ObjectField') (OrderedMap.toList object)
instance Arbitrary scalar => Arbitrary (Object' scalar) where
arbitrary = sized genObject
genObject :: Arbitrary scalar => Int -> Gen (Object' scalar)
genObject n = Object' <$> OrderedMap.genOrderedMap arbitrary (genValue n)
data ObjectField' scalar = ObjectField' Name (Value' scalar) deriving (Eq, Ord, Show, Functor)
type ObjectField = ObjectField' ConstScalar
pattern ObjectField :: forall t. Name -> Value' t -> ObjectField' t
pattern ObjectField name value = ObjectField' name value
instance Arbitrary scalar => Arbitrary (ObjectField' scalar) where
arbitrary = ObjectField' <$> arbitrary <*> arbitrary
makeObject :: [ObjectField' scalar] -> Maybe (Object' scalar)
makeObject fields = objectFromList [(name, value) | ObjectField' name value <- fields]
objectFromOrderedMap :: OrderedMap Name (Value' scalar) -> Object' scalar
objectFromOrderedMap = Object'
objectFromList :: [(Name, Value' scalar)] -> Maybe (Object' scalar)
objectFromList xs = Object' <$> OrderedMap.orderedMap xs
unionObjects :: [Object' scalar] -> Maybe (Object' scalar)
unionObjects objects = Object' <$> OrderedMap.unions [obj | Object' obj <- objects]
instance ToJSON scalar => ToJSON (Object' scalar) where
toJSON (Object' xs) = toJSON (Map.fromList [(unName k, v) | (k, v) <- OrderedMap.toList xs])
toEncoding (Object' xs) = pairs (foldMap (\(k, v) -> toS (unName k) .= v) (OrderedMap.toList xs))
astToValue' :: (AST.Value -> scalar) -> AST.Value -> Maybe (Value' scalar)
astToValue' f x@(AST.ValueInt _) = pure (ValueScalar' (f x))
astToValue' f x@(AST.ValueFloat _) = pure (ValueScalar' (f x))
astToValue' f x@(AST.ValueBoolean _) = pure (ValueScalar' (f x))
astToValue' f x@(AST.ValueString (AST.StringValue _)) = pure (ValueScalar' (f x))
astToValue' f x@(AST.ValueEnum _) = pure (ValueScalar' (f x))
astToValue' f AST.ValueNull = pure (ValueScalar' (f AST.ValueNull))
astToValue' f x@(AST.ValueVariable _) = pure (ValueScalar' (f x))
astToValue' f (AST.ValueList (AST.ListValue xs)) = ValueList' . List' <$> traverse (astToValue' f) xs
astToValue' f (AST.ValueObject (AST.ObjectValue fields)) = do
fields' <- traverse toObjectField fields
object <- makeObject fields'
pure (ValueObject' object)
where
toObjectField (AST.ObjectField name value) = ObjectField' name <$> astToValue' f value
astToVariableValue :: HasCallStack => AST.Value -> Maybe UnresolvedVariableValue
astToVariableValue ast = astToValue' convertScalar ast
where
convertScalar x =
case astToScalar x of
Just scalar -> scalar
Nothing -> panic ("Non-scalar passed to convertScalar, bug in astToValue': " <> show x)
valueToAST :: Value -> AST.Value
valueToAST = valueToAST' constScalarToAST
variableValueToAST :: UnresolvedVariableValue -> AST.Value
variableValueToAST = valueToAST' variableToAST
valueToAST' :: (scalar -> AST.Value) -> Value' scalar -> AST.Value
valueToAST' f (ValueScalar' x) = f x
valueToAST' f (ValueList' (List' xs)) = AST.ValueList (AST.ListValue (map (valueToAST' f) xs))
valueToAST' f (ValueObject' (Object' fields)) = AST.ValueObject (AST.ObjectValue (map toObjectField (OrderedMap.toList fields)))
where
toObjectField (name, value) = AST.ObjectField name (valueToAST' f value)