morpheus-graphql-server-0.27.1: src/Data/Morpheus/Server/Deriving/Internal/Decode/Utils.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.Morpheus.Server.Deriving.Internal.Decode.Utils
( withInputObject,
withEnum,
withInputUnion,
decodeFieldWith,
withScalar,
handleEither,
getFieldName,
DecoderT,
setVariantRef,
Context (..),
getUnionInfos,
DescribeCons,
CountFields (..),
RefType (..),
repValue,
useDecodeArguments,
)
where
import Control.Monad.Except (MonadError (throwError))
import Data.Morpheus.App.Internal.Resolving (ResolverState)
import Data.Morpheus.Internal.Ext (GQLResult)
import Data.Morpheus.Internal.Utils
( fromElems,
selectOr,
)
import Data.Morpheus.Server.Deriving.Utils.AST (argumentsToObject)
import Data.Morpheus.Server.Deriving.Utils.GRep
( ConsRep (..),
FieldRep (..),
TypeRep (..),
)
import Data.Morpheus.Server.Deriving.Utils.Kinded
( CatType (..),
)
import Data.Morpheus.Server.Deriving.Utils.Proxy
( conNameProxy,
)
import Data.Morpheus.Server.Deriving.Utils.Use (UseDeriving, UseGQLType (useTypename), UseValue (..), dirArgs)
import Data.Morpheus.Types.GQLScalar
( toScalar,
)
import Data.Morpheus.Types.Internal.AST
( Arguments,
CONST,
FieldName,
GQLError,
IN,
Msg (msg),
ObjectEntry (..),
ScalarValue,
Token,
TypeName,
VALID,
ValidObject,
ValidValue,
Value (..),
getInputUnionValue,
internal,
)
import GHC.Generics
import Relude
repValue ::
TypeRep (GQLResult (Value CONST)) ->
GQLResult (Value CONST)
repValue
TypeRep
{ tyIsUnion,
tyCons = ConsRep {consFields, consName}
} = encodeTypeFields consFields
where
encodeTypeFields ::
[FieldRep (GQLResult (Value CONST))] -> GQLResult (Value CONST)
encodeTypeFields [] = pure $ Enum consName
encodeTypeFields fields | not tyIsUnion = Object <$> (traverse fromField fields >>= fromElems)
where
fromField FieldRep {fieldSelector, fieldValue} = do
entryValue <- fieldValue
pure ObjectEntry {entryName = fieldSelector, entryValue}
-- Type References --------------------------------------------------------------
encodeTypeFields _ = throwError (internal "input unions are not supported")
withInputObject ::
MonadError GQLError m =>
(ValidObject -> m a) ->
ValidValue ->
m a
withInputObject f (Object object) = f object
withInputObject _ isType = throwError (typeMismatch "InputObject" isType)
-- | Useful for more restrictive instances of lists (non empty, size indexed etc)
withEnum :: MonadError GQLError m => (TypeName -> m a) -> Value VALID -> m a
withEnum decode (Enum value) = decode value
withEnum _ isType = throwError (typeMismatch "Enum" isType)
withInputUnion ::
(MonadError GQLError m, Monad m) =>
(TypeName -> ValidObject -> ValidObject -> m a) ->
ValidObject ->
m a
withInputUnion decoder unions =
either onFail onSuccess (getInputUnionValue unions)
where
onSuccess (name, value) = withInputObject (decoder name unions) value
onFail = throwError . internal . msg
withScalar ::
(Applicative m, MonadError GQLError m) =>
TypeName ->
(ScalarValue -> Either Token a) ->
Value VALID ->
m a
withScalar typename decodeScalar value = case toScalar value >>= decodeScalar of
Right scalar -> pure scalar
Left message ->
throwError
( typeMismatch
("SCALAR(" <> msg typename <> ")" <> msg message)
value
)
decodeFieldWith :: (Value VALID -> m a) -> FieldName -> ValidObject -> m a
decodeFieldWith decoder = selectOr (decoder Null) (decoder . entryValue)
handleEither :: MonadError GQLError m => Either GQLError a -> m a
handleEither = either throwError pure
-- if value is already validated but value has different type
typeMismatch :: GQLError -> Value s -> GQLError
typeMismatch text jsType =
internal $
"Type mismatch! expected:"
<> text
<> ", got: "
<> msg jsType
getFieldName :: FieldName -> Int -> FieldName
getFieldName "" index = "_" <> show index
getFieldName label _ = label
data VariantKind = InlineVariant | VariantRef deriving (Eq, Ord)
data Info = Info
{ kind :: VariantKind,
tagName :: [TypeName]
}
instance Semigroup Info where
Info VariantRef t1 <> Info _ t2 = Info VariantRef (t1 <> t2)
Info _ t1 <> Info VariantRef t2 = Info VariantRef (t1 <> t2)
Info InlineVariant t1 <> Info InlineVariant t2 = Info InlineVariant (t1 <> t2)
data Context = Context
{ isVariantRef :: Bool,
typeName :: TypeName,
enumVisitor :: TypeName -> TypeName,
fieldVisitor :: FieldName -> FieldName
}
type DecoderT = ReaderT Context ResolverState
setVariantRef :: Bool -> DecoderT a -> DecoderT a
setVariantRef isVariantRef = local (\ctx -> ctx {isVariantRef})
class DescribeCons gql (f :: Type -> Type) where
tags :: UseGQLType gql -> Proxy f -> Context -> Info
instance (Datatype d, DescribeCons gql f) => DescribeCons gql (M1 D d f) where
tags ctx _ = tags ctx (Proxy @f)
instance (DescribeCons gql a, DescribeCons gql b) => DescribeCons gql (a :+: b) where
tags ctx _ = tags ctx (Proxy @a) <> tags ctx (Proxy @b)
instance (Constructor c, CountFields a, RefType gql a) => DescribeCons gql (M1 C c a) where
tags ctx _ Context {typeName} = getTag (refType ctx (Proxy @a))
where
getTag (Just memberRef)
| isUnionRef memberRef = Info {kind = VariantRef, tagName = [memberRef]}
| otherwise = Info {kind = InlineVariant, tagName = [consName]}
getTag Nothing = Info {kind = InlineVariant, tagName = [consName]}
--------
consName = conNameProxy (Proxy @c)
----------
isUnionRef x = typeName <> x == consName
getUnionInfos ::
forall f a b gql.
(DescribeCons gql a, DescribeCons gql b) =>
UseGQLType gql ->
f (a :+: b) ->
DecoderT (Bool, ([TypeName], [TypeName]))
getUnionInfos ctx _ = do
context <- ask
let l = tags ctx (Proxy @a) context
let r = tags ctx (Proxy @b) context
let k = kind (l <> r)
pure (k == VariantRef, (tagName l, tagName r))
class RefType gql (f :: Type -> Type) where
refType :: UseGQLType gql -> Proxy f -> Maybe TypeName
instance (RefType gql f, RefType gql g) => RefType gql (f :*: g) where
refType _ _ = Nothing
instance (Selector s, gql a) => RefType gql (M1 S s (K1 i a)) where
refType dir _ = Just $ useTypename dir (InputType :: CatType IN a)
instance RefType gql U1 where
refType _ _ = Nothing
class CountFields (f :: Type -> Type) where
countFields :: Proxy f -> Int
instance (CountFields f, CountFields g) => CountFields (f :*: g) where
countFields _ = countFields (Proxy @f) + countFields (Proxy @g)
instance (Selector s) => CountFields (M1 S s (K1 i a)) where
countFields _ = 1
instance CountFields U1 where
countFields _ = 0
useDecodeArguments :: val a => UseDeriving gql val -> Arguments VALID -> ResolverState a
useDecodeArguments drv = useDecodeValue (dirArgs drv) . argumentsToObject