Skip to content

Commit

Permalink
Default values should be in GraphQL format (introspection). Fixes #141
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegIlyenko committed Jun 9, 2016
1 parent 5f1973c commit 2936845
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 145 deletions.
93 changes: 39 additions & 54 deletions src/main/scala/sangria/renderer/SchemaRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package sangria.renderer

import sangria.execution.ValueCoercionHelper
import sangria.introspection._
import sangria.marshalling.{ToInput, ResultMarshaller, InputUnmarshaller}
import sangria.marshalling.{ToInput, InputUnmarshaller}
import sangria.parser.DeliveryScheme.Throw
import sangria.schema._
import sangria.util.StringUtil.escapeString
Expand Down Expand Up @@ -51,25 +51,19 @@ object SchemaRenderer {
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]

import sangria.marshalling.queryAst.queryAstResultMarshaller

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

private def renderArg(arg: IntrospectionInputValue, defParser: Option[DefaultValueParser[_]]) = {
private def renderArg(arg: IntrospectionInputValue) = {
val argDef = s"${arg.name}: ${renderTypeName(arg.tpe)}"
val default =
for {
default arg.defaultValue
parser defParser
tpe parser.schema.getInputType(arg.tpe)
parsed parser.parser.parse(default).toOption
} yield renderDefault(parsed parser.toInput, tpe)

argDef + (default getOrElse "")

argDef + renderDefault(arg.defaultValue)
}

private def renderArg(arg: Argument[_]) = {
Expand All @@ -88,9 +82,9 @@ object SchemaRenderer {
case _ ""
}

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

Expand All @@ -100,11 +94,11 @@ object SchemaRenderer {
else
""

private def renderFields(fields: Seq[IntrospectionField], defParser: Option[DefaultValueParser[_]]) =
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, defParser)
renderField(f)
} mkString "\n"
else
""
Expand All @@ -118,11 +112,11 @@ object SchemaRenderer {
else
""

private def renderInputFieldsI(fields: Seq[IntrospectionInputValue], defParser: Option[DefaultValueParser[_]]) =
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, defParser)
renderInputField(f)
} mkString "\n"
else
""
Expand All @@ -136,32 +130,23 @@ object SchemaRenderer {
else
""

private def renderField(field: IntrospectionField, defParser: Option[DefaultValueParser[_]]) =
s"${renderDescription(field.description, prefix = Indention)}$Indention${field.name}${renderArgs(field.args, defParser)}: ${renderTypeName(field.tpe)}${renderDeprecation(field.isDeprecated, field.deprecationReason)}"
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, defParser: Option[DefaultValueParser[_]]) = {
val default =
for {
default field.defaultValue
parser defParser
tpe parser.schema.getInputType(field.tpe)
parsed parser.parser.parse(default).toOption
} yield renderDefault(parsed parser.toInput, tpe)

s"${renderDescription(field.description, prefix = Indention)}$Indention${field.name}: ${renderTypeName(field.tpe)}${default getOrElse ""}"
}
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, defParser: Option[DefaultValueParser[_]]) =
s"${renderDescription(tpe.description)}type ${tpe.name}${renderImplementedInterfaces(tpe)} {\n${renderFields(tpe.fields, defParser)}\n}"
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}"
Expand Down Expand Up @@ -196,14 +181,14 @@ object SchemaRenderer {
private def renderScalar(tpe: ScalarType[_]) =
s"${renderDescription(tpe.description)}scalar ${tpe.name}"

private def renderInputObject(tpe: IntrospectionInputObjectType, defParser: Option[DefaultValueParser[_]]) =
s"${renderDescription(tpe.description)}input ${tpe.name} {\n${renderInputFieldsI(tpe.inputFields, defParser)}\n}"
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, defParser: Option[DefaultValueParser[_]]) =
s"${renderDescription(tpe.description)}interface ${tpe.name} {\n${renderFields(tpe.fields, defParser)}\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}"
Expand Down Expand Up @@ -236,12 +221,12 @@ object SchemaRenderer {
s"schema {\n${withSubs mkString "\n"}\n}"
}

private def renderType(tpe: IntrospectionType, defParser: Option[DefaultValueParser[_]]) =
private def renderType(tpe: IntrospectionType) =
tpe match {
case o: IntrospectionObjectType renderObject(o, defParser)
case o: IntrospectionObjectType renderObject(o)
case u: IntrospectionUnionType renderUnion(u)
case i: IntrospectionInterfaceType renderInterface(i, defParser)
case io: IntrospectionInputObjectType renderInputObject(io, defParser)
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")
Expand All @@ -264,19 +249,19 @@ object SchemaRenderer {
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, defParser: Option[DefaultValueParser[_]]) =
s"${renderDescription(dir.description)}directive @${dir.name}${renderArgs(dir.args, defParser)} 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, defParser: Option[DefaultValueParser[_]]): String = {
def renderSchema(introspectionSchema: IntrospectionSchema): String = {
val schemaDef = renderSchemaDefinition(introspectionSchema)
val types = introspectionSchema.types filterNot isBuiltIn sortBy (_.name) map (renderType(_, defParser))
val directives = introspectionSchema.directives filterNot (d Schema.isBuiltInDirective(d.name)) sortBy (_.name) map (renderDirective(_, defParser))
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, defParser: Option[DefaultValueParser[_]]): String =
renderSchema(IntrospectionParser parse introspectionResult, defParser)
def renderSchema[T: InputUnmarshaller](introspectionResult: T): String =
renderSchema(IntrospectionParser parse introspectionResult)

def renderSchema(schema: Schema[_, _]): String = {
val schemaDef = renderSchemaDefinition(schema)
Expand All @@ -286,15 +271,15 @@ object SchemaRenderer {
schemaDef +: (types ++ directives) mkString TypeSeparator
}

def renderIntrospectionSchema(introspectionSchema: IntrospectionSchema, defParser: Option[DefaultValueParser[_]]): String = {
val types = introspectionSchema.types filter (tpe Schema.isIntrospectionType(tpe.name)) sortBy (_.name) map (renderType(_, defParser))
val directives = introspectionSchema.directives filter (d Schema.isBuiltInDirective(d.name)) sortBy (_.name) map (renderDirective(_, defParser))
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, defParser: Option[DefaultValueParser[_]]): String =
renderIntrospectionSchema(IntrospectionParser parse introspectionResult, defParser)
def renderIntrospectionSchema[T: InputUnmarshaller](introspectionResult: T): String =
renderIntrospectionSchema(IntrospectionParser parse introspectionResult)

private def isBuiltIn(tpe: IntrospectionType) =
Schema.isBuiltInType(tpe.name)
Expand Down
71 changes: 32 additions & 39 deletions src/main/scala/sangria/schema/Context.scala
Original file line number Diff line number Diff line change
Expand Up @@ -148,17 +148,8 @@ trait WithInputTypeRendering[Ctx] {

private lazy val coercionHelper = new ValueCoercionHelper[Ctx](sourceMapper, deprecationTracker, Some(ctx))

def renderInputValueCompact[T](value: (_, ToInput[_, _]), tpe: InputType[T], m: ResultMarshaller = marshaller): String =
DefaultValueRenderer.renderInputValueCompact(value, tpe, coercionHelper)(m)

def renderInputValuePretty[T](value: (_, ToInput[_, _]), tpe: InputType[T], m: ResultMarshaller = marshaller): String =
DefaultValueRenderer.renderInputValuePretty(value, tpe, coercionHelper)(m)

def renderCoercedInputValueCompact[T](value: Any, tpe: InputType[T], m: ResultMarshaller = marshaller): String =
DefaultValueRenderer.renderCoercedInputValueCompact(value, tpe)(m)

def renderCoercedInputValuePretty[T](value: Any, tpe: InputType[T], m: ResultMarshaller = marshaller): String =
DefaultValueRenderer.renderCoercedInputValuePretty(value, tpe)(m)
def renderInputValueCompact[T](value: (_, ToInput[_, _]), tpe: InputType[T]): String =
DefaultValueRenderer.renderInputValueCompact(value, tpe, coercionHelper)
}

case class DefaultValueParser[T](schema: Schema[_, _], parser: InputParser[T], toInput: ToInput[T, _])
Expand All @@ -169,34 +160,36 @@ object DefaultValueParser {
}

object DefaultValueRenderer {
def renderInputValueCompact[T, Ctx](value: (_, ToInput[_, _]), tpe: InputType[T], coercionHelper: ValueCoercionHelper[Ctx])(implicit m: ResultMarshaller): String =
m.renderCompact(renderInputValue(value, tpe, coercionHelper))
implicit val marshaller = sangria.marshalling.queryAst.queryAstResultMarshaller

def renderInputValueCompact[T, Ctx](value: (_, ToInput[_, _]), tpe: InputType[T], coercionHelper: ValueCoercionHelper[Ctx]): String =
marshaller.renderCompact(renderInputValue(value, tpe, coercionHelper))

def renderInputValuePretty[T, Ctx](value: (_, ToInput[_, _]), tpe: InputType[T], coercionHelper: ValueCoercionHelper[Ctx])(implicit m: ResultMarshaller): String =
m.renderPretty(renderInputValue(value, tpe, coercionHelper))
def renderInputValuePretty[T, Ctx](value: (_, ToInput[_, _]), tpe: InputType[T], coercionHelper: ValueCoercionHelper[Ctx]): String =
marshaller.renderPretty(renderInputValue(value, tpe, coercionHelper))

def renderInputValue[T, Ctx](value: (_, ToInput[_, _]), tpe: InputType[T], coercionHelper: ValueCoercionHelper[Ctx])(implicit m: ResultMarshaller): m.Node = {
def loop(t: InputType[_], v: Any): m.Node = t match {
case _ if v == null m.nullNode
case s: ScalarType[Any @unchecked] Resolver.marshalScalarValue(s.coerceOutput(v, m.capabilities), m, s.name, s.scalarInfo)
case e: EnumType[Any @unchecked] Resolver.marshalEnumValue(e.coerceOutput(v), m, e.name)
def renderInputValue[T, Ctx](value: (_, ToInput[_, _]), tpe: InputType[T], coercionHelper: ValueCoercionHelper[Ctx]): marshaller.Node = {
def loop(t: InputType[_], v: Any): marshaller.Node = t match {
case _ if v == null marshaller.nullNode
case s: ScalarType[Any @unchecked] Resolver.marshalScalarValue(s.coerceOutput(v, marshaller.capabilities), marshaller, s.name, s.scalarInfo)
case e: EnumType[Any @unchecked] Resolver.marshalEnumValue(e.coerceOutput(v), marshaller, e.name)
case io: InputObjectType[_]
val mapValue = v.asInstanceOf[Map[String, Any]]

val builder = io.fields.foldLeft(m.emptyMapNode(io.fields.map(_.name))) {
val builder = io.fields.foldLeft(marshaller.emptyMapNode(io.fields.map(_.name))) {
case (acc, field) if mapValue contains field.name
m.addMapNodeElem(acc, field.name, loop(field.fieldType, mapValue(field.name)), optional = false)
marshaller.addMapNodeElem(acc, field.name, loop(field.fieldType, mapValue(field.name)), optional = false)
case (acc, _) acc
}

m.mapNode(builder)
marshaller.mapNode(builder)
case l: ListInputType[_]
val listValue = v.asInstanceOf[Seq[Any]]

m.mapAndMarshal[Any](listValue, loop(l.ofType, _))
marshaller.mapAndMarshal[Any](listValue, loop(l.ofType, _))
case o: OptionInputType[_] v match {
case Some(optVal) loop(o.ofType, optVal)
case None m.nullNode
case None marshaller.nullNode
case other loop(o.ofType, other)
}
}
Expand All @@ -206,37 +199,37 @@ object DefaultValueRenderer {

coercionHelper.coerceInputValue(tpe, Nil, inputValue, None, CoercedScalaResultMarshaller.default, CoercedScalaResultMarshaller.default)(iu) match {
case Right(Some(coerced)) renderCoercedInputValue(tpe, coerced)
case _ m.nullNode
case _ marshaller.nullNode
}
}

def renderCoercedInputValueCompact[T](value: Any, tpe: InputType[T])(implicit m: ResultMarshaller): String =
m.renderCompact(renderCoercedInputValue(tpe, value))
def renderCoercedInputValueCompact[T](value: Any, tpe: InputType[T]): String =
marshaller.renderCompact(renderCoercedInputValue(tpe, value))

def renderCoercedInputValuePretty[T](value: Any, tpe: InputType[T])(implicit m: ResultMarshaller): String =
m.renderPretty(renderCoercedInputValue(tpe, value))
def renderCoercedInputValuePretty[T](value: Any, tpe: InputType[T]): String =
marshaller.renderPretty(renderCoercedInputValue(tpe, value))

def renderCoercedInputValue(t: InputType[_], v: Any)(implicit m: ResultMarshaller): m.Node = t match {
case _ if v == null m.nullNode
case s: ScalarType[Any @unchecked] Resolver.marshalScalarValue(s.coerceOutput(v, m.capabilities), m, s.name, s.scalarInfo)
case e: EnumType[Any @unchecked] Resolver.marshalEnumValue(e.coerceOutput(v), m, e.name)
def renderCoercedInputValue(t: InputType[_], v: Any): marshaller.Node = t match {
case _ if v == null marshaller.nullNode
case s: ScalarType[Any @unchecked] Resolver.marshalScalarValue(s.coerceOutput(v, marshaller.capabilities), marshaller, s.name, s.scalarInfo)
case e: EnumType[Any @unchecked] Resolver.marshalEnumValue(e.coerceOutput(v), marshaller, e.name)
case io: InputObjectType[_]
val mapValue = v.asInstanceOf[Map[String, Any]]

val builder = io.fields.foldLeft(m.emptyMapNode(io.fields.map(_.name))) {
val builder = io.fields.foldLeft(marshaller.emptyMapNode(io.fields.map(_.name))) {
case (acc, field) if mapValue contains field.name
m.addMapNodeElem(acc, field.name, renderCoercedInputValue(field.fieldType, mapValue(field.name)), optional = false)
marshaller.addMapNodeElem(acc, field.name, renderCoercedInputValue(field.fieldType, mapValue(field.name)), optional = false)
case (acc, _) acc
}

m.mapNode(builder)
marshaller.mapNode(builder)
case l: ListInputType[_]
val listValue = v.asInstanceOf[Seq[Any]]

m.mapAndMarshal[Any](listValue, renderCoercedInputValue(l.ofType, _))
marshaller.mapAndMarshal[Any](listValue, renderCoercedInputValue(l.ofType, _))
case o: OptionInputType[_] v match {
case Some(optVal) renderCoercedInputValue(o.ofType, optVal)
case None m.nullNode
case None marshaller.nullNode
case other renderCoercedInputValue(o.ofType, other)
}
}
Expand Down
11 changes: 0 additions & 11 deletions src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,10 @@ trait IntrospectionSchemaBuilder[Ctx] {
definition: IntrospectionDirective,
arguments: List[Argument[_]],
mat: IntrospectionSchemaMaterializer[Ctx, _]): Option[Directive]

def defaultValueParser: Option[String Try[(Any, InputUnmarshaller[Any])]]
}

object IntrospectionSchemaBuilder {
def default[Ctx] = new DefaultIntrospectionSchemaBuilder[Ctx]

def withDefaultValues[Ctx, T : InputUnmarshaller : InputParser] = new DefaultIntrospectionSchemaBuilder[Ctx] {
override val defaultValueParser =
Some((raw: String) implicitly[InputParser[T]].parse(raw) map (_ implicitly[InputUnmarshaller[T]].asInstanceOf[InputUnmarshaller[Any]]))
}
}

class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[Ctx] {
Expand Down Expand Up @@ -339,8 +332,4 @@ object DefaultIntrospectionSchemaBuilder {
case object MaterializedSchemaViolation extends Violation {
val errorMessage = "Schema was materialized and cannot be used for any queries except introspection queries."
}

case class ConstantToInput[T](iu: InputUnmarshaller[T]) extends ToInput[T, T] {
def toInput(value: T) = value iu
}
}
Loading

0 comments on commit 2936845

Please sign in to comment.