{-# language DataKinds #-} {-# language FlexibleContexts #-} {-# language FlexibleInstances #-} {-# language MultiParamTypeClasses #-} {-# language OverloadedStrings #-} {-# language ScopedTypeVariables #-} {-# language TypeApplications #-} {-# language TypeFamilies #-} {-# language TypeOperators #-} {-# language UndecidableInstances #-} module Mu.GraphQL.Query.Introspection where import Control.Monad.Writer import qualified Data.Aeson as JSON import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as S import Data.Int (Int32) import Data.Maybe (catMaybes, fromMaybe) import Data.Proxy import qualified Data.Text as T import GHC.TypeLits import Mu.Rpc import qualified Mu.Schema as Mu type TypeMap = HM.HashMap T.Text Type data Schema = Schema { queryType :: Maybe T.Text , mutationType :: Maybe T.Text , subscriptionType :: Maybe T.Text , types :: TypeMap } deriving Show data Type = Type { kind :: TypeKind , typeName :: Maybe T.Text , fields :: [Field] , enumValues :: [EnumValue] , possibleTypes :: [Type] , ofType :: Maybe Type } | TypeRef { to :: T.Text } deriving Show data Field = Field { fieldName :: T.Text , args :: [Input] , fieldType :: Type } deriving Show data Input = Input { inputName :: T.Text , inputDefaultValue :: Maybe T.Text , inputType :: Type } deriving Show newtype EnumValue = EnumValue { enumValueName :: T.Text } deriving Show data TypeKind = SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | LIST | NON_NULL deriving Show tSimple :: T.Text -> Type tSimple t = Type SCALAR (Just t) [] [] [] Nothing tList :: Type -> Type tList = Type LIST Nothing [] [] [] . Just tNonNull :: Type -> Type tNonNull = Type NON_NULL Nothing [] [] [] . Just unwrapNonNull :: Type -> Maybe Type unwrapNonNull (Type NON_NULL _ _ _ _ x) = x unwrapNonNull _ = Nothing -- BUILD INTROSPECTION DATA -- ======================== class Introspect (p :: Package') (qr :: Maybe Symbol) (mut :: Maybe Symbol) (sub :: Maybe Symbol) where introspect :: Proxy p -> Proxy qr -> Proxy mut -> Proxy sub -> Schema instance ( IntrospectServices ss sub , KnownMaybeSymbol qr , KnownMaybeSymbol mut , KnownMaybeSymbol sub) => Introspect ('Package nm ss) qr mut sub where introspect _ _ _ _ = let (_, ts) = runWriter $ introspectServices (Proxy @ss) (Proxy @sub) >> tell (HM.fromList ( (\i -> (i, tSimple i)) <$> [ "Null", "Int", "Float" , "String", "Boolean", "ID" , "JSON", "JSONObject" ] )) -- return only reachable types qrS = maybeSymbolVal (Proxy @qr) mutS = maybeSymbolVal (Proxy @mut) subS = maybeSymbolVal (Proxy @sub) initials = S.fromList $ catMaybes [qrS, mutS, subS] reach = reachableFrom ts initials -- finalTs = HM.filterWithKey (\k _ -> k `S.member` reach) ts in Schema qrS mutS subS finalTs reachableFrom :: TypeMap -> S.HashSet T.Text -> S.HashSet T.Text reachableFrom mp tys = let tys' = S.toList tys fromThis = S.fromList . reachableFromOne <$> tys' allReachable = S.unions fromThis in if tys == allReachable then tys else reachableFrom mp allReachable where reachableFromOne :: T.Text -> [T.Text] reachableFromOne t = case HM.lookup t mp of Just ty@Type {} -> t : concatMap reachableFromField (fields ty) _ -> error "this should never happen" reachableFromField :: Field -> [T.Text] reachableFromField f = reachableFromType (fieldType f) ++ concatMap reachableFromInput (args f) reachableFromInput :: Input -> [T.Text] reachableFromInput i = reachableFromType (inputType i) reachableFromType :: Type -> [T.Text] reachableFromType (TypeRef t) = [t] reachableFromType t@Type {} = case ofType t of Just t' -> reachableFromType t' Nothing -> case typeName t of Just tn -> [tn] Nothing -> [] class KnownMaybeSymbol (s :: Maybe Symbol) where maybeSymbolVal :: Proxy s -> Maybe T.Text instance KnownSymbol s => KnownMaybeSymbol ('Just s) where maybeSymbolVal _ = Just $ T.pack $ symbolVal (Proxy @s) instance KnownMaybeSymbol 'Nothing where maybeSymbolVal _ = Nothing type family IsSub (sname :: Symbol) (sub :: Maybe Symbol) :: Bool where IsSub sname 'Nothing = 'False IsSub sname ('Just sname) = 'True IsSub sname ('Just other) = 'False class IntrospectServices (ss :: [Service']) (sub :: Maybe Symbol) where introspectServices :: Proxy ss -> Proxy sub -> Writer TypeMap () instance IntrospectServices '[] sub where introspectServices _ _ = pure () instance ( KnownSymbol sname , IntrospectFields smethods (IsSub sname sub) , IntrospectServices ss sub ) => IntrospectServices ('Service sname smethods ': ss) sub where introspectServices _ psub = do let name = T.pack $ symbolVal (Proxy @sname) fs <- introspectFields (Proxy @smethods) (Proxy @(IsSub sname sub)) let t = Type OBJECT (Just name) fs [] [] Nothing -- add this one to the mix tell (HM.singleton name t) -- continue with the rest introspectServices (Proxy @ss) psub instance ( KnownSymbol sname, KnownSymbols elts , IntrospectServices ss sub ) => IntrospectServices ('OneOf sname elts ': ss) sub where introspectServices _ psub = do let name = T.pack $ symbolVal (Proxy @sname) tys = map tSimple (symbolsVal (Proxy @elts)) t = Type UNION (Just name) [] [] tys Nothing -- add this one to the mix tell (HM.singleton name t) -- continue with the rest introspectServices (Proxy @ss) psub class KnownSymbols (ss :: [Symbol]) where symbolsVal :: Proxy ss -> [T.Text] instance KnownSymbols '[] where symbolsVal _ = [] instance (KnownSymbol s, KnownSymbols ss) => KnownSymbols (s ': ss) where symbolsVal _ = T.pack (symbolVal (Proxy @s)) : symbolsVal (Proxy @ss) class IntrospectFields (fs :: [Method']) (isSub :: Bool) where introspectFields :: Proxy fs -> Proxy isSub -> Writer TypeMap [Field] instance IntrospectFields '[] isSub where introspectFields _ _ = pure [] instance ( KnownSymbol mname , IntrospectInputs margs , IntrospectReturn mret isSub , IntrospectFields fs isSub) => IntrospectFields ('Method mname margs mret ': fs) isSub where introspectFields _ pIsSub = do let name = T.pack $ symbolVal (Proxy @mname) inputs <- introspectInputs (Proxy @margs) ret <- introspectReturn (Proxy @mret) pIsSub let this = Field name inputs ret (this :) <$> introspectFields (Proxy @fs) pIsSub class IntrospectInputs (args :: [Argument']) where introspectInputs :: Proxy args -> Writer TypeMap [Input] instance IntrospectInputs '[] where introspectInputs _ = pure [] instance ( KnownMaybeSymbol nm , IntrospectTypeRef r , IntrospectInputs args ) => IntrospectInputs ('ArgSingle nm r ': args) where introspectInputs _ = do let nm = maybeSymbolVal (Proxy @nm) t <- introspectTypeRef (Proxy @r) False -- TODO: Find default value let this = Input (fromMaybe "arg" nm) Nothing t (this :) <$> introspectInputs (Proxy @args) instance ( KnownMaybeSymbol nm , IntrospectTypeRef r , IntrospectInputs args ) => IntrospectInputs ('ArgStream nm r ': args) where introspectInputs _ = do let nm = maybeSymbolVal (Proxy @nm) t <- tList <$> introspectTypeRef (Proxy @r) False -- TODO: Find default value let this = Input (fromMaybe "arg" nm) Nothing t (this :) <$> introspectInputs (Proxy @args) class IntrospectReturn (r :: Return Symbol (TypeRef Symbol)) (isSub :: Bool) where introspectReturn :: Proxy r -> Proxy isSub -> Writer TypeMap Type instance IntrospectReturn 'RetNothing isSub where introspectReturn _ _ = pure $ tSimple "Null" instance IntrospectTypeRef t => IntrospectReturn ('RetSingle t) isSub where introspectReturn _ _ = introspectTypeRef (Proxy @t) True instance IntrospectTypeRef t => IntrospectReturn ('RetStream t) 'False where introspectReturn _ _ = tList <$> introspectTypeRef (Proxy @t) True instance IntrospectTypeRef t => IntrospectReturn ('RetStream t) 'True where introspectReturn _ _ = introspectTypeRef (Proxy @t) True class IntrospectTypeRef (tr :: TypeRef Symbol) where introspectTypeRef :: Proxy tr -> Bool -> Writer TypeMap Type instance IntrospectTypeRef ('PrimitiveRef Bool) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Boolean" instance IntrospectTypeRef ('PrimitiveRef Int32) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Int" instance IntrospectTypeRef ('PrimitiveRef Integer) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Int" instance IntrospectTypeRef ('PrimitiveRef Double) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "Float" instance IntrospectTypeRef ('PrimitiveRef String) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "String" instance IntrospectTypeRef ('PrimitiveRef T.Text) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "String" instance IntrospectTypeRef ('PrimitiveRef JSON.Value) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "JSON" instance IntrospectTypeRef ('PrimitiveRef JSON.Object) where introspectTypeRef _ _ = pure $ tNonNull $ tSimple "JSONObject" instance (IntrospectTypeRef r) => IntrospectTypeRef ('ListRef r) where introspectTypeRef _ isRet = tList <$> introspectTypeRef (Proxy @r) isRet instance (IntrospectTypeRef r) => IntrospectTypeRef ('OptionalRef r) where introspectTypeRef _ isRet = do r <- introspectTypeRef (Proxy @r) isRet pure $ fromMaybe r (unwrapNonNull r) instance (KnownSymbol o) => IntrospectTypeRef ('ObjectRef o) where introspectTypeRef _ _ = pure $ TypeRef $ T.pack $ symbolVal (Proxy @o) instance (IntrospectSchema sch, KnownSymbol t) => IntrospectTypeRef ('SchemaRef sch t) where introspectTypeRef _ isRet = do let (k, suffix) = if isRet then (OBJECT, "R") else (INPUT_OBJECT, "") introspectSchema k suffix (Proxy @sch) pure $ TypeRef $ T.pack (symbolVal (Proxy @t)) <> suffix class IntrospectSchema (ts :: [Mu.TypeDef Symbol Symbol]) where introspectSchema :: TypeKind -> T.Text -> Proxy ts -> Writer TypeMap () instance IntrospectSchema '[] where introspectSchema _ _ _ = pure () instance (KnownSymbol name, IntrospectSchemaFields fields, IntrospectSchema ts) => IntrospectSchema ('Mu.DRecord name fields ': ts) where introspectSchema k suffix _ = do let name = T.pack (symbolVal (Proxy @name)) <> suffix fs = introspectSchemaFields suffix (Proxy @fields) t = Type k (Just name) fs [] [] Nothing -- add this one to the mix tell (HM.singleton name t) -- continue with the rest introspectSchema k suffix (Proxy @ts) instance (KnownSymbol name, IntrospectSchemaEnum choices, IntrospectSchema ts) => IntrospectSchema ('Mu.DEnum name choices ': ts) where introspectSchema k suffix _ = do let name = T.pack (symbolVal (Proxy @name)) <> suffix cs = introspectSchemaEnum (Proxy @choices) t = Type ENUM (Just name) [] cs [] Nothing -- add this one to the mix tell (HM.singleton name t) -- continue with the rest introspectSchema k suffix (Proxy @ts) class IntrospectSchemaFields (fs :: [Mu.FieldDef Symbol Symbol]) where introspectSchemaFields :: T.Text -> Proxy fs -> [Field] instance IntrospectSchemaFields '[] where introspectSchemaFields _ _ = [] instance (KnownSymbol fname,IntrospectSchemaFieldType r, IntrospectSchemaFields fs) => IntrospectSchemaFields ('Mu.FieldDef fname r ': fs) where introspectSchemaFields suffix _ = let name = T.pack $ symbolVal (Proxy @fname) ret = introspectSchemaFieldType suffix (Proxy @r) this = Field name [] ret in this : introspectSchemaFields suffix (Proxy @fs) class IntrospectSchemaFieldType (t :: Mu.FieldType Symbol) where introspectSchemaFieldType :: T.Text -> Proxy t -> Type instance IntrospectSchemaFieldType ('Mu.TPrimitive Bool) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "Boolean" instance IntrospectSchemaFieldType ('Mu.TPrimitive Int32) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "Int" instance IntrospectSchemaFieldType ('Mu.TPrimitive Integer) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "Int" instance IntrospectSchemaFieldType ('Mu.TPrimitive Double) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "Float" instance IntrospectSchemaFieldType ('Mu.TPrimitive String) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "String" instance IntrospectSchemaFieldType ('Mu.TPrimitive T.Text) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "String" instance IntrospectSchemaFieldType ('Mu.TPrimitive JSON.Value) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "JSON" instance IntrospectSchemaFieldType ('Mu.TPrimitive JSON.Object) where introspectSchemaFieldType _ _ = tNonNull $ tSimple "JSONObject" instance (IntrospectSchemaFieldType r) => IntrospectSchemaFieldType ('Mu.TList r) where introspectSchemaFieldType suffix _ = tList $ introspectSchemaFieldType suffix (Proxy @r) instance (IntrospectSchemaFieldType r) => IntrospectSchemaFieldType ('Mu.TOption r) where introspectSchemaFieldType suffix _ = let r = introspectSchemaFieldType suffix (Proxy @r) in fromMaybe r (unwrapNonNull r) instance (KnownSymbol nm) => IntrospectSchemaFieldType ('Mu.TSchematic nm) where introspectSchemaFieldType suffix _ = TypeRef $ T.pack (symbolVal (Proxy @nm)) <> suffix class IntrospectSchemaEnum (c :: [Mu.ChoiceDef Symbol]) where introspectSchemaEnum :: Proxy c -> [EnumValue] instance IntrospectSchemaEnum '[] where introspectSchemaEnum _ = [] instance (KnownSymbol nm, IntrospectSchemaEnum cs) => IntrospectSchemaEnum ('Mu.ChoiceDef nm ': cs) where introspectSchemaEnum _ = let this = EnumValue $ T.pack $ symbolVal (Proxy @nm) in this : introspectSchemaEnum (Proxy @cs)