{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveLift #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE TemplateHaskell #-} module Data.Morpheus.Types.Internal.AST.Value ( Value(..) , ScalarValue(..) , Object , GQLValue(..) , replaceValue , decodeScientific , convertToJSONName , convertToHaskellName , RawValue , ValidValue , RawObject , ValidObject , Variable(..) , ResolvedValue , ResolvedObject , VariableContent(..) , ObjectEntry(..) , VariableDefinitions ) where import qualified Data.Aeson as A ( FromJSON(..) , ToJSON(..) , Value(..) , object , pairs , (.=) ) import qualified Data.HashMap.Strict as M ( toList ) import Data.Scientific ( Scientific , floatingOrInteger ) import Data.Semigroup ( (<>) ) import Data.Text ( Text , unpack ) import qualified Data.Text as T import qualified Data.Vector as V ( toList ) import GHC.Generics ( Generic ) import Instances.TH.Lift ( ) import Language.Haskell.TH.Syntax ( Lift(..) ) -- MORPHEUS import Data.Morpheus.Error.NameCollision ( NameCollision(..) ) import Data.Morpheus.Types.Internal.AST.Base ( Ref(..) , Name , RAW , VALID , Position , Stage , RESOLVED , TypeRef , GQLError(..) , TypeRef(..) ) import Data.Morpheus.Types.Internal.AST.OrderedMap ( OrderedMap , unsafeFromValues , foldWithKey ) import Data.Morpheus.Types.Internal.Operation ( Listable(..) , KeyOf(..) ) isReserved :: Name -> Bool isReserved "case" = True isReserved "class" = True isReserved "data" = True isReserved "default" = True isReserved "deriving" = True isReserved "do" = True isReserved "else" = True isReserved "foreign" = True isReserved "if" = True isReserved "import" = True isReserved "in" = True isReserved "infix" = True isReserved "infixl" = True isReserved "infixr" = True isReserved "instance" = True isReserved "let" = True isReserved "module" = True isReserved "newtype" = True isReserved "of" = True isReserved "then" = True isReserved "type" = True isReserved "where" = True isReserved "_" = True isReserved _ = False {-# INLINE isReserved #-} convertToJSONName :: Text -> Text convertToJSONName hsName | not (T.null hsName) && isReserved name && (T.last hsName == '\'') = name | otherwise = hsName where name = T.init hsName convertToHaskellName :: Text -> Text convertToHaskellName name | isReserved name = name <> "'" | otherwise = name -- | Primitive Values for GQLScalar: 'Int', 'Float', 'String', 'Boolean'. -- for performance reason type 'Text' represents GraphQl 'String' value data ScalarValue = Int Int | Float Float | String Text | Boolean Bool deriving (Show, Eq, Generic,Lift) instance A.ToJSON ScalarValue where toJSON (Float x) = A.toJSON x toJSON (Int x) = A.toJSON x toJSON (Boolean x) = A.toJSON x toJSON (String x) = A.toJSON x instance A.FromJSON ScalarValue where parseJSON (A.Bool v) = pure $ Boolean v parseJSON (A.Number v) = pure $ decodeScientific v parseJSON (A.String v) = pure $ String v parseJSON notScalar = fail $ "Expected Scalar got :" <> show notScalar type family VAR (a:: Stage) :: Stage type instance VAR RAW = RESOLVED type instance VAR RESOLVED = RESOLVED type instance VAR VALID = VALID data VariableContent (stage:: Stage) where DefaultValue ::Maybe ResolvedValue -> VariableContent RESOLVED ValidVariableValue ::{ validVarContent::ValidValue }-> VariableContent VALID instance Lift (VariableContent a) where lift (DefaultValue x) = [| DefaultValue x |] lift (ValidVariableValue x) = [| ValidVariableValue x |] deriving instance Show (VariableContent a) deriving instance Eq (VariableContent a) data Variable (stage :: Stage) = Variable { variableName :: Name , variableType :: TypeRef , variablePosition :: Position , variableValue :: VariableContent (VAR stage) } deriving (Show, Eq, Lift) instance KeyOf (Variable s) where keyOf = variableName instance NameCollision (Variable s) where nameCollision _ Variable { variableName , variablePosition } = GQLError { message = "There can Be only One Variable Named \"" <> variableName <> "\"", locations = [variablePosition] } type VariableDefinitions s = OrderedMap (Variable s) data Value (stage :: Stage) where ResolvedVariable::Ref -> Variable VALID -> Value RESOLVED VariableValue ::Ref -> Value RAW Object ::Object stage -> Value stage List ::[Value stage] -> Value stage Enum ::Name -> Value stage Scalar ::ScalarValue -> Value stage Null ::Value stage deriving instance Eq (Value s) data ObjectEntry (s :: Stage) = ObjectEntry { entryName :: Name, entryValue :: Value s -- ObjectEntryposition :: Position } deriving (Eq) instance Show (ObjectEntry s) where show (ObjectEntry name value) = unpack name <> ":" <> show value instance NameCollision (ObjectEntry s) where nameCollision _ ObjectEntry { entryName } = GQLError { message = "There can Be only One field Named \"" <> entryName <> "\"", locations = [] } instance KeyOf (ObjectEntry s) where keyOf = entryName type Object a = OrderedMap (ObjectEntry a) type ValidObject = Object VALID type RawObject = Object RAW type ResolvedObject = Object RESOLVED type RawValue = Value RAW type ValidValue = Value VALID type ResolvedValue = Value RESOLVED deriving instance Lift (Value a) deriving instance Lift (ObjectEntry a) instance Show (Value a) where show Null = "null" show (Enum x) = "" <> unpack x show (Scalar x) = show x show (ResolvedVariable Ref { refName } Variable { variableValue }) = "($" <> unpack refName <> ": " <> show variableValue <> ") " show (VariableValue Ref { refName }) = "$" <> unpack refName <> " " show (Object keys ) = "{" <> foldWithKey toEntry "" keys <> "}" where toEntry :: Name -> ObjectEntry a -> String -> String toEntry _ value "" = show value toEntry _ value txt = txt <> ", " <> show value show (List list) = "[" <> foldl toEntry "" list <> "]" where toEntry :: String -> Value a -> String toEntry "" value = show value toEntry txt value = txt <> ", " <> show value instance A.ToJSON (Value a) where toJSON (ResolvedVariable _ Variable { variableValue = ValidVariableValue x }) = A.toJSON x toJSON (VariableValue Ref { refName }) = A.String $ "($ref:" <> refName <> ")" toJSON Null = A.Null toJSON (Enum x ) = A.String x toJSON (Scalar x ) = A.toJSON x toJSON (List x ) = A.toJSON x toJSON (Object fields) = A.object $ map toEntry (toList fields) where toEntry (ObjectEntry key value) = key A..= A.toJSON value ------------------------------------------- toEncoding (ResolvedVariable _ Variable { variableValue = ValidVariableValue x }) = A.toEncoding x toEncoding (VariableValue Ref { refName }) = A.toEncoding $ "($ref:" <> refName <> ")" toEncoding Null = A.toEncoding A.Null toEncoding (Enum x ) = A.toEncoding x toEncoding (Scalar x ) = A.toEncoding x toEncoding (List x ) = A.toEncoding x toEncoding (Object ordmap) | null ordmap = A.toEncoding $ A.object [] | otherwise = A.pairs $ foldl1 (<>) $ map encodeField (toList ordmap) where encodeField (ObjectEntry key value) = convertToJSONName key A..= value decodeScientific :: Scientific -> ScalarValue decodeScientific v = case floatingOrInteger v of Left float -> Float float Right int -> Int int replaceValue :: A.Value -> Value a replaceValue (A.Bool v) = gqlBoolean v replaceValue (A.Number v) = Scalar $ decodeScientific v replaceValue (A.String v) = gqlString v replaceValue (A.Object v) = gqlObject $ map replace (M.toList v) where --replace :: (a, A.Value) -> (a, Value a) replace (key, val) = (key, replaceValue val) replaceValue (A.Array li) = gqlList (map replaceValue (V.toList li)) replaceValue A.Null = gqlNull instance A.FromJSON (Value a) where parseJSON = pure . replaceValue -- DEFAULT VALUES class GQLValue a where gqlNull :: a gqlScalar :: ScalarValue -> a gqlBoolean :: Bool -> a gqlString :: Text -> a gqlList :: [a] -> a gqlObject :: [(Name, a)] -> a -- build GQL Values for Subscription Resolver instance GQLValue (Value a) where gqlNull = Null gqlScalar = Scalar gqlBoolean = Scalar . Boolean gqlString = Scalar . String gqlList = List gqlObject = Object . unsafeFromValues . map toEntry where toEntry (key,value) = ObjectEntry key value