{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Hasura.GraphQL.Resolve.Introspect ( schemaR , typeR ) where import Data.Has import Hasura.Prelude import qualified Data.Aeson as J import qualified Data.Text as T import qualified Data.HashMap.Strict as Map import qualified Language.GraphQL.Draft.Syntax as G import Hasura.GraphQL.Resolve.Context import Hasura.GraphQL.Resolve.InputValue import Hasura.GraphQL.Validate.Context import Hasura.GraphQL.Validate.Field import Hasura.GraphQL.Validate.Types import Hasura.RQL.Types import Hasura.SQL.Value data TypeKind = TKSCALAR | TKOBJECT | TKINTERFACE | TKUNION | TKENUM | TKINPUT_OBJECT | TKLIST | TKNON_NULL deriving (Show, Eq) instance J.ToJSON TypeKind where toJSON = J.toJSON . T.pack . drop 2 . show withSubFields :: (Monad m) => SelSet -> (Field -> m J.Value) -> m J.Object withSubFields selSet fn = fmap Map.fromList $ forM (toList selSet) $ \fld -> do val <- fn fld return (G.unName $ G.unAlias $ _fAlias fld, val) namedTyToTxt :: G.NamedType -> Text namedTyToTxt = G.unName . G.unNamedType retJ :: (Applicative m, J.ToJSON a) => a -> m J.Value retJ = pure . J.toJSON retJT :: (Applicative m) => Text -> m J.Value retJT = pure . J.toJSON -- 4.5.2.1 scalarR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => ScalarTyInfo -> Field -> m J.Object scalarR (ScalarTyInfo descM pgColType) fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Type" "kind" -> retJ TKSCALAR "description" -> retJ $ fmap G.unDescription descM "name" -> retJ $ pgColTyToScalar pgColType _ -> return J.Null -- 4.5.2.2 objectTypeR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => ObjTyInfo -> Field -> m J.Object objectTypeR (ObjTyInfo descM n flds) fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Type" "kind" -> retJ TKOBJECT "name" -> retJ $ namedTyToTxt n "description" -> retJ $ fmap G.unDescription descM "interfaces" -> retJ ([] :: [()]) "fields" -> fmap J.toJSON $ mapM (`fieldR` subFld) $ sortBy (comparing _fiName) $ filter notBuiltinFld $ Map.elems flds _ -> return J.Null notBuiltinFld :: ObjFldInfo -> Bool notBuiltinFld f = fldName /= "__typename" && fldName /= "__type" && fldName /= "__schema" where fldName = _fiName f -- 4.5.2.5 enumTypeR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => EnumTyInfo -> Field -> m J.Object enumTypeR (EnumTyInfo descM n vals) fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Type" "kind" -> retJ TKENUM "name" -> retJ $ namedTyToTxt n "description" -> retJ $ fmap G.unDescription descM "enumValues" -> fmap J.toJSON $ mapM (enumValueR subFld) $ sortBy (comparing _eviVal) $ Map.elems vals _ -> return J.Null -- 4.5.2.6 inputObjR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => InpObjTyInfo -> Field -> m J.Object inputObjR (InpObjTyInfo descM nt flds) fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Type" "kind" -> retJ TKINPUT_OBJECT "name" -> retJ $ namedTyToTxt nt "description" -> retJ $ fmap G.unDescription descM "inputFields" -> fmap J.toJSON $ mapM (inputValueR subFld) $ sortBy (comparing _iviName) $ Map.elems flds _ -> return J.Null -- 4.5.2.7 listTypeR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => G.ListType -> Field -> m J.Object listTypeR (G.ListType ty) fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Type" "kind" -> retJ TKLIST "ofType" -> J.toJSON <$> gtypeR ty subFld _ -> return J.Null -- 4.5.2.8 nonNullR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => G.NonNullType -> Field -> m J.Object nonNullR nnt fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Type" "kind" -> retJ TKNON_NULL "ofType" -> case nnt of G.NonNullTypeNamed nt -> J.toJSON <$> namedTypeR nt subFld G.NonNullTypeList lt -> J.toJSON <$> listTypeR lt subFld _ -> return J.Null namedTypeR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => G.NamedType -> Field -> m J.Object namedTypeR nt fld = do tyInfo <- getTyInfo nt namedTypeR' fld tyInfo namedTypeR' :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => Field -> TypeInfo -> m J.Object namedTypeR' fld = \case TIScalar colTy -> scalarR colTy fld TIObj objTyInfo -> objectTypeR objTyInfo fld TIEnum enumTypeInfo -> enumTypeR enumTypeInfo fld TIInpObj inpObjTyInfo -> inputObjR inpObjTyInfo fld -- 4.5.3 fieldR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => ObjFldInfo -> Field -> m J.Object fieldR (ObjFldInfo descM n params ty) fld = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Field" "name" -> retJ $ G.unName n "description" -> retJ $ fmap G.unDescription descM "args" -> fmap J.toJSON $ mapM (inputValueR subFld) $ sortBy (comparing _iviName) $ Map.elems params "type" -> J.toJSON <$> gtypeR ty subFld "isDeprecated" -> retJ False _ -> return J.Null -- 4.5.4 inputValueR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => Field -> InpValInfo -> m J.Object inputValueR fld (InpValInfo descM n ty) = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__InputValue" "name" -> retJ $ G.unName n "description" -> retJ $ fmap G.unDescription descM "type" -> J.toJSON <$> gtypeR ty subFld -- TODO: figure out what the spec means by 'string encoding' "defaultValue" -> return J.Null _ -> return J.Null -- 4.5.5 enumValueR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => Field -> EnumValInfo -> m J.Object enumValueR fld (EnumValInfo descM enumVal isDeprecated) = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__EnumValue" "name" -> retJ $ G.unName $ G.unEnumValue enumVal "description" -> retJ $ fmap G.unDescription descM "isDeprecated" -> retJ isDeprecated _ -> return J.Null -- 4.5.6 directiveR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => Field -> DirectiveInfo -> m J.Object directiveR fld (DirectiveInfo descM n args locs) = withSubFields (_fSelSet fld) $ \subFld -> case _fName subFld of "__typename" -> retJT "__Directive" "name" -> retJ $ G.unName n "description" -> retJ $ fmap G.unDescription descM "locations" -> retJ $ map showDirLoc locs "args" -> fmap J.toJSON $ mapM (inputValueR subFld) $ sortBy (comparing _iviName) $ Map.elems args _ -> return J.Null showDirLoc :: G.DirectiveLocation -> Text showDirLoc = \case G.DLExecutable edl -> T.pack $ drop 3 $ show edl G.DLTypeSystem tsdl -> T.pack $ drop 4 $ show tsdl gtypeR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => G.GType -> Field -> m J.Object gtypeR ty fld = case ty of G.TypeList lt -> listTypeR lt fld G.TypeNonNull nnt -> nonNullR nnt fld G.TypeNamed nt -> namedTypeR nt fld schemaR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => Field -> m J.Object schemaR fld = withSubFields (_fSelSet fld) $ \subFld -> do (tyMap :: TypeMap) <- asks getter case _fName subFld of "__typename" -> retJT "__Schema" "types" -> fmap J.toJSON $ mapM (namedTypeR' subFld) $ sortBy (comparing getNamedTy) $ Map.elems tyMap "queryType" -> J.toJSON <$> namedTypeR (G.NamedType "query_root") subFld "mutationType" -> typeR' "mutation_root" subFld "directives" -> J.toJSON <$> mapM (directiveR subFld) (sortBy (comparing _diName) defaultDirectives) _ -> return J.Null typeR :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => Field -> m J.Value typeR fld = do name <- withArg args "name" $ \arg -> do (_, pgColVal) <- asPGColVal arg case pgColVal of PGValText t -> return t _ -> throw500 "expecting string for name arg of __type" typeR' (G.Name name) fld where args = _fArguments fld typeR' :: ( MonadReader r m, Has TypeMap r , MonadError QErr m) => G.Name -> Field -> m J.Value typeR' n fld = do tyMap <- asks getter case Map.lookup (G.NamedType n) tyMap of Nothing -> return J.Null Just tyInfo -> J.Object <$> namedTypeR' fld tyInfo