sangria.schema.IntrospectionSchemaMaterializer.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sangria-core_2.13 Show documentation
Show all versions of sangria-core_2.13 Show documentation
Scala GraphQL implementation
The newest version!
package sangria.schema
import sangria.introspection._
import sangria.marshalling._
import sangria.renderer.SchemaRenderer
import sangria.util.Cache
import scala.util.{Failure, Success}
class IntrospectionSchemaMaterializer[Ctx, T: InputUnmarshaller](
introspectionResult: T,
builder: IntrospectionSchemaBuilder[Ctx]) {
private val typeDefCache = Cache.empty[String, Type with Named]
private lazy val schemaDef = IntrospectionParser.parse(introspectionResult).get
lazy val build: Schema[Ctx, Any] = {
val queryType = getObjectType(schemaDef.queryType)
val mutationType = schemaDef.mutationType.map(getObjectType)
val subscriptionType = schemaDef.subscriptionType.map(getObjectType)
val directives = (schemaDef.directives.toList ++ builder.additionalDirectiveDefs)
.filterNot(d => Schema.isBuiltInDirective(d.name))
.flatMap(buildDirective)
builder.buildSchema(
schemaDef,
queryType,
mutationType,
subscriptionType,
findUnusedTypes(schemaDef.types ++ builder.additionalTypeDefs),
BuiltinDirectives ++ directives,
this)
}
def findUnusedTypes(allTypes: Seq[IntrospectionType]): List[Type with Named] = {
// first init all lazy fields. TODO: think about better solution
typeDefCache.forEachValue {
case o: ObjectLikeType[_, _] => o.fields
case o: InputObjectType[_] => o.fields
case _ => // do nothing
}
val referenced = typeDefCache
val notReferenced =
allTypes.filterNot(tpe => Schema.isBuiltInType(tpe.name) || referenced.contains(tpe.name))
notReferenced.toList.map(tpe => getNamedType(tpe.name))
}
def buildDirective(directive: IntrospectionDirective) =
BuiltinDirectives
.find(_.name == directive.name)
.orElse(builder
.buildDirective(directive, directive.args.toList.flatMap(buildArgument(None, _)), this))
def getObjectType(typeRef: IntrospectionTypeRef): ObjectType[Ctx, Any] =
getOutputType(typeRef, false) match {
case obj: ObjectType[_, _] => obj.asInstanceOf[ObjectType[Ctx, Any]]
case _ =>
throw new SchemaMaterializationException(
s"Type '${SchemaRenderer.renderTypeName(typeRef)}' is not an object type.")
}
def getInterfaceType(typeRef: IntrospectionTypeRef) =
getOutputType(typeRef, false) match {
case obj: InterfaceType[_, _] => obj.asInstanceOf[InterfaceType[Ctx, Any]]
case _ =>
throw new SchemaMaterializationException(
s"Type '${SchemaRenderer.renderTypeName(typeRef)}' is not an interface type.")
}
def getInputType(typeRef: IntrospectionTypeRef, optional: Boolean = true): InputType[_] =
typeRef match {
case IntrospectionListTypeRef(ofType) if optional =>
OptionInputType(ListInputType(getInputType(ofType, true)))
case IntrospectionListTypeRef(ofType) => ListInputType(getInputType(ofType, true))
case IntrospectionNonNullTypeRef(ofType) => getInputType(ofType, false)
case IntrospectionNamedTypeRef(_, name) =>
getNamedType(name) match {
case input: InputType[_] if optional => OptionInputType(input)
case input: InputType[_] => input
case _ =>
throw new SchemaMaterializationException(
s"Type '$name' is not an input type, but was used in input type position!")
}
}
def getOutputType(typeRef: IntrospectionTypeRef, optional: Boolean = true): OutputType[_] =
typeRef match {
case IntrospectionListTypeRef(ofType) if optional =>
OptionType(ListType(getOutputType(ofType, true)))
case IntrospectionListTypeRef(ofType) => ListType(getOutputType(ofType, true))
case IntrospectionNonNullTypeRef(ofType) => getOutputType(ofType, false)
case IntrospectionNamedTypeRef(_, name) =>
getNamedType(name) match {
case input: OutputType[_] if optional => OptionType(input)
case input: OutputType[_] => input
case _ =>
throw new SchemaMaterializationException(
s"Type '$name' is not an output type, but was used in output type position!")
}
}
def getNamedType(typeName: String): Type with Named =
typeDefCache.getOrElseUpdate(
typeName,
Schema
.getBuiltInType(typeName)
.getOrElse(
schemaDef.types
.find(_.name == typeName)
.flatMap(buildType)
.getOrElse(throw new SchemaMaterializationException(
s"Invalid or incomplete schema, unknown type: $typeName. Ensure that a full introspection query is used in order to build a client schema.")))
)
def buildType(tpe: IntrospectionType): Option[Type with Named] = tpe match {
case o: IntrospectionObjectType => buildObjectDef(o)
case i: IntrospectionInterfaceType => buildInterfaceDef(i)
case u: IntrospectionUnionType => buildUnionDef(u)
case io: IntrospectionInputObjectType => buildInputObjectDef(io)
case s: IntrospectionScalarType => buildScalarDef(s)
case e: IntrospectionEnumType => buildEnumDef(e)
}
def buildField(typeDef: IntrospectionType, field: IntrospectionField) =
builder.buildField(
typeDef,
field,
getOutputType(field.tpe),
field.args.toList.flatMap(buildArgument(Some(field), _)),
this)
def buildObjectDef(tpe: IntrospectionObjectType) =
builder.buildObjectType(
tpe,
() => tpe.fields.toList.flatMap(buildField(tpe, _)),
tpe.interfaces.toList.map(getInterfaceType),
this)
def buildInterfaceDef(tpe: IntrospectionInterfaceType) =
builder.buildInterfaceType(
tpe,
() => tpe.fields.toList.flatMap(buildField(tpe, _)),
tpe.interfaces.toList.map(getInterfaceType),
this)
def buildUnionDef(tpe: IntrospectionUnionType) =
builder.buildUnionType(tpe, tpe.possibleTypes.toList.map(getObjectType), this)
def buildInputObjectDef(tpe: IntrospectionInputObjectType) =
builder.buildInputObjectType(
tpe,
() => tpe.inputFields.toList.flatMap(buildInputField(tpe, _)),
this)
def buildScalarDef(tpe: IntrospectionScalarType) =
builder.buildScalarType(tpe, this)
def buildEnumDef(tpe: IntrospectionEnumType) =
builder.buildEnumType(tpe, tpe.enumValues.toList.flatMap(buildEnumValue(tpe, _)), this)
def buildEnumValue(tpe: IntrospectionEnumType, value: IntrospectionEnumValue) =
builder.buildEnumValue(tpe, value, this)
def buildDefault(defaultValue: Option[String]) =
defaultValue.map(dv =>
sangria.marshalling.queryAst.QueryAstInputParser.parse(dv) match {
case Success(parsed) => parsed -> sangria.marshalling.queryAst.queryAstToInput
case Failure(error) =>
throw new SchemaMaterializationException(s"Unable to parse default value '$dv'.", error)
})
def buildArgument(fieldDef: Option[IntrospectionField], value: IntrospectionInputValue) =
builder.buildArgument(
fieldDef,
value,
getInputType(value.tpe),
buildDefault(value.defaultValue),
this)
def buildInputField(tpe: IntrospectionInputObjectType, value: IntrospectionInputValue) =
builder.buildInputField(
tpe,
value,
getInputType(value.tpe),
buildDefault(value.defaultValue),
this)
}
object IntrospectionSchemaMaterializer {
/** Build a `Schema` for use by client tools.
*
* Given the result of a client running the introspection query, creates and returns a `Schema`
* instance which can be then used with all sangria tools, but cannot be used to execute a query,
* as introspection does not represent the "resolver", "parse" or "serialize" functions or any
* other server-internal mechanisms.
*
* @param introspectionResult
* the result of introspection query
*/
def buildSchema[T: InputUnmarshaller](introspectionResult: T): Schema[Any, Any] =
buildSchema[Any, T](introspectionResult, IntrospectionSchemaBuilder.default)
/** Build a `Schema` for use by client tools.
*
* Given the result of a client running the introspection query, creates and returns a `Schema`
* instance which can be then used with all sangria tools, but cannot be used to execute a query,
* as introspection does not represent the "resolver", "parse" or "serialize" functions or any
* other server-internal mechanisms.
*
* @param introspectionResult
* the result of introspection query
* @param builder
* custom schema construction logic. By default `MaterializedSchemaException` would be thrown
* from a `resolve` function.
*/
def buildSchema[Ctx, T: InputUnmarshaller](
introspectionResult: T,
builder: IntrospectionSchemaBuilder[Ctx]): Schema[Ctx, Any] =
new IntrospectionSchemaMaterializer[Ctx, T](introspectionResult, builder).build
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy