{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Data.Morpheus.Types.Internal.Value
( Value(..)
, ScalarValue(..)
, Object
, GQLValue(..)
, replaceValue
, decodeScientific
, convertToJSONName
, convertToHaskellName
) where
import qualified Data.Aeson as A (FromJSON (..), ToJSON (..), Value (..), object, pairs, (.=))
import Data.Function ((&))
import qualified Data.HashMap.Strict as M (toList)
import Data.Scientific (Scientific, floatingOrInteger)
import Data.Semigroup ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V (toList)
import GHC.Generics (Generic)
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Data.Morpheus.Types.Internal.TH (apply, liftText, liftTextMap)
isReserved :: Text -> 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
data ScalarValue
= Int Int
| Float Float
| String Text
| Boolean Bool
deriving (Show, Generic)
instance Lift ScalarValue where
lift (String n) = apply 'String [liftText n]
lift (Int n) = apply 'Int [lift n]
lift (Float n) = apply 'Float [lift n]
lift (Boolean n) = apply 'Boolean [lift n]
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
instance Lift Value where
lift (Object ls) = apply 'Object [liftTextMap ls]
lift (List n) = apply 'List [lift n]
lift (Enum n) = apply 'Enum [liftText n]
lift (Scalar n) = apply 'Scalar [lift n]
lift Null = varE 'Null
type Object = [(Text, Value)]
data Value
= Object Object
| List [Value]
| Enum Text
| Scalar ScalarValue
| Null
deriving (Show, Generic)
instance A.ToJSON Value where
toEncoding Null = A.toEncoding A.Null
toEncoding (Enum x) = A.toEncoding x
toEncoding (List x) = A.toEncoding x
toEncoding (Scalar x) = A.toEncoding x
toEncoding (Object []) = A.toEncoding $ A.object []
toEncoding (Object x) = A.pairs $ foldl1 (<>) $ map encodeField x
where
encodeField (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
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)
replace (key, val) = (key, replaceValue val)
replaceValue (A.Array li) = gqlList (map replaceValue (V.toList li))
replaceValue A.Null = gqlNull
instance A.FromJSON Value where
parseJSON = pure . replaceValue
class GQLValue a where
gqlNull :: a
gqlScalar :: ScalarValue -> a
gqlBoolean :: Bool -> a
gqlString :: Text -> a
gqlList :: [a] -> a
gqlObject :: [(Text, a)] -> a
instance GQLValue Value where
gqlNull = Null
gqlScalar = Scalar
gqlBoolean = Scalar . Boolean
gqlString = Scalar . String
gqlList = List
gqlObject = Object
instance Monad m => GQLValue (m Value) where
gqlNull = pure gqlNull
gqlScalar = pure . gqlScalar
gqlBoolean = pure . gqlBoolean
gqlString = pure . gqlString
gqlList = fmap gqlList . sequence
gqlObject = fmap gqlObject . traverse keyVal
where
keyVal :: Monad m => (Text, m Value) -> m (Text, Value)
keyVal (key, valFunc) = (key, ) <$> valFunc
instance Monad m => GQLValue (args -> m Value) where
gqlNull = const gqlNull
gqlScalar = const . gqlScalar
gqlBoolean = pure . gqlBoolean
gqlString = const . gqlString
gqlList res args = gqlList <$> traverse (args &) res
gqlObject res args = gqlObject <$> traverse keyVal res
where
keyVal :: Monad m => (Text, args -> m Value) -> m (Text, Value)
keyVal (key, valFunc) = (key, ) <$> valFunc args