mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-24 07:52:14 +03:00
316 lines
11 KiB
Haskell
316 lines
11 KiB
Haskell
module Hasura.GraphQL.Validate.InputValue
|
|
( validateInputValue
|
|
, jsonParser
|
|
, valueParser
|
|
, constValueParser
|
|
, pPrintValueC
|
|
) where
|
|
|
|
import Data.Scientific (fromFloatDigits)
|
|
import Hasura.Prelude
|
|
import Hasura.Server.Utils (duplicates)
|
|
|
|
import Data.Has
|
|
|
|
import qualified Data.Aeson as J
|
|
import qualified Data.HashMap.Strict as Map
|
|
import qualified Data.HashMap.Strict.InsOrd as OMap
|
|
import qualified Data.Text as T
|
|
import qualified Data.Vector as V
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import Hasura.GraphQL.Utils
|
|
import Hasura.GraphQL.Validate.Context
|
|
import Hasura.GraphQL.Validate.Types
|
|
import Hasura.RQL.Types
|
|
import Hasura.SQL.Value
|
|
|
|
newtype P a = P { unP :: Maybe (Either AnnGValue a)}
|
|
|
|
pNull :: (Monad m) => m (P a)
|
|
pNull = return $ P Nothing
|
|
|
|
pVal :: (Monad m) => a -> m (P a)
|
|
pVal = return . P . Just . Right
|
|
|
|
resolveVar
|
|
:: ( MonadError QErr m
|
|
, MonadReader ValidationCtx m)
|
|
=> G.Variable -> m AnnGValue
|
|
resolveVar var = do
|
|
varVals <- _vcVarVals <$> ask
|
|
-- TODO typecheck
|
|
onNothing (Map.lookup var varVals) $
|
|
throwVE $ "no such variable defined in the operation: "
|
|
<> showName (G.unVariable var)
|
|
where
|
|
typeCheck expectedTy actualTy = case (expectedTy, actualTy) of
|
|
-- named types
|
|
(G.TypeNamed _ eTy, G.TypeNamed _ aTy) -> eTy == aTy
|
|
-- list types
|
|
(G.TypeList _ eTy, G.TypeList _ aTy) -> typeCheck (G.unListType eTy) (G.unListType aTy)
|
|
(_, _) -> False
|
|
|
|
pVar
|
|
:: ( MonadError QErr m
|
|
, MonadReader ValidationCtx m)
|
|
=> G.Variable -> m (P a)
|
|
pVar var = do
|
|
annInpVal <- resolveVar var
|
|
return . P . Just . Left $ annInpVal
|
|
|
|
data InputValueParser a m
|
|
= InputValueParser
|
|
{ getScalar :: a -> m (P J.Value)
|
|
, getList :: a -> m (P [a])
|
|
, getObject :: a -> m (P [(G.Name, a)])
|
|
, getEnum :: a -> m (P G.EnumValue)
|
|
}
|
|
|
|
jsonParser :: (MonadError QErr m) => InputValueParser J.Value m
|
|
jsonParser =
|
|
InputValueParser jScalar jList jObject jEnum
|
|
where
|
|
jEnum (J.String t) = pVal $ G.EnumValue $ G.Name t
|
|
jEnum J.Null = pNull
|
|
jEnum _ = throwVE "expecting a JSON string for Enum"
|
|
|
|
jList (J.Array l) = pVal $ V.toList l
|
|
jList J.Null = pNull
|
|
jList v = pVal [v]
|
|
|
|
jObject (J.Object m) = pVal [(G.Name t, v) | (t, v) <- Map.toList m]
|
|
jObject J.Null = pNull
|
|
jObject _ = throwVE "expecting a JSON object"
|
|
|
|
jScalar J.Null = pNull
|
|
jScalar v = pVal v
|
|
|
|
toJValue :: (MonadError QErr m) => G.Value -> m J.Value
|
|
toJValue = \case
|
|
G.VVariable _ ->
|
|
throwVE "variables are not allowed in scalars"
|
|
G.VInt i -> return $ J.toJSON i
|
|
G.VFloat f -> return $ J.toJSON f
|
|
G.VString (G.StringValue t) -> return $ J.toJSON t
|
|
G.VBoolean b -> return $ J.toJSON b
|
|
G.VNull -> return J.Null
|
|
G.VEnum (G.EnumValue n) -> return $ J.toJSON n
|
|
G.VList (G.ListValueG vals) ->
|
|
J.toJSON <$> mapM toJValue vals
|
|
G.VObject (G.ObjectValueG objs) ->
|
|
J.toJSON . Map.fromList <$> mapM toTup objs
|
|
where
|
|
toTup (G.ObjectFieldG f v) = (f,) <$> toJValue v
|
|
|
|
valueParser
|
|
:: ( MonadError QErr m
|
|
, MonadReader ValidationCtx m)
|
|
=> InputValueParser G.Value m
|
|
valueParser =
|
|
InputValueParser pScalar pList pObject pEnum
|
|
where
|
|
pEnum (G.VVariable var) = pVar var
|
|
pEnum (G.VEnum e) = pVal e
|
|
pEnum G.VNull = pNull
|
|
pEnum _ = throwVE "expecting an enum"
|
|
|
|
pList (G.VVariable var) = pVar var
|
|
pList (G.VList lv) = pVal $ G.unListValue lv
|
|
pList G.VNull = pNull
|
|
pList v = pVal [v]
|
|
|
|
pObject (G.VVariable var) = pVar var
|
|
pObject (G.VObject ov) = pVal
|
|
[(G._ofName oFld, G._ofValue oFld) | oFld <- G.unObjectValue ov]
|
|
pObject G.VNull = pNull
|
|
pObject _ = throwVE "expecting an object"
|
|
|
|
-- scalar json
|
|
pScalar (G.VVariable var) = pVar var
|
|
pScalar G.VNull = pNull
|
|
pScalar (G.VInt v) = pVal $ J.Number $ fromIntegral v
|
|
pScalar (G.VFloat v) = pVal $ J.Number $ fromFloatDigits v
|
|
pScalar (G.VBoolean b) = pVal $ J.Bool b
|
|
pScalar (G.VString sv) = pVal $ J.String $ G.unStringValue sv
|
|
pScalar (G.VEnum _) = throwVE "unexpected enum for a scalar"
|
|
pScalar v = pVal =<< toJValue v
|
|
|
|
pPrintValueC :: G.ValueConst -> Text
|
|
pPrintValueC = \case
|
|
G.VCInt i -> T.pack $ show i
|
|
G.VCFloat f -> T.pack $ show f
|
|
G.VCString (G.StringValue t) -> T.pack $ show t
|
|
G.VCBoolean b -> bool "false" "true" b
|
|
G.VCNull -> "null"
|
|
G.VCEnum (G.EnumValue n) -> G.unName n
|
|
G.VCList (G.ListValueG vals) -> withSquareBraces $ T.intercalate ", " $ map pPrintValueC vals
|
|
G.VCObject (G.ObjectValueG objs) -> withCurlyBraces $ T.intercalate ", " $ map ppObjFld objs
|
|
where
|
|
ppObjFld (G.ObjectFieldG f v) = G.unName f <> ": " <> pPrintValueC v
|
|
withSquareBraces t = "[" <> t <> "]"
|
|
withCurlyBraces t = "{" <> t <> "}"
|
|
|
|
|
|
toJValueC :: G.ValueConst -> J.Value
|
|
toJValueC = \case
|
|
G.VCInt i -> J.toJSON i
|
|
G.VCFloat f -> J.toJSON f
|
|
G.VCString (G.StringValue t) -> J.toJSON t
|
|
G.VCBoolean b -> J.toJSON b
|
|
G.VCNull -> J.Null
|
|
G.VCEnum (G.EnumValue n) -> J.toJSON n
|
|
G.VCList (G.ListValueG vals) ->
|
|
J.toJSON $ map toJValueC vals
|
|
G.VCObject (G.ObjectValueG objs) ->
|
|
J.toJSON . OMap.fromList $ map toTup objs
|
|
where
|
|
toTup (G.ObjectFieldG f v) = (f, toJValueC v)
|
|
|
|
constValueParser :: (MonadError QErr m) => InputValueParser G.ValueConst m
|
|
constValueParser =
|
|
InputValueParser pScalar pList pObject pEnum
|
|
where
|
|
pEnum (G.VCEnum e) = pVal e
|
|
pEnum G.VCNull = pNull
|
|
pEnum _ = throwVE "expecting an enum"
|
|
|
|
pList (G.VCList lv) = pVal $ G.unListValue lv
|
|
pList G.VCNull = pNull
|
|
pList v = pVal [v]
|
|
|
|
pObject (G.VCObject ov) = pVal
|
|
[(G._ofName oFld, G._ofValue oFld) | oFld <- G.unObjectValue ov]
|
|
pObject G.VCNull = pNull
|
|
pObject _ = throwVE "expecting an object"
|
|
|
|
-- scalar json
|
|
pScalar G.VCNull = pNull
|
|
pScalar (G.VCInt v) = pVal $ J.Number $ fromIntegral v
|
|
pScalar (G.VCFloat v) = pVal $ J.Number $ fromFloatDigits v
|
|
pScalar (G.VCBoolean b) = pVal $ J.Bool b
|
|
pScalar (G.VCString sv) = pVal $ J.String $ G.unStringValue sv
|
|
pScalar (G.VCEnum _) = throwVE "unexpected enum for a scalar"
|
|
pScalar v = pVal $ toJValueC v
|
|
|
|
validateObject
|
|
:: ( MonadReader r m, Has TypeMap r
|
|
, MonadError QErr m
|
|
)
|
|
=> InputValueParser a m
|
|
-> InpObjTyInfo -> [(G.Name, a)] -> m AnnGObject
|
|
validateObject valParser tyInfo flds = do
|
|
|
|
-- check duplicates
|
|
unless (null dups) $
|
|
throwVE $ "when parsing a value of type: " <> showNamedTy (_iotiName tyInfo)
|
|
<> ", the following fields are duplicated: "
|
|
<> showNames dups
|
|
|
|
-- check fields with not null types
|
|
forM_ (Map.toList $ _iotiFields tyInfo) $
|
|
\(fldName, inpValInfo) -> do
|
|
let ty = _iviType inpValInfo
|
|
isNotNull = G.isNotNull ty
|
|
fldPresent = fldName `elem` inpFldNames
|
|
when (not fldPresent && isNotNull) $ throwVE $
|
|
"field " <> G.unName fldName <> " of type " <> G.showGT ty
|
|
<> " is required, but not found"
|
|
|
|
fmap OMap.fromList $ forM flds $ \(fldName, fldVal) ->
|
|
withPathK (G.unName fldName) $ do
|
|
fldTy <- getInpFieldInfo tyInfo fldName
|
|
convFldVal <- validateInputValue valParser fldTy fldVal
|
|
return (fldName, convFldVal)
|
|
|
|
where
|
|
inpFldNames = map fst flds
|
|
dups = duplicates inpFldNames
|
|
|
|
validateNamedTypeVal
|
|
:: ( MonadReader r m, Has TypeMap r
|
|
, MonadError QErr m)
|
|
=> InputValueParser a m
|
|
-> G.NamedType -> a -> m AnnGValue
|
|
validateNamedTypeVal inpValParser nt val = do
|
|
tyInfo <- getTyInfo nt
|
|
case tyInfo of
|
|
-- this should never happen
|
|
TIObj _ ->
|
|
throwUnexpTypeErr "object"
|
|
TIIFace _ ->
|
|
throwUnexpTypeErr "interface"
|
|
TIUnion _ ->
|
|
throwUnexpTypeErr "union"
|
|
TIInpObj ioti ->
|
|
withParsed (getObject inpValParser) val $
|
|
fmap (AGObject nt) . mapM (validateObject inpValParser ioti)
|
|
TIEnum eti ->
|
|
withParsed (getEnum inpValParser) val $
|
|
fmap (AGEnum nt) . mapM (validateEnum eti)
|
|
TIScalar (ScalarTyInfo _ pgColTy _) ->
|
|
withParsed (getScalar inpValParser) val $
|
|
fmap (AGScalar pgColTy) . mapM (validateScalar pgColTy)
|
|
where
|
|
throwUnexpTypeErr ty = throw500 $ "unexpected " <> ty <> " type info for: "
|
|
<> showNamedTy nt
|
|
validateEnum enumTyInfo enumVal =
|
|
if Map.member enumVal (_etiValues enumTyInfo)
|
|
then return enumVal
|
|
else throwVE $ "unexpected value " <>
|
|
showName (G.unEnumValue enumVal) <>
|
|
" for enum: " <> showNamedTy nt
|
|
validateScalar pgColTy =
|
|
runAesonParser (parsePGValue pgColTy)
|
|
|
|
validateList
|
|
:: (MonadError QErr m, MonadReader r m, Has TypeMap r)
|
|
=> InputValueParser a m
|
|
-> G.ListType
|
|
-> a
|
|
-> m AnnGValue
|
|
validateList inpValParser listTy val =
|
|
withParsed (getList inpValParser) val $ \lM -> do
|
|
let baseTy = G.unListType listTy
|
|
AGArray listTy <$>
|
|
mapM (indexedMapM (validateInputValue inpValParser baseTy)) lM
|
|
|
|
-- validateNonNull
|
|
-- :: (MonadError QErr m, MonadReader r m, Has TypeMap r)
|
|
-- => InputValueParser a m
|
|
-- -> G.NonNullType
|
|
-- -> a
|
|
-- -> m AnnGValue
|
|
-- validateNonNull inpValParser nonNullTy val = do
|
|
-- parsedVal <- case nonNullTy of
|
|
-- G.NonNullTypeNamed nt -> validateNamedTypeVal inpValParser nt val
|
|
-- G.NonNullTypeList lt -> validateList inpValParser lt val
|
|
-- when (hasNullVal parsedVal) $
|
|
-- throwVE $ "unexpected null value for type: " <> G.showGT (G.TypeNonNull nonNullTy)
|
|
-- return parsedVal
|
|
|
|
validateInputValue
|
|
:: (MonadError QErr m, MonadReader r m, Has TypeMap r)
|
|
=> InputValueParser a m
|
|
-> G.GType
|
|
-> a
|
|
-> m AnnGValue
|
|
validateInputValue inpValParser ty val =
|
|
case ty of
|
|
G.TypeNamed _ nt -> validateNamedTypeVal inpValParser nt val
|
|
G.TypeList _ lt -> validateList inpValParser lt val
|
|
--G.TypeNonNull nnt -> validateNonNull inpValParser nnt val
|
|
|
|
withParsed
|
|
:: (Monad m)
|
|
=> (val -> m (P specificVal))
|
|
-> val
|
|
-> (Maybe specificVal -> m AnnGValue)
|
|
-> m AnnGValue
|
|
withParsed valParser val fn = do
|
|
parsedVal <- valParser val
|
|
case unP parsedVal of
|
|
Nothing -> fn Nothing
|
|
Just (Right a) -> fn $ Just a
|
|
Just (Left annVal) -> return annVal
|