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.execution.ValueCoercionHelper
import sangria.introspection._
import sangria.marshalling.{ToInput, InputUnmarshaller}
import sangria.parser.DeliveryScheme.Throw
import sangria.schema._
import sangria.util.StringUtil.escapeString
import sangria.introspection.__DirectiveLocation

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 "!")
  }

  val TypeSeparator = "\n\n"
  val Indention = "  "

  private def renderDescription(description: Option[String], prefix: String = "", lineBreak : Boolean = true) = description match {
    case Some(descr) if descr.trim.nonEmpty ⇒
      val lines = descr.lines.map(_.trim).toList

      lines map (l ⇒ prefix + "##" + (if (l.trim.nonEmpty) " " + l else "")) mkString ("", "\n", if (lineBreak) "\n" else "")
    case _ ⇒ ""
  }

  private def renderImplementedInterfaces(tpe: IntrospectionObjectType) =
    if (tpe.interfaces.nonEmpty)
      tpe.interfaces map (_.name) mkString (" implements ", ", ", "")
    else
      ""

  private def renderImplementedInterfaces(tpe: ObjectLikeType[_, _]) =
    if (tpe.allInterfaces.nonEmpty)
      tpe.allInterfaces map (_.name) mkString (" implements ", ", ", "")
    else
      ""

  def renderTypeName(tpe: IntrospectionTypeRef): String =
    tpe match {
      case IntrospectionListTypeRef(ofType) ⇒ s"[${renderTypeName(ofType)}]"
      case IntrospectionNonNullTypeRef(ofType) ⇒ s"${renderTypeName(ofType)}!"
      case IntrospectionNamedTypeRef(_, name) ⇒ name
    }

  private def renderDefault(defaultValue: Option[String]) =
    defaultValue.fold("")(d ⇒ s" = $d")

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

    s" = ${DefaultValueRenderer.renderInputValueCompact(value, tpe, coercionHelper)}"
  }

  private def renderArg(arg: IntrospectionInputValue) = {
    val argDef = s"${arg.name}: ${renderTypeName(arg.tpe)}"

    argDef + renderDefault(arg.defaultValue)
  }

  private def renderArg(arg: Argument[_]) = {
    val argDef = s"${arg.name}: ${renderTypeName(arg.argumentType)}"
    val default = arg.defaultValue.fold("")(renderDefault(_, arg.argumentType))

    argDef + default
  }

  def spaceOpt(show: Boolean) = if (show) " " else ""

  private def renderDeprecation(isDeprecated: Boolean, reason: Option[String], frontSep: Boolean = true) = (isDeprecated, reason) match {
    case (true, Some(r)) if r.trim == DefaultDeprecationReason ⇒ spaceOpt(frontSep) + "@deprecated"
    case (true, Some(r)) if r.trim.nonEmpty ⇒ spaceOpt(frontSep) + "@deprecated(reason: \"" + escapeString(r.trim) + "\")"
    case (true, _) ⇒ spaceOpt(frontSep) + "@deprecated"
    case _ ⇒ ""
  }

  def renderArgsI(args: Seq[IntrospectionInputValue]) =
    if (args.nonEmpty)
      args map (renderArg(_)) mkString ("(", ", ", ")")
    else
      ""

  private def renderArgs(args: Seq[Argument[_]]) =
    if (args.nonEmpty)
      args map renderArg mkString ("(", ", ", ")")
    else
      ""

  private def renderFieldsI(fields: Seq[IntrospectionField]) =
    if (fields.nonEmpty)
      fields.zipWithIndex map { case (f, idx) ⇒
        (if (idx != 0 && f.description.isDefined) "\n" else "") +
          renderField(f)
      } mkString "\n"
    else
      ""

  private def renderFields(fields: Seq[Field[_, _]]) =
    if (fields.nonEmpty)
      fields.zipWithIndex map { case (f, idx) ⇒
        (if (idx != 0 && f.description.isDefined) "\n" else "") +
          renderField(f)
      } mkString "\n"
    else
      ""

  private def renderInputFieldsI(fields: Seq[IntrospectionInputValue]) =
    if (fields.nonEmpty)
      fields.zipWithIndex map { case (f, idx) ⇒
        (if (idx != 0 && f.description.isDefined) "\n" else "") +
          renderInputField(f)
      } mkString "\n"
    else
      ""

  private def renderInputFields(fields: Seq[InputField[_]]) =
    if (fields.nonEmpty)
      fields.zipWithIndex map { case (f, idx) ⇒
        (if (idx != 0 && f.description.isDefined) "\n" else "") +
          renderInputField(f)
      } mkString "\n"
    else
      ""

  private def renderField(field: IntrospectionField) =
    s"${renderDescription(field.description, prefix = Indention)}$Indention${field.name}${renderArgsI(field.args)}: ${renderTypeName(field.tpe)}${renderDeprecation(field.isDeprecated, field.deprecationReason)}"

  private def renderField(field: Field[_, _]) =
    s"${renderDescription(field.description, prefix = Indention)}$Indention${field.name}${renderArgs(field.arguments)}: ${renderTypeName(field.fieldType)}${renderDeprecation(field.deprecationReason.isDefined, field.deprecationReason)}"

  private def renderInputField(field: IntrospectionInputValue) =
    s"${renderDescription(field.description, prefix = Indention)}$Indention${field.name}: ${renderTypeName(field.tpe)}${renderDefault(field.defaultValue)}"

  private def renderInputField(field: InputField[_]) = {
    val default = field.defaultValue.fold("")(renderDefault(_, field.fieldType))

    s"${renderDescription(field.description, prefix = Indention)}$Indention${field.name}: ${renderTypeName(field.fieldType)}$default"
  }

  private def renderObject(tpe: IntrospectionObjectType) =
    s"${renderDescription(tpe.description)}type ${tpe.name}${renderImplementedInterfaces(tpe)} {\n${renderFieldsI(tpe.fields)}\n}"

  private def renderObject(tpe: ObjectType[_, _]) =
    s"${renderDescription(tpe.description)}type ${tpe.name}${renderImplementedInterfaces(tpe)} {\n${renderFields(tpe.uniqueFields)}\n}"

  private def renderEnum(tpe: IntrospectionEnumType) =
    s"${renderDescription(tpe.description)}enum ${tpe.name} {\n${renderEnumValuesI(tpe.enumValues)}\n}"

  private def renderEnum(tpe: EnumType[_]) =
    s"${renderDescription(tpe.description)}enum ${tpe.name} {\n${renderEnumValues(tpe.values)}\n}"

  private def renderEnumValuesI(values: Seq[IntrospectionEnumValue]) =
    if (values.nonEmpty)
      values.zipWithIndex map { case (v, idx) ⇒
        (if (idx != 0 && v.description.isDefined) "\n" else "") +
          renderDescription(v.description, prefix = Indention) + Indention + v.name + renderDeprecation(v.isDeprecated, v.deprecationReason)
      } mkString "\n"
    else
      ""

  private def renderEnumValues(values: Seq[EnumValue[_]]) =
    if (values.nonEmpty)
      values.zipWithIndex map { case (v, idx) ⇒
        (if (idx != 0 && v.description.isDefined) "\n" else "") +
          renderDescription(v.description, prefix = Indention) + Indention + v.name + renderDeprecation(v.deprecationReason.isDefined, v.deprecationReason)
      } mkString "\n"
    else
      ""

  private def renderScalar(tpe: IntrospectionScalarType) =
    s"${renderDescription(tpe.description)}scalar ${tpe.name}"

  private def renderScalar(tpe: ScalarType[_]) =
    s"${renderDescription(tpe.description)}scalar ${tpe.name}"

  private def renderInputObject(tpe: IntrospectionInputObjectType) =
    s"${renderDescription(tpe.description)}input ${tpe.name} {\n${renderInputFieldsI(tpe.inputFields)}\n}"

  private def renderInputObject(tpe: InputObjectType[_]) =
    s"${renderDescription(tpe.description)}input ${tpe.name} {\n${renderInputFields(tpe.fields)}\n}"

  private def renderInterface(tpe: IntrospectionInterfaceType) =
    s"${renderDescription(tpe.description)}interface ${tpe.name} {\n${renderFieldsI(tpe.fields)}\n}"

  private def renderInterface(tpe: InterfaceType[_, _]) =
    s"${renderDescription(tpe.description)}interface ${tpe.name} {\n${renderFields(tpe.uniqueFields)}\n}"

  private def renderUnion(tpe: IntrospectionUnionType) =
    if (tpe.possibleTypes.nonEmpty)
      tpe.possibleTypes map (_.name) mkString (s"${renderDescription(tpe.description)}union ${tpe.name} = ", " | ", "")
    else
      ""

  private def renderUnion(tpe: UnionType[_]) =
    if (tpe.types.nonEmpty)
      tpe.types map (_.name) mkString (s"${renderDescription(tpe.description)}union ${tpe.name} = ", " | ", "")
    else
      ""

  private def renderSchemaDefinition(schema: IntrospectionSchema) = {
    val withQuery = (Indention + "query: " + renderTypeName(schema.queryType)) :: Nil
    val withMutation = schema.mutationType.fold(withQuery)(t ⇒ withQuery :+ (Indention + "mutation: " + renderTypeName(t)))
    val withSubs = schema.subscriptionType.fold(withMutation)(t ⇒ withMutation :+ (Indention + "subscription: " + renderTypeName(t)))

    s"schema {\n${withSubs mkString "\n"}\n}"
  }

  private def renderSchemaDefinition(schema: Schema[_, _]) = {
    val withQuery = (Indention + "query: " + renderTypeName(schema.query, true)) :: Nil
    val withMutation = schema.mutation.fold(withQuery)(t ⇒ withQuery :+ (Indention + "mutation: " + renderTypeName(t, true)))
    val withSubs = schema.subscription.fold(withMutation)(t ⇒ withMutation :+ (Indention + "subscription: " + renderTypeName(t, true)))

    s"schema {\n${withSubs mkString "\n"}\n}"
  }

  private def renderType(tpe: IntrospectionType) =
    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")
    }

  private def renderType(tpe: Type) =
    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 e: EnumType[_] ⇒ renderEnum(e)
      case _ ⇒ throw new IllegalArgumentException(s"Unsupported type: $tpe")
    }

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

  private def renderDirective(dir: Directive) =
    s"${renderDescription(dir.description)}directive @${dir.name}${renderArgs(dir.arguments)} on ${dir.locations.toList.map(renderDirectiveLocation).sorted mkString " | "}"

  private def renderDirective(dir: IntrospectionDirective) =
    s"${renderDescription(dir.description)}directive @${dir.name}${renderArgsI(dir.args)} on ${dir.locations.toList.map(renderDirectiveLocation).sorted mkString " | "}"

  def renderSchema(introspectionSchema: IntrospectionSchema): String = {
    val schemaDef = renderSchemaDefinition(introspectionSchema)
    val types = introspectionSchema.types filterNot isBuiltIn sortBy (_.name) map (renderType(_))
    val directives = introspectionSchema.directives filterNot (d ⇒ Schema.isBuiltInDirective(d.name)) sortBy (_.name) map (renderDirective(_))

    schemaDef +: (types ++ directives) mkString TypeSeparator
  }

  def renderSchema[T: InputUnmarshaller](introspectionResult: T): String =
    renderSchema(IntrospectionParser parse introspectionResult)

  def renderSchema(schema: Schema[_, _]): String = {
    val schemaDef = renderSchemaDefinition(schema)
    val types = schema.typeList filterNot isBuiltInType sortBy (_.name) map renderType
    val directives = schema.directives filterNot (d ⇒ Schema.isBuiltInDirective(d.name)) sortBy (_.name) map renderDirective

    schemaDef +: (types ++ directives) mkString TypeSeparator
  }

  def renderIntrospectionSchema(introspectionSchema: IntrospectionSchema): String = {
    val types = introspectionSchema.types filter (tpe ⇒ Schema.isIntrospectionType(tpe.name)) sortBy (_.name) map (renderType(_))
    val directives = introspectionSchema.directives filter (d ⇒ Schema.isBuiltInDirective(d.name)) sortBy (_.name) map (renderDirective(_))

    (types ++ directives) mkString TypeSeparator
  }

  def renderIntrospectionSchema[T: InputUnmarshaller](introspectionResult: T): String =
    renderIntrospectionSchema(IntrospectionParser parse introspectionResult)

  private def isBuiltIn(tpe: IntrospectionType) =
    Schema.isBuiltInType(tpe.name)

  private def isBuiltInType(tpe: Type with Named) =
    Schema.isBuiltInType(tpe.name)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy