All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sangria.renderer.SchemaRenderer.scala Maven / Gradle / Ivy

package sangria.renderer

import sangria.ast
import sangria.execution.ValueCoercionHelper
import sangria.introspection._
import sangria.marshalling.{InputUnmarshaller, ToInput}
import sangria.parser.QueryParser
import sangria.schema._

object SchemaRenderer {
  def renderTypeName(tpe: Type, topLevel: Boolean = false) = {
    def loop(t: Type, suffix: String): String = t match {
      case OptionType(ofType) => loop(ofType, "")
      case OptionInputType(ofType) => loop(ofType, "")
      case ListType(ofType) => s"[${loop(ofType, "!")}]" + suffix
      case ListInputType(ofType) => s"[${loop(ofType, "!")}]" + suffix
      case named: Named => named.name + suffix
    }

    loop(tpe, if (topLevel) "" else "!")
  }

  def renderTypeNameAst(tpe: Type, topLevel: Boolean = false): ast.Type = {
    def nn(tpe: ast.Type, notNull: Boolean) =
      if (notNull) ast.NotNullType(tpe)
      else tpe

    def loop(t: Type, notNull: Boolean): ast.Type = t match {
      case OptionType(ofType) => loop(ofType, false)
      case OptionInputType(ofType) => loop(ofType, false)
      case ListType(ofType) => nn(ast.ListType(loop(ofType, true)), notNull)
      case ListInputType(ofType) => nn(ast.ListType(loop(ofType, true)), notNull)
      case named: Named => nn(ast.NamedType(named.name), notNull)
    }

    loop(tpe, !topLevel)
  }

  def renderDescription(description: Option[String]): Option[ast.StringValue] =
    description.flatMap { d =>
      if (d.trim.nonEmpty) Some(ast.StringValue(d, block = d.indexOf('\n') > 0))
      else None
    }

  def renderImplementedInterfaces(tpe: IntrospectionObjectType) =
    tpe.interfaces.map(t => ast.NamedType(t.name)).toVector

  def renderImplementedInterfaces(tpe: IntrospectionInterfaceType) =
    tpe.interfaces.map(t => ast.NamedType(t.name)).toVector

  def renderImplementedInterfaces(tpe: ObjectLikeType[_, _]) =
    tpe.allInterfaces.map(t => ast.NamedType(t.name))

  def renderTypeName(tpe: IntrospectionTypeRef): ast.Type =
    tpe match {
      case IntrospectionListTypeRef(ofType) => ast.ListType(renderTypeName(ofType))
      case IntrospectionNonNullTypeRef(ofType) => ast.NotNullType(renderTypeName(ofType))
      case IntrospectionNamedTypeRef(_, name) => ast.NamedType(name)
    }

  def renderDefault(defaultValue: Option[String]) =
    defaultValue.flatMap(d => QueryParser.parseInput(d).toOption)

  def renderDefault(value: (Any, ToInput[_, _]), tpe: InputType[_]) = {
    val coercionHelper = new ValueCoercionHelper[Any]

    DefaultValueRenderer.renderInputValue(value, tpe, coercionHelper)
  }

  def renderArg(arg: IntrospectionInputValue) =
    ast.InputValueDefinition(
      arg.name,
      renderTypeName(arg.tpe),
      renderDefault(arg.defaultValue),
      description = renderDescription(arg.description))

  def renderArg(arg: Argument[_]) =
    ast.InputValueDefinition(
      arg.name,
      renderTypeNameAst(arg.argumentType),
      arg.defaultValue.flatMap(renderDefault(_, arg.argumentType)),
      withoutDeprecated(arg.astDirectives) ++ renderDeprecation(
        arg.deprecationReason.isDefined,
        arg.deprecationReason),
      renderDescription(arg.description)
    )

  def withoutDeprecated(dirs: Vector[ast.Directive]) = dirs.filterNot(_.name == "deprecated")

  def renderDeprecation(isDeprecated: Boolean, reason: Option[String]) =
    (isDeprecated, reason) match {
      case (true, Some(r)) if r.trim == DefaultDeprecationReason =>
        Vector(ast.Directive("deprecated", Vector.empty))
      case (true, Some(r)) if r.trim.nonEmpty =>
        Vector(ast.Directive("deprecated", Vector(ast.Argument("reason", ast.StringValue(r.trim)))))
      case (true, _) => Vector(ast.Directive("deprecated", Vector.empty))
      case _ => Vector.empty
    }

  def renderArgsI(args: Seq[IntrospectionInputValue]) =
    args.map(renderArg).toVector

  def renderArgs(args: Seq[Argument[_]]) =
    args.map(renderArg).toVector

  def renderFieldsI(fields: Seq[IntrospectionField]) =
    fields.map(renderField).toVector

  def renderFields(fields: Seq[Field[_, _]]) =
    fields.map(renderField).toVector

  def renderInputFieldsI(fields: Seq[IntrospectionInputValue]) =
    fields.map(renderInputField).toVector

  def renderInputFields(fields: Seq[InputField[_]]) =
    fields.map(renderInputField).toVector

  def renderField(field: IntrospectionField) =
    ast.FieldDefinition(
      field.name,
      renderTypeName(field.tpe),
      renderArgsI(field.args),
      renderDeprecation(field.isDeprecated, field.deprecationReason),
      renderDescription(field.description)
    )

  def renderField(field: Field[_, _]) =
    ast.FieldDefinition(
      field.name,
      renderTypeNameAst(field.fieldType),
      renderArgs(field.arguments),
      withoutDeprecated(field.astDirectives) ++ renderDeprecation(
        field.deprecationReason.isDefined,
        field.deprecationReason),
      renderDescription(field.description)
    )

  def renderInputField(field: IntrospectionInputValue) =
    ast.InputValueDefinition(
      field.name,
      renderTypeName(field.tpe),
      renderDefault(field.defaultValue),
      directives = renderDeprecation(field.isDeprecated.getOrElse(false), field.deprecationReason),
      description = renderDescription(field.description)
    )

  def renderInputField(field: InputField[_]) =
    ast.InputValueDefinition(
      field.name,
      renderTypeNameAst(field.fieldType),
      field.defaultValue.flatMap(renderDefault(_, field.fieldType)),
      withoutDeprecated(field.astDirectives) ++ renderDeprecation(
        field.deprecationReason.isDefined,
        field.deprecationReason),
      renderDescription(field.description)
    )

  def renderObject(tpe: IntrospectionObjectType) =
    ast.ObjectTypeDefinition(
      tpe.name,
      renderImplementedInterfaces(tpe),
      renderFieldsI(tpe.fields),
      description = renderDescription(tpe.description))

  def renderObject(tpe: ObjectType[_, _]) =
    ast.ObjectTypeDefinition(
      tpe.name,
      renderImplementedInterfaces(tpe),
      renderFields(tpe.uniqueFields),
      tpe.astDirectives,
      renderDescription(tpe.description))

  def renderEnum(tpe: IntrospectionEnumType) =
    ast.EnumTypeDefinition(
      tpe.name,
      renderEnumValuesI(tpe.enumValues),
      description = renderDescription(tpe.description))

  def renderEnum(tpe: EnumType[_]) =
    ast.EnumTypeDefinition(
      tpe.name,
      renderEnumValues(tpe.values),
      tpe.astDirectives,
      renderDescription(tpe.description))

  def renderEnumValuesI(values: Seq[IntrospectionEnumValue]) =
    values
      .map(v =>
        ast.EnumValueDefinition(
          v.name,
          renderDeprecation(v.isDeprecated, v.deprecationReason),
          renderDescription(v.description)))
      .toVector

  def renderEnumValues(values: Seq[EnumValue[_]]) =
    values.map(renderEnumValue).toVector

  def renderEnumValue(v: EnumValue[_]) =
    ast.EnumValueDefinition(
      v.name,
      withoutDeprecated(v.astDirectives) ++ renderDeprecation(
        v.deprecationReason.isDefined,
        v.deprecationReason),
      renderDescription(v.description))

  def renderScalar(tpe: IntrospectionScalarType) =
    ast.ScalarTypeDefinition(tpe.name, description = renderDescription(tpe.description))

  def renderScalar(tpe: ScalarType[_]) =
    ast.ScalarTypeDefinition(tpe.name, tpe.astDirectives, renderDescription(tpe.description))

  def renderInputObject(tpe: IntrospectionInputObjectType) =
    ast.InputObjectTypeDefinition(
      tpe.name,
      renderInputFieldsI(tpe.inputFields),
      description = renderDescription(tpe.description))

  def renderInputObject(tpe: InputObjectType[_]) =
    ast.InputObjectTypeDefinition(
      tpe.name,
      renderInputFields(tpe.fields),
      tpe.astDirectives,
      renderDescription(tpe.description))

  def renderInterface(tpe: IntrospectionInterfaceType) =
    ast.InterfaceTypeDefinition(
      tpe.name,
      renderFieldsI(tpe.fields),
      renderImplementedInterfaces(tpe),
      Vector.empty,
      renderDescription(tpe.description),
      Vector.empty,
      Vector.empty,
      None
    )

  def renderInterface(tpe: InterfaceType[_, _]) =
    ast.InterfaceTypeDefinition(
      tpe.name,
      renderFields(tpe.uniqueFields),
      renderImplementedInterfaces(tpe),
      tpe.astDirectives,
      renderDescription(tpe.description),
      Vector.empty,
      Vector.empty,
      None
    )

  def renderUnion(tpe: IntrospectionUnionType) =
    ast.UnionTypeDefinition(
      tpe.name,
      tpe.possibleTypes.map(t => ast.NamedType(t.name)).toVector,
      description = renderDescription(tpe.description))

  def renderUnion(tpe: UnionType[_]) =
    ast.UnionTypeDefinition(
      tpe.name,
      tpe.types.map(t => ast.NamedType(t.name)).toVector,
      tpe.astDirectives,
      renderDescription(tpe.description))

  private def renderSchemaDefinition(schema: IntrospectionSchema): Option[ast.SchemaDefinition] =
    if (isSchemaOfCommonNames(
        schema.queryType.name,
        schema.mutationType.map(_.name),
        schema.subscriptionType.map(_.name)))
      None
    else {
      val withQuery = Vector(
        ast.OperationTypeDefinition(ast.OperationType.Query, ast.NamedType(schema.queryType.name)))
      val withMutation = schema.mutationType.fold(withQuery)(t =>
        withQuery :+ ast.OperationTypeDefinition(ast.OperationType.Mutation, ast.NamedType(t.name)))
      val withSubs = schema.subscriptionType.fold(withMutation)(t =>
        withMutation :+ ast.OperationTypeDefinition(
          ast.OperationType.Subscription,
          ast.NamedType(t.name)))

      Some(ast.SchemaDefinition(withSubs, description = renderDescription(schema.description)))
    }

  private def renderSchemaDefinition(schema: Schema[_, _]): Option[ast.SchemaDefinition] =
    if (isSchemaOfCommonNames(
        schema.query.name,
        schema.mutation.map(_.name),
        schema.subscription.map(
          _.name)) && schema.description.isEmpty && schema.astDirectives.isEmpty)
      None
    else {
      val withQuery = Vector(
        ast.OperationTypeDefinition(ast.OperationType.Query, ast.NamedType(schema.query.name)))
      val withMutation = schema.mutation.fold(withQuery)(t =>
        withQuery :+ ast.OperationTypeDefinition(ast.OperationType.Mutation, ast.NamedType(t.name)))
      val withSubs = schema.subscription.fold(withMutation)(t =>
        withMutation :+ ast.OperationTypeDefinition(
          ast.OperationType.Subscription,
          ast.NamedType(t.name)))

      Some(
        ast.SchemaDefinition(withSubs, schema.astDirectives, renderDescription(schema.description)))
    }

  private def isSchemaOfCommonNames(
      query: String,
      mutation: Option[String],
      subscription: Option[String]) =
    query == "Query" && mutation.fold(true)(_ == "Mutation") && subscription.fold(true)(
      _ == "Subscription")

  def renderType(tpe: IntrospectionType): ast.TypeDefinition =
    tpe match {
      case o: IntrospectionObjectType => renderObject(o)
      case u: IntrospectionUnionType => renderUnion(u)
      case i: IntrospectionInterfaceType => renderInterface(i)
      case io: IntrospectionInputObjectType => renderInputObject(io)
      case s: IntrospectionScalarType => renderScalar(s)
      case e: IntrospectionEnumType => renderEnum(e)
      case kind => throw new IllegalArgumentException(s"Unsupported kind: $kind")
    }

  def renderType(tpe: Type with Named): ast.TypeDefinition =
    tpe match {
      case o: ObjectType[_, _] => renderObject(o)
      case u: UnionType[_] => renderUnion(u)
      case i: InterfaceType[_, _] => renderInterface(i)
      case io: InputObjectType[_] => renderInputObject(io)
      case s: ScalarType[_] => renderScalar(s)
      case s: ScalarAlias[_, _] => renderScalar(s.aliasFor)
      case e: EnumType[_] => renderEnum(e)
      case _ => throw new IllegalArgumentException(s"Unsupported type: $tpe")
    }

  def renderDirectiveLocation(loc: DirectiveLocation.Value) =
    ast.DirectiveLocation(__DirectiveLocation.byValue(loc).name)

  def renderDirective(dir: Directive) =
    ast.DirectiveDefinition(
      dir.name,
      renderArgs(dir.arguments),
      dir.locations.toVector.map(renderDirectiveLocation).sortBy(_.name),
      renderDescription(dir.description),
      dir.repeatable
    )

  def renderDirective(dir: IntrospectionDirective) =
    ast.DirectiveDefinition(
      dir.name,
      renderArgsI(dir.args),
      dir.locations.toVector.map(renderDirectiveLocation).sortBy(_.name),
      renderDescription(dir.description),
      dir.repeatable)

  def schemaAstFromIntrospection(
      introspectionSchema: IntrospectionSchema,
      filter: SchemaFilter = SchemaFilter.default): ast.Document = {
    val schemaDef = if (filter.renderSchema) renderSchemaDefinition(introspectionSchema) else None
    val types = introspectionSchema.types
      .filter(t => filter.filterTypes(t.name))
      .sortBy(_.name)
      .map(renderType)
    val directives = introspectionSchema.directives
      .filter(d => filter.filterDirectives(d.name))
      .sortBy(_.name)
      .map(renderDirective)

    ast.Document(schemaDef.toVector ++ types ++ directives)
  }

  def renderSchema(introspectionSchema: IntrospectionSchema): String =
    QueryRenderer.renderPretty(
      schemaAstFromIntrospection(introspectionSchema, SchemaFilter.default))

  def renderSchema[T: InputUnmarshaller](introspectionResult: T): String =
    QueryRenderer.renderPretty(
      schemaAstFromIntrospection(
        IntrospectionParser.parse(introspectionResult).get,
        SchemaFilter.default))

  def renderSchema(introspectionSchema: IntrospectionSchema, filter: SchemaFilter): String =
    QueryRenderer.renderPretty(schemaAstFromIntrospection(introspectionSchema, filter))

  def renderSchema[T: InputUnmarshaller](introspectionResult: T, filter: SchemaFilter): String =
    QueryRenderer.renderPretty(
      schemaAstFromIntrospection(IntrospectionParser.parse(introspectionResult).get, filter))

  def schemaAst(schema: Schema[_, _], filter: SchemaFilter = SchemaFilter.default): ast.Document = {
    val schemaDef = if (filter.renderSchema) renderSchemaDefinition(schema) else None
    val types =
      schema.typeList.filter(t => filter.filterTypes(t.name)).sortBy(_.name).map(renderType)
    val directives = schema.directives
      .filter(d => filter.filterDirectives(d.name))
      .sortBy(_.name)
      .map(renderDirective)

    ast.Document(schemaDef.toVector ++ types ++ directives)
  }

  def renderSchema(schema: Schema[_, _]): String =
    QueryRenderer.renderPretty(schemaAst(schema, SchemaFilter.default))

  def renderSchema(schema: Schema[_, _], filter: SchemaFilter): String =
    QueryRenderer.renderPretty(schemaAst(schema, filter))
}

case class SchemaFilter(
    filterTypes: String => Boolean,
    filterDirectives: String => Boolean,
    renderSchema: Boolean = true)

object SchemaFilter {
  val withoutSangriaBuiltIn: SchemaFilter = SchemaFilter(
    typeName => !Schema.isBuiltInType(typeName),
    dirName => !Schema.isBuiltInDirective(dirName))

  val default: SchemaFilter = withoutSangriaBuiltIn

  val withoutGraphQLBuiltIn = SchemaFilter(
    typeName => !Schema.isBuiltInGraphQLType(typeName),
    dirName => !Schema.isBuiltInDirective(dirName))

  val withoutIntrospection: SchemaFilter =
    SchemaFilter(typeName => !Schema.isIntrospectionType(typeName), Function.const(true))

  val builtIn: SchemaFilter = SchemaFilter(
    typeName => Schema.isBuiltInType(typeName),
    dirName => Schema.isBuiltInDirective(dirName))

  val introspection: SchemaFilter = SchemaFilter(
    typeName => Schema.isIntrospectionType(typeName),
    Function.const(false),
    renderSchema = false)

  val all: SchemaFilter = SchemaFilter(Function.const(true), Function.const(true))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy