morpheus-graphql-0.21.0: src/Data/Morpheus/Server/Deriving/Utils/Decode.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.Morpheus.Server.Deriving.Utils.Decode
( withInputObject,
withEnum,
withInputUnion,
decodeFieldWith,
withScalar,
handleEither,
getFieldName,
DecoderT,
withKind,
Info (..),
Context (..),
Tag (..),
)
where
import Control.Monad.Except (MonadError (throwError))
import Data.Morpheus.App.Internal.Resolving (ResolverState)
import Data.Morpheus.Internal.Utils
( selectOr,
)
import Data.Morpheus.Server.Types.Internal
import Data.Morpheus.Types.GQLScalar
( toScalar,
)
import Data.Morpheus.Types.Internal.AST
( FieldName,
GQLError,
Msg (msg),
ObjectEntry (..),
ScalarValue,
Token,
TypeName,
VALID,
ValidObject,
ValidValue,
Value (..),
getInputUnionValue,
internal,
)
import Relude
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 Tag = D_CONS | D_UNION deriving (Eq, Ord)
data Info = Info
{ kind :: Tag,
tagName :: [TypeName]
}
instance Semigroup Info where
Info D_UNION t1 <> Info _ t2 = Info D_UNION (t1 <> t2)
Info _ t1 <> Info D_UNION t2 = Info D_UNION (t1 <> t2)
Info D_CONS t1 <> Info D_CONS t2 = Info D_CONS (t1 <> t2)
data Context = Context
{ contKind :: Tag,
typeName :: TypeName,
options :: GQLTypeOptions
}
type DecoderT = ReaderT Context ResolverState
withKind :: Tag -> DecoderT a -> DecoderT a
withKind contKind = local (\ctx -> ctx {contKind})