diff --git a/benchmarks/src/main/scala/caliban/json/JsonEncodingBenchmark.scala b/benchmarks/src/main/scala/caliban/json/JsonEncodingBenchmark.scala index 7c70554f6a..1baf9b0d2f 100644 --- a/benchmarks/src/main/scala/caliban/json/JsonEncodingBenchmark.scala +++ b/benchmarks/src/main/scala/caliban/json/JsonEncodingBenchmark.scala @@ -1,9 +1,9 @@ package caliban.json import caliban.Value._ -import caliban.interop.circe.json.GraphQLResponseCirce.{graphQLResponseEncoder => circeEncoder} -import caliban.interop.zio.GraphQLResponseZioJson.{graphQLResponseEncoder => zioEncoder} -import caliban.{GraphQLResponse, ResponseValue} +import caliban.interop.circe.json.GraphQLResponseCirce.{ graphQLResponseEncoder => circeEncoder } +import caliban.interop.zio.GraphQLResponseZioJson.{ graphQLResponseEncoder => zioEncoder } +import caliban.{ GraphQLResponse, ResponseValue } import com.github.plokhotnyuk.jsoniter_scala.core._ import org.openjdk.jmh.annotations._ diff --git a/build.sbt b/build.sbt index b2708faf2d..4861d0d57d 100644 --- a/build.sbt +++ b/build.sbt @@ -753,7 +753,13 @@ lazy val enableMimaSettingsJVM = mimaFailOnProblem := enforceMimaCompatibility, mimaPreviousArtifacts := previousStableVersion.value.map(organization.value %% moduleName.value % _).toSet, mimaBinaryIssueFilters ++= Seq( - ProblemFilters.exclude[IncompatibleMethTypeProblem]("caliban.execution.Executor#ReducedStepExecutor.makeQuery") + ProblemFilters.exclude[IncompatibleMethTypeProblem]("caliban.execution.Executor#ReducedStepExecutor.makeQuery"), + ProblemFilters.exclude[DirectMissingMethodProblem]("caliban.parsing.adt.Type.$init$"), + ProblemFilters.exclude[DirectMissingMethodProblem]("caliban.introspection.adt.__Type.*"), + ProblemFilters.exclude[DirectMissingMethodProblem]("caliban.introspection.adt.__InputValue.*"), + ProblemFilters.exclude[FinalMethodProblem]("caliban.parsing.adt.Type*"), + ProblemFilters.exclude[MissingTypesProblem]("caliban.introspection.adt.__Type$"), + ProblemFilters.exclude[MissingTypesProblem]("caliban.introspection.adt.__InputValue$") ) ) diff --git a/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala index 692a4b6f60..be5d5ae816 100644 --- a/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-2/caliban/schema/ArgBuilderDerivation.scala @@ -3,10 +3,10 @@ package caliban.schema import caliban.CalibanError.ExecutionError import caliban.InputValue import caliban.Value._ -import caliban.schema.Annotations.GQLDefault -import caliban.schema.Annotations.GQLName +import caliban.schema.Annotations.{ GQLDefault, GQLName, GQLOneOfInput } import magnolia1._ +import scala.collection.compat._ import scala.language.experimental.macros trait CommonArgBuilderDerivation { @@ -25,19 +25,44 @@ trait CommonArgBuilderDerivation { override def map[A, B](from: EitherExecutionError[A])(fn: A => B): EitherExecutionError[B] = from.map(fn) } - def join[T](ctx: CaseClass[ArgBuilder, T]): ArgBuilder[T] = - (input: InputValue) => + def join[T](ctx: CaseClass[ArgBuilder, T]): ArgBuilder[T] = new ArgBuilder[T] { + + private val params = { + val arr = Array.ofDim[(String, EitherExecutionError[Any])](ctx.parameters.length) + ctx.parameters.zipWithIndex.foreach { case (p, i) => + val label = p.annotations.collectFirst { case GQLName(name) => name }.getOrElse(p.label) + val default = p.typeclass.buildMissing(p.annotations.collectFirst { case GQLDefault(v) => v }) + arr(i) = (label, default) + } + arr + } + + private val required = params.collect { case (label, default) if default.isLeft => label } + + override private[schema] val partial: PartialFunction[InputValue, Either[ExecutionError, T]] = { + case InputValue.ObjectValue(fields) if required.forall(fields.contains) => fromFields(fields) + } + + def build(input: InputValue): Either[ExecutionError, T] = + input match { + case InputValue.ObjectValue(fields) => fromFields(fields) + case _ => Left(ExecutionError("expected an input object")) + } + + private[this] def fromFields(fields: Map[String, InputValue]): Either[ExecutionError, T] = ctx.constructMonadic { p => - input match { - case InputValue.ObjectValue(fields) => - val label = p.annotations.collectFirst { case GQLName(name) => name }.getOrElse(p.label) - val default = p.annotations.collectFirst { case GQLDefault(v) => v } - fields.get(label).fold(p.typeclass.buildMissing(default))(p.typeclass.build) - case value => p.typeclass.build(value) - } + val idx = p.index + val (label, default) = params(idx) + val field = fields.getOrElse(label, null) + if (field ne null) p.typeclass.build(field) else default } + } - def split[T](ctx: SealedTrait[ArgBuilder, T]): ArgBuilder[T] = input => + def split[T](ctx: SealedTrait[ArgBuilder, T]): ArgBuilder[T] = + if (ctx.annotations.contains(GQLOneOfInput())) makeOneOfBuilder(ctx) + else makeSumBuilder(ctx) + + private def makeSumBuilder[T](ctx: SealedTrait[ArgBuilder, T]): ArgBuilder[T] = input => (input match { case EnumValue(value) => Some(value) case StringValue(value) => Some(value) @@ -53,6 +78,26 @@ trait CommonArgBuilderDerivation { } case None => Left(ExecutionError(s"Can't build a trait from input $input")) } + + private def makeOneOfBuilder[A](ctx: SealedTrait[ArgBuilder, A]): ArgBuilder[A] = new ArgBuilder[A] { + + private def inputError(input: InputValue) = + ExecutionError(s"Invalid oneOf input $input for trait ${ctx.typeName.short}") + + override val partial: PartialFunction[InputValue, Either[ExecutionError, A]] = { + val xs = ctx.subtypes.map(_.typeclass).toList.asInstanceOf[List[ArgBuilder[A]]] + + val checkSize: PartialFunction[InputValue, Either[ExecutionError, A]] = { + case InputValue.ObjectValue(f) if f.size != 1 => + Left(ExecutionError("Exactly one key must be specified for oneOf inputs")) + } + xs.foldLeft(checkSize)(_ orElse _.partial) + } + + def build(input: InputValue): Either[ExecutionError, A] = + partial.applyOrElse(input, (in: InputValue) => Left(inputError(in))) + } + } trait ArgBuilderDerivation extends CommonArgBuilderDerivation { diff --git a/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala b/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala index 6089a58717..40bbd51d48 100644 --- a/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala +++ b/core/src/main/scala-2/caliban/schema/SchemaDerivation.scala @@ -1,8 +1,9 @@ package caliban.schema +import caliban.CalibanError.ValidationError import caliban.Value._ import caliban.introspection.adt._ -import caliban.parsing.adt.Directive +import caliban.parsing.adt.{ Directive, Directives } import caliban.schema.Annotations._ import caliban.schema.Types._ import magnolia1._ @@ -65,8 +66,8 @@ trait CommonSchemaDerivation[R] { if (_isValueType) { if (isScalarValueType(ctx)) makeScalar(getName(ctx), getDescription(ctx)) else ctx.parameters.head.typeclass.toType_(isInput, isSubscription) - } else if (isInput) - makeInputObject( + } else if (isInput) { + lazy val tpe: __Type = makeInputObject( Some(ctx.annotations.collectFirst { case GQLInputName(suffix) => suffix } .getOrElse(customizeInputTypeName(getName(ctx)))), getDescription(ctx), @@ -81,14 +82,16 @@ trait CommonSchemaDerivation[R] { p.annotations.collectFirst { case GQLDefault(v) => v }, p.annotations.collectFirst { case GQLDeprecated(_) => () }.isDefined, p.annotations.collectFirst { case GQLDeprecated(reason) => reason }, - Some(p.annotations.collect { case GQLDirective(dir) => dir }.toList).filter(_.nonEmpty) + Some(p.annotations.collect { case GQLDirective(dir) => dir }.toList).filter(_.nonEmpty), + () => Some(tpe) ) ) .toList, Some(ctx.typeName.full), Some(getDirectives(ctx)) ) - else + tpe + } else makeObject( Some(getName(ctx)), getDescription(ctx), @@ -172,11 +175,13 @@ trait CommonSchemaDerivation[R] { case _ => false } - if (isEnum && subtypes.nonEmpty && !isInterface && !isUnion) + val isOneOfInput = ctx.annotations.contains(GQLOneOfInput()) + + if (isEnum && subtypes.nonEmpty && !isInterface && !isUnion && !isOneOfInput) { makeEnum( Some(getName(ctx)), getDescription(ctx), - subtypes.collect { case (__Type(_, Some(name), description, _, _, _, _, _, _, _, _, _), annotations) => + subtypes.collect { case (__Type(_, Some(name), description, _, _, _, _, _, _, _, _, _, _), annotations) => __EnumValue( name, description, @@ -188,7 +193,18 @@ trait CommonSchemaDerivation[R] { Some(ctx.typeName.full), Some(getDirectives(ctx.annotations)) ) - else if (!isInterface) { + } else if (isOneOfInput && isInput) { + makeInputObject( + Some(ctx.annotations.collectFirst { case GQLInputName(suffix) => suffix } + .getOrElse(customizeInputTypeName(getName(ctx)))), + getDescription(ctx), + ctx.subtypes.toList.flatMap { p => + p.typeclass.toType_(isInput = true).allInputFields.map(_.nullable) + }, + Some(ctx.typeName.full), + Some(List(Directive(Directives.OneOf))) + ) + } else if (!isInterface) { containsEmptyUnionObjects = emptyUnionObjectIdxs.contains(true) makeUnion( Some(getName(ctx)), diff --git a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala index d54329bb9b..114867daec 100644 --- a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala @@ -2,7 +2,7 @@ package caliban.schema import caliban.CalibanError.ExecutionError import caliban.Value.* -import caliban.schema.Annotations.{ GQLDefault, GQLName } +import caliban.schema.Annotations.{ GQLDefault, GQLName, GQLOneOfInput } import caliban.schema.macros.Macros import caliban.{ CalibanError, InputValue } import magnolia1.Macro as MagnoliaMacro @@ -53,10 +53,16 @@ trait CommonArgBuilderDerivation { inline def derived[A]: ArgBuilder[A] = inline summonInline[Mirror.Of[A]] match { case m: Mirror.SumOf[A] => - makeSumArgBuilder[A]( - recurseSum[A, m.MirroredElemLabels, m.MirroredElemTypes](), - constValue[m.MirroredLabel] - ) + inline if (Macros.hasAnnotation[A, GQLOneOfInput]) { + makeOneOfBuilder[A]( + recurseSum[A, m.MirroredElemLabels, m.MirroredElemTypes](), + constValue[m.MirroredLabel] + ) + } else + makeSumArgBuilder[A]( + recurseSum[A, m.MirroredElemLabels, m.MirroredElemTypes](), + constValue[m.MirroredLabel] + ) case m: Mirror.ProductOf[A] => makeProductArgBuilder( @@ -68,11 +74,11 @@ trait CommonArgBuilderDerivation { private def makeSumArgBuilder[A]( _subTypes: => List[(String, List[Any], ArgBuilder[Any])], traitLabel: String - ) = new ArgBuilder[A] { + ): ArgBuilder[A] = new ArgBuilder[A] { private lazy val subTypes = _subTypes private val emptyInput = InputValue.ObjectValue(Map.empty) - def build(input: InputValue): Either[ExecutionError, A] = + final def build(input: InputValue): Either[ExecutionError, A] = input.match { case EnumValue(value) => Right(value) case StringValue(value) => Right(value) @@ -91,28 +97,69 @@ trait CommonArgBuilderDerivation { } } + private def makeOneOfBuilder[A]( + _subTypes: => List[(String, List[Any], ArgBuilder[Any])], + traitLabel: String + ): ArgBuilder[A] = new ArgBuilder[A] { + + override val partial: PartialFunction[InputValue, Either[ExecutionError, A]] = { + val xs = _subTypes.map(_._3).asInstanceOf[List[ArgBuilder[A]]] + + val checkSize: PartialFunction[InputValue, Either[ExecutionError, A]] = { + case InputValue.ObjectValue(f) if f.size != 1 => + Left(ExecutionError("Exactly one key must be specified for oneOf inputs")) + } + xs.foldLeft(checkSize)(_ orElse _.partial) + } + + def build(input: InputValue): Either[ExecutionError, A] = + partial.applyOrElse(input, (in: InputValue) => Left(inputError(in))) + + private def inputError(input: InputValue) = + ExecutionError(s"Invalid oneOf input $input for trait $traitLabel") + } + private def makeProductArgBuilder[A]( _fields: => List[(String, ArgBuilder[Any])], annotations: Map[String, List[Any]] - )(fromProduct: Product => A) = new ArgBuilder[A] { - private lazy val fields = _fields + )(fromProduct: Product => A): ArgBuilder[A] = new ArgBuilder[A] { + + private val params = Array.from(_fields.map { (label, builder) => + val labelList = annotations.get(label) + val default = builder.buildMissing(labelList.flatMap(_.collectFirst { case GQLDefault(v) => v })) + val finalLabel = labelList.flatMap(_.collectFirst { case GQLName(name) => name }).getOrElse(label) + (finalLabel, default, builder) + }) + + private val required = params.collect { case (label, default, _) if default.isLeft => label } + + override private[schema] val partial: PartialFunction[InputValue, Either[ExecutionError, A]] = { + case InputValue.ObjectValue(fields) if required.forall(fields.contains) => fromFields(fields) + } def build(input: InputValue): Either[ExecutionError, A] = - fields.view.map { (label, builder) => - input match { - case InputValue.ObjectValue(fields) => - val labelList = annotations.get(label) - def default = labelList.flatMap(_.collectFirst { case GQLDefault(v) => v }) - val finalLabel = labelList.flatMap(_.collectFirst { case GQLName(name) => name }).getOrElse(label) - fields.get(finalLabel).fold(builder.buildMissing(default))(builder.build) - case value => builder.build(value) - } - }.foldLeft[Either[ExecutionError, Tuple]](Right(EmptyTuple)) { case (acc, item) => - item match { - case Right(value) => acc.map(_ :* value) - case Left(e) => Left(e) + input match { + case InputValue.ObjectValue(fields) => fromFields(fields) + case _ => Left(ExecutionError("expected an input object")) + } + + private def fromFields(fields: Map[String, InputValue]): Either[ExecutionError, A] = { + var i = 0 + val l = params.length + var acc: Tuple = EmptyTuple + while (i < l) { + val (label, default, builder) = params(i) + val field = fields.getOrElse(label, null) + val value = if (field ne null) builder.build(field) else default + value match { + case Right(v) => acc :*= v + case e @ Left(_) => return e.asInstanceOf[Either[ExecutionError, A]] } - }.map(fromProduct) + i += 1 + } + Right(fromProduct(acc)) + } + } } diff --git a/core/src/main/scala-3/caliban/schema/DerivationUtils.scala b/core/src/main/scala-3/caliban/schema/DerivationUtils.scala index 090bc5a580..cc14690572 100644 --- a/core/src/main/scala-3/caliban/schema/DerivationUtils.scala +++ b/core/src/main/scala-3/caliban/schema/DerivationUtils.scala @@ -1,7 +1,7 @@ package caliban.schema import caliban.introspection.adt.* -import caliban.parsing.adt.Directive +import caliban.parsing.adt.{ Directive, Directives } import caliban.schema.Annotations.* import caliban.schema.Types.* import magnolia1.TypeInfo @@ -48,13 +48,13 @@ private object DerivationUtils { makeEnum( Some(getName(annotations, info)), getDescription(annotations), - subTypes.map { case (name, __Type(_, _, description, _, _, _, _, _, _, _, _, _), annotations) => + subTypes.map { case (name, __Type(_, _, description, _, _, _, _, _, _, _, _, _, _), annotations) => __EnumValue( getName(annotations, name), description, getDeprecatedReason(annotations).isDefined, getDeprecatedReason(annotations), - Some(annotations.collect { case GQLDirective(dir) => dir }.toList).filter(_.nonEmpty) + Some(annotations.collect { case GQLDirective(dir) => dir }).filter(_.nonEmpty) ) }.sortBy(_.name), Some(info.full), @@ -97,25 +97,43 @@ private object DerivationUtils { annotations: List[Any], fields: List[(String, List[Any], Schema[R, Any])], info: TypeInfo - )(isInput: Boolean, isSubscription: Boolean): __Type = makeInputObject( - Some(getInputName(annotations).getOrElse(customizeInputTypeName(getName(annotations, info)))), - getDescription(annotations), - fields.map { (name, fieldAnnotations, schema) => - __InputValue( - name, - getDescription(fieldAnnotations), - () => - if (schema.optional) schema.toType_(isInput, isSubscription) - else schema.toType_(isInput, isSubscription).nonNull, - getDefaultValue(fieldAnnotations), - getDeprecatedReason(fieldAnnotations).isDefined, - getDeprecatedReason(fieldAnnotations), - Some(getDirectives(fieldAnnotations)).filter(_.nonEmpty) - ) - }, - Some(info.full), - Some(getDirectives(annotations)) - ) + )(isInput: Boolean, isSubscription: Boolean): __Type = { + lazy val tpe: __Type = makeInputObject( + Some(getInputName(annotations).getOrElse(customizeInputTypeName(getName(annotations, info)))), + getDescription(annotations), + fields.map { (name, fieldAnnotations, schema) => + val deprecationReason = getDeprecatedReason(fieldAnnotations) + __InputValue( + name, + description = getDescription(fieldAnnotations), + `type` = () => + if (schema.optional) schema.toType_(isInput, isSubscription) + else schema.toType_(isInput, isSubscription).nonNull, + defaultValue = getDefaultValue(fieldAnnotations), + isDeprecated = deprecationReason.isDefined, + deprecationReason = deprecationReason, + directives = Some(getDirectives(fieldAnnotations)).filter(_.nonEmpty), + parentType = () => Some(tpe) + ) + }, + Some(info.full), + Some(getDirectives(annotations)) + ) + tpe + } + + def mkOneOfInput[R]( + annotations: List[Any], + schemas: List[Schema[R, Any]], + info: TypeInfo + ): __Type = + makeInputObject( + Some(getInputName(annotations).getOrElse(customizeInputTypeName(getName(annotations, info)))), + getDescription(annotations), + schemas.flatMap(_.toType_(isInput = true).allInputFields.map(_.nullable)), + Some(info.full), + Some(List(Directive(Directives.OneOf))) + ) def mkObject[R]( annotations: List[Any], diff --git a/core/src/main/scala-3/caliban/schema/SumSchema.scala b/core/src/main/scala-3/caliban/schema/SumSchema.scala index 7cd6dc3053..620f257a16 100644 --- a/core/src/main/scala-3/caliban/schema/SumSchema.scala +++ b/core/src/main/scala-3/caliban/schema/SumSchema.scala @@ -1,7 +1,7 @@ package caliban.schema import caliban.introspection.adt.__Type -import caliban.schema.Annotations.{ GQLInterface, GQLUnion } +import caliban.schema.Annotations.{ GQLInterface, GQLOneOfInput, GQLUnion } import caliban.schema.DerivationUtils.* import caliban.schema.Types.makeUnion import magnolia1.TypeInfo @@ -26,6 +26,7 @@ final private class SumSchema[R, A]( } private var containsEmptyUnionObjects = false + private val isOneOfInput = annotations.contains(GQLOneOfInput()) def toType(isInput: Boolean, isSubscription: Boolean): __Type = { val _ = schemas @@ -33,7 +34,8 @@ final private class SumSchema[R, A]( val isUnion = annotations.contains(GQLUnion()) @threadUnsafe lazy val isEnum = subTypes.forall((_, t, _) => t.allFields.isEmpty && t.allInputFields.isEmpty) - if (!isInterface && !isUnion && subTypes.nonEmpty && isEnum) mkEnum(annotations, info, subTypes) + if (!isInterface && !isUnion && subTypes.nonEmpty && isEnum && !isOneOfInput) mkEnum(annotations, info, subTypes) + else if (isOneOfInput && isInput) mkOneOfInput(annotations, schemas.toList, info) else if (!isInterface) { containsEmptyUnionObjects = emptyUnionObjectIdxs.contains(true) makeUnion( diff --git a/core/src/main/scala-3/caliban/schema/macros/Macros.scala b/core/src/main/scala-3/caliban/schema/macros/Macros.scala index e5b5a06266..853b10099d 100644 --- a/core/src/main/scala-3/caliban/schema/macros/Macros.scala +++ b/core/src/main/scala-3/caliban/schema/macros/Macros.scala @@ -1,6 +1,6 @@ package caliban.schema.macros -import caliban.schema.Annotations.{ GQLExcluded, GQLField } +import caliban.schema.Annotations.* import caliban.schema.Schema import scala.quoted.* diff --git a/core/src/main/scala/caliban/introspection/Introspector.scala b/core/src/main/scala/caliban/introspection/Introspector.scala index f6c37862da..2035ed210e 100644 --- a/core/src/main/scala/caliban/introspection/Introspector.scala +++ b/core/src/main/scala/caliban/introspection/Introspector.scala @@ -47,6 +47,17 @@ object Introspector extends IntrospectionDerivation { private val introspectionType = introspectionSchema.toType_() val introspectionRootType: RootType = RootType(introspectionType, None, None) + private val oneOfDirective = + __Directive( + "oneOf", + Some( + "The `@oneOf` directive is used within the type system definition language to indicate an Input Object is a OneOf Input Object." + ), + Set(__DirectiveLocation.INPUT_OBJECT), + _ => Nil, + isRepeatable = false + ) + /** * Generates a schema for introspecting the given type. */ @@ -70,6 +81,9 @@ object Introspector extends IntrospectionDerivation { .values .toList .sortBy(_.name.getOrElse("")) + + val hasOneOf = types.exists(_._isOneOfInput) + val resolver = __Introspection( __Schema( rootType.description, @@ -77,7 +91,7 @@ object Introspector extends IntrospectionDerivation { rootType.mutationType, rootType.subscriptionType, types, - directives ++ rootType.additionalDirectives + directives ++ (if (hasOneOf) List(oneOfDirective) else Nil) ++ rootType.additionalDirectives ), args => types.find(_.name.contains(args.name)) ) diff --git a/core/src/main/scala/caliban/introspection/adt/__InputValue.scala b/core/src/main/scala/caliban/introspection/adt/__InputValue.scala index 73c58b7bb9..3ab5e06124 100644 --- a/core/src/main/scala/caliban/introspection/adt/__InputValue.scala +++ b/core/src/main/scala/caliban/introspection/adt/__InputValue.scala @@ -6,6 +6,8 @@ import caliban.parsing.adt.Directive import caliban.parsing.Parser import caliban.schema.Annotations.GQLExcluded +import scala.annotation.tailrec + case class __InputValue( name: String, description: Option[String], @@ -13,7 +15,8 @@ case class __InputValue( defaultValue: Option[String], isDeprecated: Boolean = false, deprecationReason: Option[String] = None, - @GQLExcluded directives: Option[List[Directive]] = None + @GQLExcluded directives: Option[List[Directive]] = None, + @GQLExcluded parentType: () => Option[__Type] = () => None ) { def toInputValueDefinition: InputValueDefinition = { val default = defaultValue.flatMap(v => Parser.parseInputValue(v).toOption) @@ -29,4 +32,20 @@ case class __InputValue( } private[caliban] lazy val _type: __Type = `type`() + private[caliban] lazy val _parentType = parentType() + + /** + * Makes the [[`type`]] nullable as required by the spec for OneOf Input Objects + */ + private[caliban] def nullable: __InputValue = { + @tailrec + def loop(tpe: __Type): __Type = + (tpe.kind, tpe.ofType) match { + case (__TypeKind.NON_NULL, Some(inner)) => loop(inner) + case _ => tpe + } + + val t = loop(_type) + copy(`type` = () => t) + } } diff --git a/core/src/main/scala/caliban/introspection/adt/__Type.scala b/core/src/main/scala/caliban/introspection/adt/__Type.scala index db11ec27f7..c870fd168b 100644 --- a/core/src/main/scala/caliban/introspection/adt/__Type.scala +++ b/core/src/main/scala/caliban/introspection/adt/__Type.scala @@ -21,7 +21,8 @@ case class __Type( ofType: Option[__Type] = None, specifiedBy: Option[String] = None, @GQLExcluded directives: Option[List[Directive]] = None, - @GQLExcluded origin: Option[String] = None + @GQLExcluded origin: Option[String] = None, + isOneOf: Option[Boolean] = None ) { self => final override lazy val hashCode: Int = super.hashCode() @@ -150,6 +151,8 @@ case class __Type( case _ => Set.empty } + private[caliban] val _isOneOfInput: Boolean = isOneOf.getOrElse(false) + private[caliban] def mapInnerType(f: __Type => __Type): __Type = { def loop(t: __Type): __Type = t.ofType match { diff --git a/core/src/main/scala/caliban/parsing/adt/Directive.scala b/core/src/main/scala/caliban/parsing/adt/Directive.scala index 4d862853bd..e3cc12df1f 100644 --- a/core/src/main/scala/caliban/parsing/adt/Directive.scala +++ b/core/src/main/scala/caliban/parsing/adt/Directive.scala @@ -9,6 +9,7 @@ object Directives { final val LazyDirective = "lazy" final val NewtypeDirective = "newtype" final val DeprecatedDirective = "deprecated" + final val OneOf = "oneOf" def isDeprecated(directives: List[Directive]): Boolean = directives.exists(_.name == DeprecatedDirective) @@ -16,11 +17,15 @@ object Directives { def deprecationReason(directives: List[Directive]): Option[String] = findDirective(directives, DeprecatedDirective, "reason") - def isNewType(directives: List[Directive]): Boolean = + def isNewType(directives: List[Directive]): Boolean = directives.exists(_.name == NewtypeDirective) + def newTypeName(directives: List[Directive]): Option[String] = findDirective(directives, NewtypeDirective, "name") + def isOneOf(directives: List[Directive]): Boolean = + directives.exists(_.name == OneOf) + private def findDirective(directives: List[Directive], name: String, argument: String): Option[String] = directives.collectFirst { case f if f.name == name => diff --git a/core/src/main/scala/caliban/parsing/adt/Type.scala b/core/src/main/scala/caliban/parsing/adt/Type.scala index b3b0506734..0dcca87e07 100644 --- a/core/src/main/scala/caliban/parsing/adt/Type.scala +++ b/core/src/main/scala/caliban/parsing/adt/Type.scala @@ -4,7 +4,8 @@ import scala.annotation.tailrec sealed trait Type extends Serializable { self => val nonNull: Boolean - lazy val nullable: Boolean = !nonNull + + final def nullable: Boolean = !nonNull override def toString: String = self match { case Type.NamedType(name, nonNull) => if (nonNull) s"$name!" else name diff --git a/core/src/main/scala/caliban/schema/Annotations.scala b/core/src/main/scala/caliban/schema/Annotations.scala index 25c7a6c0a3..39b662b9f1 100644 --- a/core/src/main/scala/caliban/schema/Annotations.scala +++ b/core/src/main/scala/caliban/schema/Annotations.scala @@ -76,4 +76,9 @@ object Annotations extends AnnotationsVersionSpecific { */ case class GQLNonNullable() extends StaticAnnotation + /** + * Annotation to make a sealed trait as a GraphQL @oneOf input + */ + case class GQLOneOfInput() extends StaticAnnotation + } diff --git a/core/src/main/scala/caliban/schema/ArgBuilder.scala b/core/src/main/scala/caliban/schema/ArgBuilder.scala index 78ce9157e5..985f97d213 100644 --- a/core/src/main/scala/caliban/schema/ArgBuilder.scala +++ b/core/src/main/scala/caliban/schema/ArgBuilder.scala @@ -35,6 +35,20 @@ trait ArgBuilder[T] { self => */ def build(input: InputValue): Either[ExecutionError, T] + private[schema] def partial: PartialFunction[InputValue, Either[ExecutionError, T]] = + new PartialFunction[InputValue, Either[ExecutionError, T]] { + final def isDefinedAt(x: InputValue): Boolean = build(x).isRight + final def apply(x: InputValue): Either[ExecutionError, T] = build(x) + + final override def applyOrElse[A1 <: InputValue, B1 >: Either[ExecutionError, T]]( + x: A1, + default: A1 => B1 + ): B1 = { + val maybeMatch = build(x) + if (maybeMatch.isRight) maybeMatch else default(x) + } + } + /** * Builds a value of type `T` from a missing input value. * By default, this delegates to [[build]], passing it NullValue. diff --git a/core/src/main/scala/caliban/schema/Schema.scala b/core/src/main/scala/caliban/schema/Schema.scala index 24657497f6..56b8e57c06 100644 --- a/core/src/main/scala/caliban/schema/Schema.scala +++ b/core/src/main/scala/caliban/schema/Schema.scala @@ -469,22 +469,23 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema { ev2: Schema[RB, B] ): Schema[RB, A => B] = new Schema[RB, A => B] { - private lazy val inputType = ev1.toType_(true) - private val unwrappedArgumentName = "value" - override def arguments: List[__InputValue] = { + private lazy val inputType = ev1.toType_(true) + private val unwrappedArgumentName = "value" + + private def mkValueType = List( + __InputValue( + unwrappedArgumentName, + None, + () => if (ev1.nullable || ev1.canFail) inputType else inputType.nonNull, + None + ) + ) + + override lazy val arguments: List[__InputValue] = { val input = inputType.allInputFields - if (input.nonEmpty) input - else - handleInput(List.empty[__InputValue])( - List( - __InputValue( - unwrappedArgumentName, - None, - () => if (ev1.nullable || ev1.canFail) inputType else inputType.nonNull, - None - ) - ) - ) + if (inputType._isOneOfInput) mkValueType + else if (input.nonEmpty) input + else handleInput(List.empty[__InputValue])(mkValueType) } override def nullable: Boolean = ev2.nullable @@ -508,6 +509,7 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema { case __TypeKind.SCALAR | __TypeKind.ENUM | __TypeKind.LIST => // argument was not wrapped in a case class onUnwrapped + case _ if inputType._isOneOfInput => onUnwrapped case _ => onWrapped } } diff --git a/core/src/main/scala/caliban/schema/Types.scala b/core/src/main/scala/caliban/schema/Types.scala index 72b72da762..3ddf7eaf9c 100644 --- a/core/src/main/scala/caliban/schema/Types.scala +++ b/core/src/main/scala/caliban/schema/Types.scala @@ -1,7 +1,7 @@ package caliban.schema import caliban.introspection.adt._ -import caliban.parsing.adt.Directive +import caliban.parsing.adt.{ Directive, Directives } import scala.annotation.tailrec @@ -97,7 +97,8 @@ object Types { if (args.includeDeprecated.getOrElse(false)) Some(fields) else Some(fields.filter(!_.isDeprecated)), origin = origin, - directives = directives + directives = directives, + isOneOf = Some(Directives.isOneOf(directives.getOrElse(Nil))) ) def makeUnion( @@ -240,4 +241,5 @@ object Types { case __TypeKind.NON_NULL => t.ofType.map(name) case _ => t.name }).getOrElse("") + } diff --git a/core/src/main/scala/caliban/transformers/Transformer.scala b/core/src/main/scala/caliban/transformers/Transformer.scala index 11a157f491..7c7ca5f677 100644 --- a/core/src/main/scala/caliban/transformers/Transformer.scala +++ b/core/src/main/scala/caliban/transformers/Transformer.scala @@ -246,18 +246,18 @@ object Transformer { val typeVisitor: TypeVisitor = TypeVisitor.fields.modify { field => - def loop(parentType: Option[String])(arg: __InputValue): Option[__InputValue] = - parentType.flatMap(map.get) match { + def loop(arg: __InputValue): Option[__InputValue] = + arg._parentType.flatMap(_.name).flatMap(map.get) match { case Some(s) if arg._type.isNullable && s.contains(arg.name) => None case _ => lazy val newType = arg._type.mapInnerType { t => - t.copy(inputFields = t.inputFields(_).map(_.flatMap(loop(t.name)))) + t.copy(inputFields = t.inputFields(_).map(_.flatMap(loop))) } Some(arg.copy(`type` = () => newType)) } - field.copy(args = field.args(_).flatMap(loop(None))) + field.copy(args = field.args(_).flatMap(loop)) } protected val typeNames: Set[String] = Set.empty diff --git a/core/src/main/scala/caliban/validation/FragmentValidator.scala b/core/src/main/scala/caliban/validation/FragmentValidator.scala index e1de0b7f64..7a66bad171 100644 --- a/core/src/main/scala/caliban/validation/FragmentValidator.scala +++ b/core/src/main/scala/caliban/validation/FragmentValidator.scala @@ -105,7 +105,7 @@ object FragmentValidator { fields.foreach { case field @ SelectedField( - __Type(_, Some(name), _, _, _, _, _, _, _, _, _, _), + __Type(_, Some(name), _, _, _, _, _, _, _, _, _, _, _), _, _ ) if isConcrete(field.parentType) => diff --git a/core/src/main/scala/caliban/validation/Validator.scala b/core/src/main/scala/caliban/validation/Validator.scala index 8b6e2e29ad..1f9a041a76 100644 --- a/core/src/main/scala/caliban/validation/Validator.scala +++ b/core/src/main/scala/caliban/validation/Validator.scala @@ -374,14 +374,35 @@ object Validator { "If any operation defines more than one variable with the same name, it is ambiguous and invalid. It is invalid even if the type of the duplicate variable is the same." ) } *> validateAll(op.variableDefinitions) { v => - val t = Type.innerType(v.variableType) - failWhen(context.rootType.types.get(t).map(_.kind).exists { + val t = context.rootType.types.get(Type.innerType(v.variableType)) + + val v1 = failWhen(t.map(_.kind).exists { case __TypeKind.OBJECT | __TypeKind.UNION | __TypeKind.INTERFACE => true case _ => false })( s"Type of variable '${v.name}' is not a valid input type.", "Variables can only be input types. Objects, unions, and interfaces cannot be used as inputs." ) + + val v2 = + if ( + t match { + case Some(t0) => t0._isOneOfInput + case _ => false + } + ) + Some( + failWhen(v.variableType.nullable)( + s"Variable '${v.name}' cannot be nullable.", + "Variables used for OneOf Input Object fields must be non-nullable." + ) *> validateOneOfInputValue( + context.variables.getOrElse(v.name, NullValue), + s"Variable '${v.name}'" + ) + ) + else None + + v2.fold(v1)(v1 *> _) } *> { validateAll(variableUsages)(v => failWhen(!op.variableDefinitions.exists(_.name == v))( @@ -562,23 +583,40 @@ object Validator { val fieldArgsNonNull = fieldArgs.filter(_._type.kind == __TypeKind.NON_NULL) val providedArgs = field.arguments - val v1 = validateAllNonEmpty(fieldArgsNonNull)(arg => - (arg.defaultValue, field.arguments.get(arg.name)) match { + val v1 = validateAllNonEmpty(fieldArgsNonNull.flatMap { arg => + val arg0 = field.arguments.get(arg.name) + val opt1 = (arg.defaultValue, arg0) match { case (None, None) | (None, Some(NullValue)) => - failValidation( - s"Required argument '${arg.name}' is null or missing on field '${field.name}' of type '${currentType.name - .getOrElse("")}'.", - "Arguments can be required. An argument is required if the argument type is non‐null and does not have a default value. Otherwise, the argument is optional." + Some( + failValidation( + s"Required argument '${arg.name}' is null or missing on field '${field.name}' of type '${currentType.name + .getOrElse("")}'.", + "Arguments can be required. An argument is required if the argument type is non‐null and does not have a default value. Otherwise, the argument is optional." + ) ) case (Some(_), Some(NullValue)) => - failValidation( - s"Required argument '${arg.name}' is null on '${field.name}' of type '${currentType.name - .getOrElse("")}'.", - "Arguments can be required. An argument is required if the argument type is non‐null and does not have a default value. Otherwise, the argument is optional." + Some( + failValidation( + s"Required argument '${arg.name}' is null on '${field.name}' of type '${currentType.name + .getOrElse("")}'.", + "Arguments can be required. An argument is required if the argument type is non‐null and does not have a default value. Otherwise, the argument is optional." + ) ) - case _ => zunit + case _ => None } - ) + val opt2 = + if (arg._type.innerType._isOneOfInput) + Some( + validateOneOfInputValue( + arg0.getOrElse(NullValue), + s"Argument '${arg.name}' on field '${field.name}'" + ) + ) + else None + + combineOptionalValidations(opt1, opt2) + })(identity) + val v2 = validateAllNonEmpty(providedArgs) { case (arg, argValue) => fieldArgs.find(_.name == arg) match { case Some(inputValue) => @@ -649,6 +687,23 @@ object Validator { } } *> ValueValidator.validateInputTypes(inputValue, argValue, context, errorContext) + private def validateOneOfInputValue( + inputValue: InputValue, + errorContext: => String + ): EReader[Any, ValidationError, Unit] = + ZPure + .whenCase(inputValue match { + case InputValue.ObjectValue(fields) if fields.size == 1 => fields.headOption.map(_._2) + case vv: InputValue.VariableValue => Some(vv) + case _ => None + }) { case None | Some(NullValue) => + failValidation( + s"$errorContext is not a valid OneOf Input Object", + "OneOf Input Object arguments must specify exactly one non-null key" + ) + } + .unit + private def checkVariableUsageAllowed( variableDefinition: VariableDefinition, inputValue: __InputValue @@ -865,7 +920,7 @@ object Validator { } private[caliban] def validateInputObject(t: __Type): EReader[Any, ValidationError, Unit] = { - lazy val inputObjectContext = s"""InputObject '${t.name.getOrElse("")}'""" + lazy val inputObjectContext = s"""${if (t._isOneOfInput) "OneOf " else ""}InputObject '${t.name.getOrElse("")}'""" def noDuplicateInputValueName( inputValues: List[__InputValue], @@ -877,17 +932,40 @@ object Validator { noDuplicateName[__InputValue](inputValues, _.name, messageBuilder, explanatory) } + def noDuplicatedOneOfOrigin(inputValues: List[__InputValue]): EReader[Any, ValidationError, Unit] = { + val resolveOrigin = (i: __InputValue) => + i._parentType.flatMap(_.origin).getOrElse("") + val messageBuilder = (i: __InputValue) => + s"$inputObjectContext is extended by a case class with multiple arguments: ${resolveOrigin(i)}" + val explanatory = "All case classes used as arguments to OneOf Input Objects must have exactly one field" + noDuplicateName[__InputValue](inputValues, resolveOrigin, messageBuilder, explanatory) + } + def validateFields(fields: List[__InputValue]): EReader[Any, ValidationError, Unit] = validateAll(fields)(validateInputValue(_, inputObjectContext)) *> noDuplicateInputValueName(fields, inputObjectContext) + def validateOneOfFields(fields: List[__InputValue]): EReader[Any, ValidationError, Unit] = + noDuplicatedOneOfOrigin(fields) *> + validateAll(fields) { f => + failWhen(f.defaultValue.isDefined)( + s"$inputObjectContext argument has a default value", + "Fields of OneOf input objects cannot have default values" + ) *> + failWhen(!f._type.isNullable)( + s"$inputObjectContext argument is not nullable", + "All of OneOf input fields must be declared as nullable in the schema according to the spec" + ) + } + t.allInputFields match { - case Nil => + case Nil => failValidation( s"$inputObjectContext does not have fields", "An Input Object type must define one or more input fields" ) - case fields => validateFields(fields) + case fields if t._isOneOfInput => validateOneOfFields(fields) *> validateFields(fields) + case fields => validateFields(fields) } } diff --git a/core/src/test/scala/caliban/RenderingSpec.scala b/core/src/test/scala/caliban/RenderingSpec.scala index 7dcf5603e2..441ea8ae4c 100644 --- a/core/src/test/scala/caliban/RenderingSpec.scala +++ b/core/src/test/scala/caliban/RenderingSpec.scala @@ -4,17 +4,19 @@ import caliban.CalibanError.ParsingError import caliban.TestUtils._ import caliban.introspection.adt.{ __Type, __TypeKind } import caliban.parsing.Parser -import caliban.parsing.adt.Definition.{ TypeSystemDefinition, TypeSystemExtension } import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition.{ EnumValueDefinition, FieldDefinition, InputValueDefinition } +import caliban.parsing.adt.Definition.{ TypeSystemDefinition, TypeSystemExtension } import caliban.parsing.adt.{ Definition, Directive } import caliban.rendering.DocumentRenderer +import caliban.schema.Annotations.GQLOneOfInput import caliban.schema.Schema.auto._ import caliban.schema.ArgBuilder.auto._ +import caliban.schema.{ ArgBuilder, Schema } import zio.{ IO, ZIO } import zio.test.Assertion._ import zio.test._ @@ -179,7 +181,51 @@ object RenderingSpec extends ZIOSpecDefault { test("kitchen sink with query")(roundTrip("document-tests/kitchen-sink-query.graphql")), test("compact query")(roundTrip("document-tests/query-compact.graphql", isCompact = true)), test("compact kitchen sink")(roundTrip("document-tests/kitchen-sink-compact.graphql", isCompact = true)) - ) + ), + suite("OneOf input objects") { + def expected(label: String) = + s"""schema { + | query: Queries + |} + | + |input FooInput @oneOf { + | stringValue: String + | otherStringField: String + | intValue: FooIntInput + | otherIntField: FooInt2Input + |} + | + |input FooInt2Input { + | intValue: Int! + |} + | + |input FooIntInput { + | intValue: Int! + |} + | + |type Queries { + | foo($label: FooInput!): String! + |}""".stripMargin + + List( + test("as value types") { + case class Queries(foo: Foo => String) + + implicit val schema: Schema[Any, Queries] = Schema.gen + val resolver = RootResolver(Queries(_.toString)) + + assertTrue(graphQL(resolver).render == expected("value")) + }, + test("wrapped in a case class") { + case class Queries(foo: Foo.Wrapped => String) + + implicit val schema: Schema[Any, Queries] = Schema.gen + val resolver = RootResolver(Queries(_.toString)) + + assertTrue(graphQL(resolver).render == expected("fooInput")) + } + ) + } ) private def roundTrip(file: String, isCompact: Boolean = false) = @@ -190,4 +236,25 @@ object RenderingSpec extends ZIOSpecDefault { reparsed <- Parser.parseQuery(rendered).either } yield assertTrue(input == rendered, reparsed.isRight)) + @GQLOneOfInput + sealed trait Foo + + object Foo { + case class ArgA(stringValue: String) extends Foo + case class ArgB(otherStringField: String) extends Foo + case class ArgC(intValue: FooInt) extends Foo + case class ArgD(otherIntField: FooInt2) extends Foo + + case class FooInt(intValue: Int) + case class FooInt2(intValue: Int) + + case class Wrapped(fooInput: Foo) + } + + implicit val fooIntAb: ArgBuilder[Foo.FooInt] = ArgBuilder.gen + implicit val fooInt2Ab: ArgBuilder[Foo.FooInt2] = ArgBuilder.gen + implicit val fooAb: ArgBuilder[Foo] = ArgBuilder.gen + implicit val fooIntSchema: Schema[Any, Foo.FooInt] = Schema.gen + implicit val fooInt2Schema: Schema[Any, Foo.FooInt2] = Schema.gen + implicit val fooSchema: Schema[Any, Foo] = Schema.gen } diff --git a/core/src/test/scala/caliban/TestUtils.scala b/core/src/test/scala/caliban/TestUtils.scala index df9a26c73f..203529fec8 100644 --- a/core/src/test/scala/caliban/TestUtils.scala +++ b/core/src/test/scala/caliban/TestUtils.scala @@ -103,6 +103,15 @@ object TestUtils { case class CharacterInArgs(@GQLDirective(Directive("lowercase")) names: List[String]) case class CharacterObjectArgs(character: CharacterInput) + @GQLOneOfInput + sealed trait NameOrOrigin + object NameOrOrigin { + case class ByName(name: String) extends NameOrOrigin + case class ByOrigin(origin: Origin) extends NameOrOrigin + + case class Wrapper(nameOrOrigin: NameOrOrigin) + } + @GQLDescription("Queries") case class Query( @GQLDescription("Return all characters from a given origin") characters: CharactersArgs => List[Character], @@ -118,6 +127,12 @@ object TestUtils { @GQLDeprecated("Use `characters`") character: CharacterArgs => UIO[Option[Character]] ) + @GQLDescription("Queries") + case class QueryWithOneOf( + exists: CharacterObjectArgs => Boolean, + character: NameOrOrigin.Wrapper => List[Character] + ) + @GQLDescription("Mutations") case class MutationIO(deleteCharacter: CharacterArgs => UIO[Unit]) @@ -126,6 +141,7 @@ object TestUtils { implicit val characterSchema: Schema[Any, Character] = genAll implicit val querySchema: Schema[Any, Query] = genAll implicit val queryIOSchema: Schema[Any, QueryIO] = genAll + implicit val queryOneOfSchema: Schema[Any, QueryWithOneOf] = genAll implicit val mutationIOSchema: Schema[Any, MutationIO] = genAll implicit val subscriptionIOSchema: Schema[Any, SubscriptionIO] = genAll @@ -144,6 +160,15 @@ object TestUtils { args => ZIO.succeed(characters.find(c => c.name == args.name)) ) ) + val resolverOneOf = RootResolver( + QueryWithOneOf( + args => characters.exists(_.name == args.character.name), + { + case NameOrOrigin.Wrapper(NameOrOrigin.ByName(name)) => characters.filter(c => c.name == name) + case NameOrOrigin.Wrapper(NameOrOrigin.ByOrigin(origin)) => characters.filter(c => c.origin == origin) + } + ) + ) val resolverWithMutation = RootResolver( resolverIO.queryResolver, MutationIO(_ => ZIO.unit) diff --git a/core/src/test/scala/caliban/execution/DefaultValueSpec.scala b/core/src/test/scala/caliban/execution/DefaultValueSpec.scala index 3acd508cb1..d5a6d0e2d9 100644 --- a/core/src/test/scala/caliban/execution/DefaultValueSpec.scala +++ b/core/src/test/scala/caliban/execution/DefaultValueSpec.scala @@ -222,10 +222,10 @@ object DefaultValueSpec extends ZIOSpecDefault { case class Query(testDefault: TestInput => Int) val interpreter = graphQL(RootResolver(Query(i => i.intValue))).interpreter - interpreter.flatMap(_.execute(introspectionQuery)).map { response => + interpreter.flatMap(_.execute(introspectionQuery)).map(_.data.toString).map { response => assertTrue( - response.data.toString == - """{"__schema":{"queryType":{"name":"Query"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","fields":null,"inputFields":null},{"kind":"SCALAR","name":"Float","fields":null,"inputFields":null},{"kind":"SCALAR","name":"Int","fields":null,"inputFields":null},{"kind":"OBJECT","name":"Query","fields":[{"name":"testDefault","description":null,"args":[{"name":"intValue","description":null,"defaultValue":"1"}]}],"inputFields":null},{"kind":"SCALAR","name":"String","fields":null,"inputFields":null},{"kind":"OBJECT","name":"__Directive","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"locations","description":null,"args":[]},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"isRepeatable","description":null,"args":[]}],"inputFields":null},{"kind":"ENUM","name":"__DirectiveLocation","fields":null,"inputFields":null},{"kind":"OBJECT","name":"__EnumValue","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"isDeprecated","description":null,"args":[]},{"name":"deprecationReason","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__Field","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"type","description":null,"args":[]},{"name":"isDeprecated","description":null,"args":[]},{"name":"deprecationReason","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__InputValue","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"type","description":null,"args":[]},{"name":"defaultValue","description":null,"args":[]},{"name":"isDeprecated","description":null,"args":[]},{"name":"deprecationReason","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__Schema","fields":[{"name":"description","description":null,"args":[]},{"name":"queryType","description":null,"args":[]},{"name":"mutationType","description":null,"args":[]},{"name":"subscriptionType","description":null,"args":[]},{"name":"types","description":null,"args":[]},{"name":"directives","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__Type","fields":[{"name":"kind","description":null,"args":[]},{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"interfaces","description":null,"args":[]},{"name":"possibleTypes","description":null,"args":[]},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"ofType","description":null,"args":[]},{"name":"specifiedBy","description":null,"args":[]}],"inputFields":null},{"kind":"ENUM","name":"__TypeKind","fields":null,"inputFields":null}]}}""" + response == + """{"__schema":{"queryType":{"name":"Query"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","fields":null,"inputFields":null},{"kind":"SCALAR","name":"Float","fields":null,"inputFields":null},{"kind":"SCALAR","name":"Int","fields":null,"inputFields":null},{"kind":"OBJECT","name":"Query","fields":[{"name":"testDefault","description":null,"args":[{"name":"intValue","description":null,"defaultValue":"1"}]}],"inputFields":null},{"kind":"SCALAR","name":"String","fields":null,"inputFields":null},{"kind":"OBJECT","name":"__Directive","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"locations","description":null,"args":[]},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"isRepeatable","description":null,"args":[]}],"inputFields":null},{"kind":"ENUM","name":"__DirectiveLocation","fields":null,"inputFields":null},{"kind":"OBJECT","name":"__EnumValue","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"isDeprecated","description":null,"args":[]},{"name":"deprecationReason","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__Field","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"type","description":null,"args":[]},{"name":"isDeprecated","description":null,"args":[]},{"name":"deprecationReason","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__InputValue","fields":[{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"type","description":null,"args":[]},{"name":"defaultValue","description":null,"args":[]},{"name":"isDeprecated","description":null,"args":[]},{"name":"deprecationReason","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__Schema","fields":[{"name":"description","description":null,"args":[]},{"name":"queryType","description":null,"args":[]},{"name":"mutationType","description":null,"args":[]},{"name":"subscriptionType","description":null,"args":[]},{"name":"types","description":null,"args":[]},{"name":"directives","description":null,"args":[]}],"inputFields":null},{"kind":"OBJECT","name":"__Type","fields":[{"name":"kind","description":null,"args":[]},{"name":"name","description":null,"args":[]},{"name":"description","description":null,"args":[]},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"interfaces","description":null,"args":[]},{"name":"possibleTypes","description":null,"args":[]},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"defaultValue":null}]},{"name":"ofType","description":null,"args":[]},{"name":"specifiedBy","description":null,"args":[]},{"name":"isOneOf","description":null,"args":[]}],"inputFields":null},{"kind":"ENUM","name":"__TypeKind","fields":null,"inputFields":null}]}}""" ) } } diff --git a/core/src/test/scala/caliban/execution/ExecutionSpec.scala b/core/src/test/scala/caliban/execution/ExecutionSpec.scala index ca833b42c4..8976106c63 100644 --- a/core/src/test/scala/caliban/execution/ExecutionSpec.scala +++ b/core/src/test/scala/caliban/execution/ExecutionSpec.scala @@ -1,13 +1,15 @@ package caliban.execution +import java.util.UUID import caliban.CalibanError.ExecutionError +import caliban.InputValue.ObjectValue import caliban.Macros.gqldoc import caliban.TestUtils._ import caliban.Value.{ BooleanValue, IntValue, NullValue, StringValue } import caliban._ import caliban.introspection.adt.__Type import caliban.parsing.adt.LocationInfo -import caliban.schema.Annotations.{ GQLInterface, GQLName, GQLValueType } +import caliban.schema.Annotations.{ GQLInterface, GQLName, GQLOneOfInput, GQLValueType } import caliban.schema.ArgBuilder.auto._ import caliban.schema.Schema.auto._ import caliban.schema._ @@ -1431,6 +1433,52 @@ object ExecutionSpec extends ZIOSpecDefault { r6 == List("m2", "m1-f2", "m1-f1", "m1"), r7 == List("m2-f2", "m2-f1", "m2", "m1-f2", "m1-f1", "m1") ) + }, + test("oneOf inputs") { + + @GQLOneOfInput + sealed trait Foo + object Foo { + case class FooString(stringValue: String) extends Foo + case class FooInt(intValue: Int) extends Foo + + case class Wrapper(fooInput: Foo) + } + + case class Queries(foo: Foo.Wrapper => String, fooUnwrapped: Foo => String) + + implicit val fooStringAb: ArgBuilder[Foo.FooString] = ArgBuilder.gen + implicit val fooIntAb: ArgBuilder[Foo.FooInt] = ArgBuilder.gen + implicit val fooAb: ArgBuilder[Foo] = ArgBuilder.gen + implicit val schema: Schema[Any, Queries] = Schema.gen + + val api: GraphQL[Any] = graphQL( + RootResolver( + Queries( + { + case Foo.Wrapper(Foo.FooString(value)) => value + case Foo.Wrapper(Foo.FooInt(value)) => value.toString + }, + { + case Foo.FooString(value) => value + case Foo.FooInt(value) => value.toString + } + ) + ) + ) + + val cases = List( + gqldoc("""{ foo(fooInput: {stringValue: "hello"}) }""") -> """{"foo":"hello"}""", + gqldoc("""{ foo(fooInput: {intValue: 42}) }""") -> """{"foo":"42"}""", + gqldoc("""{ fooUnwrapped(value: {intValue: 42}) }""") -> """{"fooUnwrapped":"42"}""", + gqldoc("""query Foo($args: FooInput!){ fooUnwrapped(value: $args) }""") -> """{"fooUnwrapped":"42"}""" + ) + + ZIO.foldLeft(cases)(assertCompletes) { case (acc, (query, expected)) => + api.interpreter + .flatMap(_.execute(query, variables = Map("args" -> ObjectValue(Map("intValue" -> IntValue(42)))))) + .map(response => assertTrue(response.data.toString == expected)) + } } ) } diff --git a/core/src/test/scala/caliban/introspection/IntrospectionSpec.scala b/core/src/test/scala/caliban/introspection/IntrospectionSpec.scala index 72cd48a0ba..8303139f91 100644 --- a/core/src/test/scala/caliban/introspection/IntrospectionSpec.scala +++ b/core/src/test/scala/caliban/introspection/IntrospectionSpec.scala @@ -36,6 +36,7 @@ object IntrospectionSpec extends ZIOSpecDefault { kind name description + isOneOf fields(includeDeprecated: true) { name description @@ -122,10 +123,10 @@ object IntrospectionSpec extends ZIOSpecDefault { test("introspect schema") { val interpreter = graphQL(resolverIO).interpreter - interpreter.flatMap(_.execute(fullIntrospectionQuery)).map { response => + interpreter.flatMap(_.execute(fullIntrospectionQuery)).map(_.data.toString).map { response => assertTrue( - response.data.toString == - """{"__schema":{"queryType":{"name":"QueryIO"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Captain","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"CaptainShipName","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"CaptainShipName","description":"Description of custom scalar emphasizing proper captain ship names","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Character","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"nicknames","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"origin","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Origin","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"role","description":null,"args":[],"type":{"kind":"UNION","name":"Role","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Engineer","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Float","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Int","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Mechanic","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"Origin","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"BELT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"EARTH","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MARS","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MOON","description":null,"isDeprecated":true,"deprecationReason":"Use: EARTH | MARS | BELT"}],"possibleTypes":null},{"kind":"OBJECT","name":"Pilot","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"QueryIO","description":"Queries","fields":[{"name":"characters","description":"Return all characters from a given origin","args":[{"name":"origin","description":null,"type":{"kind":"ENUM","name":"Origin","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Character","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"character","description":null,"args":[{"name":"name","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Character","ofType":null},"isDeprecated":true,"deprecationReason":"Use `characters`"}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"UNION","name":"Role","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Captain","ofType":null},{"kind":"OBJECT","name":"Engineer","ofType":null},{"kind":"OBJECT","name":"Mechanic","ofType":null},{"kind":"OBJECT","name":"Pilot","ofType":null}]},{"kind":"SCALAR","name":"String","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Directive","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"locations","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__DirectiveLocation","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"isRepeatable","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__DirectiveLocation","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ARGUMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM_VALUE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_SPREAD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INLINE_FRAGMENT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MUTATION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"QUERY","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCHEMA","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SUBSCRIPTION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"VARIABLE_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"__EnumValue","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Field","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__InputValue","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"defaultValue","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Schema","description":null,"fields":[{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"queryType","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"mutationType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"subscriptionType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"types","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"directives","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Type","description":null,"fields":[{"name":"kind","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"interfaces","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"possibleTypes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"ofType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"specifiedBy","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__TypeKind","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"LIST","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"NON_NULL","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null}],"directives":[{"name":"skip","description":"The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"include","description":"The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"specifiedBy","description":"The @specifiedBy directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar types. The URL should point to a human-readable specification of the data format, serialization, and coercion rules. It must not appear on built-in scalar types.","locations":["SCALAR"],"args":[{"name":"url","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}]}]}}""" + response == + """{"__schema":{"queryType":{"name":"QueryIO"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Captain","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"CaptainShipName","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"CaptainShipName","description":"Description of custom scalar emphasizing proper captain ship names","isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Character","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"nicknames","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"origin","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Origin","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"role","description":null,"args":[],"type":{"kind":"UNION","name":"Role","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Engineer","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Float","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Int","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Mechanic","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"Origin","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"BELT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"EARTH","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MARS","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MOON","description":null,"isDeprecated":true,"deprecationReason":"Use: EARTH | MARS | BELT"}],"possibleTypes":null},{"kind":"OBJECT","name":"Pilot","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"QueryIO","description":"Queries","isOneOf":null,"fields":[{"name":"characters","description":"Return all characters from a given origin","args":[{"name":"origin","description":null,"type":{"kind":"ENUM","name":"Origin","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Character","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"character","description":null,"args":[{"name":"name","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Character","ofType":null},"isDeprecated":true,"deprecationReason":"Use `characters`"}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"UNION","name":"Role","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Captain","ofType":null},{"kind":"OBJECT","name":"Engineer","ofType":null},{"kind":"OBJECT","name":"Mechanic","ofType":null},{"kind":"OBJECT","name":"Pilot","ofType":null}]},{"kind":"SCALAR","name":"String","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Directive","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"locations","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__DirectiveLocation","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"isRepeatable","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__DirectiveLocation","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ARGUMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM_VALUE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_SPREAD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INLINE_FRAGMENT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MUTATION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"QUERY","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCHEMA","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SUBSCRIPTION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"VARIABLE_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"__EnumValue","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Field","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__InputValue","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"defaultValue","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Schema","description":null,"isOneOf":null,"fields":[{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"queryType","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"mutationType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"subscriptionType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"types","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"directives","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Type","description":null,"isOneOf":null,"fields":[{"name":"kind","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"interfaces","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"possibleTypes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"ofType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"specifiedBy","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isOneOf","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__TypeKind","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"LIST","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"NON_NULL","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null}],"directives":[{"name":"skip","description":"The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"include","description":"The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"specifiedBy","description":"The @specifiedBy directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar types. The URL should point to a human-readable specification of the data format, serialization, and coercion rules. It must not appear on built-in scalar types.","locations":["SCALAR"],"args":[{"name":"url","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}]}]}}""" ) } }, @@ -150,10 +151,20 @@ object IntrospectionSpec extends ZIOSpecDefault { val interpreter = (graphQL(resolverIO) @@ hideWrapper).interpreter - interpreter.flatMap(_.execute(fullIntrospectionQuery)).map { response => + interpreter.flatMap(_.execute(fullIntrospectionQuery)).map(_.data.toString).map { response => assertTrue( - response.data.toString == - """{"__schema":{"queryType":{"name":"QueryIO"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Captain","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"CaptainShipName","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"CaptainShipName","description":"Description of custom scalar emphasizing proper captain ship names","fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Character","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"nicknames","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"origin","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Origin","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"role","description":null,"args":[],"type":{"kind":"UNION","name":"Role","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Engineer","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Float","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Int","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Mechanic","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"Origin","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"BELT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"EARTH","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MARS","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MOON","description":null,"isDeprecated":true,"deprecationReason":"Use: EARTH | MARS | BELT"}],"possibleTypes":null},{"kind":"OBJECT","name":"Pilot","description":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"QueryIO","description":"Queries","fields":[{"name":"characters","description":"Return all characters from a given origin","args":[{"name":"origin","description":null,"type":{"kind":"ENUM","name":"Origin","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Character","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"UNION","name":"Role","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Captain","ofType":null},{"kind":"OBJECT","name":"Engineer","ofType":null},{"kind":"OBJECT","name":"Mechanic","ofType":null},{"kind":"OBJECT","name":"Pilot","ofType":null}]},{"kind":"SCALAR","name":"String","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Directive","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"locations","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__DirectiveLocation","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"isRepeatable","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__DirectiveLocation","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ARGUMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM_VALUE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_SPREAD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INLINE_FRAGMENT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MUTATION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"QUERY","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCHEMA","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SUBSCRIPTION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"VARIABLE_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"__EnumValue","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Field","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__InputValue","description":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"defaultValue","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Schema","description":null,"fields":[{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"queryType","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"mutationType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"subscriptionType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"types","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"directives","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Type","description":null,"fields":[{"name":"kind","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"interfaces","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"possibleTypes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"ofType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"specifiedBy","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__TypeKind","description":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"LIST","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"NON_NULL","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null}],"directives":[{"name":"skip","description":"The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"include","description":"The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"specifiedBy","description":"The @specifiedBy directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar types. The URL should point to a human-readable specification of the data format, serialization, and coercion rules. It must not appear on built-in scalar types.","locations":["SCALAR"],"args":[{"name":"url","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}]}]}}""" + response == + """{"__schema":{"queryType":{"name":"QueryIO"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Captain","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"CaptainShipName","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"CaptainShipName","description":"Description of custom scalar emphasizing proper captain ship names","isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Character","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"nicknames","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"origin","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Origin","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"role","description":null,"args":[],"type":{"kind":"UNION","name":"Role","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Engineer","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Float","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Int","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Mechanic","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"Origin","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"BELT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"EARTH","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MARS","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MOON","description":null,"isDeprecated":true,"deprecationReason":"Use: EARTH | MARS | BELT"}],"possibleTypes":null},{"kind":"OBJECT","name":"Pilot","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"QueryIO","description":"Queries","isOneOf":null,"fields":[{"name":"characters","description":"Return all characters from a given origin","args":[{"name":"origin","description":null,"type":{"kind":"ENUM","name":"Origin","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Character","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"UNION","name":"Role","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Captain","ofType":null},{"kind":"OBJECT","name":"Engineer","ofType":null},{"kind":"OBJECT","name":"Mechanic","ofType":null},{"kind":"OBJECT","name":"Pilot","ofType":null}]},{"kind":"SCALAR","name":"String","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Directive","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"locations","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__DirectiveLocation","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"isRepeatable","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__DirectiveLocation","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ARGUMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM_VALUE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_SPREAD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INLINE_FRAGMENT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MUTATION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"QUERY","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCHEMA","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SUBSCRIPTION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"VARIABLE_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"__EnumValue","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Field","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__InputValue","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"defaultValue","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Schema","description":null,"isOneOf":null,"fields":[{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"queryType","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"mutationType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"subscriptionType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"types","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"directives","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Type","description":null,"isOneOf":null,"fields":[{"name":"kind","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"interfaces","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"possibleTypes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"ofType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"specifiedBy","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isOneOf","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__TypeKind","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"LIST","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"NON_NULL","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null}],"directives":[{"name":"skip","description":"The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"include","description":"The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"specifiedBy","description":"The @specifiedBy directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar types. The URL should point to a human-readable specification of the data format, serialization, and coercion rules. It must not appear on built-in scalar types.","locations":["SCALAR"],"args":[{"name":"url","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}]}]}}""" + ) + } + }, + test("introspect schema with OneOf input objects") { + val interpreter = graphQL(resolverOneOf).interpreter + + interpreter.flatMap(_.execute(fullIntrospectionQuery)).map(_.data.toString).map { response => + assertTrue( + response == + """{"__schema":{"queryType":{"name":"QueryWithOneOf"},"mutationType":null,"subscriptionType":null,"types":[{"kind":"SCALAR","name":"Boolean","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Captain","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"CaptainShipName","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"CaptainShipName","description":"Description of custom scalar emphasizing proper captain ship names","isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Character","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"nicknames","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"origin","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Origin","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"role","description":null,"args":[],"type":{"kind":"UNION","name":"Role","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"INPUT_OBJECT","name":"CharacterInput","description":null,"isOneOf":false,"fields":null,"inputFields":[{"name":"name","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null},{"name":"nicknames","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}},"defaultValue":null},{"name":"origin","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Origin","ofType":null}},"defaultValue":null}],"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Engineer","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Float","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"SCALAR","name":"Int","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"Mechanic","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"INPUT_OBJECT","name":"NameOrOriginInput","description":null,"isOneOf":true,"fields":null,"inputFields":[{"name":"name","description":null,"type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null},{"name":"origin","description":null,"type":{"kind":"ENUM","name":"Origin","ofType":null},"defaultValue":null}],"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"Origin","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"BELT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"EARTH","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MARS","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MOON","description":null,"isDeprecated":true,"deprecationReason":"Use: EARTH | MARS | BELT"}],"possibleTypes":null},{"kind":"OBJECT","name":"Pilot","description":null,"isOneOf":null,"fields":[{"name":"shipName","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"QueryWithOneOf","description":"Queries","isOneOf":null,"fields":[{"name":"exists","description":null,"args":[{"name":"character","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"INPUT_OBJECT","name":"CharacterInput","ofType":null}},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"character","description":null,"args":[{"name":"nameOrOrigin","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"INPUT_OBJECT","name":"NameOrOriginInput","ofType":null}},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"Character","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"UNION","name":"Role","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Captain","ofType":null},{"kind":"OBJECT","name":"Engineer","ofType":null},{"kind":"OBJECT","name":"Mechanic","ofType":null},{"kind":"OBJECT","name":"Pilot","ofType":null}]},{"kind":"SCALAR","name":"String","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Directive","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"locations","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__DirectiveLocation","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"isRepeatable","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__DirectiveLocation","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ARGUMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"ENUM_VALUE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"FRAGMENT_SPREAD","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INLINE_FRAGMENT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_FIELD_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"MUTATION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"QUERY","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCHEMA","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SUBSCRIPTION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"VARIABLE_DEFINITION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null},{"kind":"OBJECT","name":"__EnumValue","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Field","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"args","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__InputValue","description":null,"isOneOf":null,"fields":[{"name":"name","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"type","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"defaultValue","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isDeprecated","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"deprecationReason","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Schema","description":null,"isOneOf":null,"fields":[{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"queryType","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"mutationType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"subscriptionType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"types","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}},"isDeprecated":false,"deprecationReason":null},{"name":"directives","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive","ofType":null}}}},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"OBJECT","name":"__Type","description":null,"isOneOf":null,"fields":[{"name":"kind","description":null,"args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"name","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"description","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"fields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"interfaces","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"possibleTypes","description":null,"args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"enumValues","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"inputFields","description":null,"args":[{"name":"includeDeprecated","description":null,"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"defaultValue":null}],"type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}},"isDeprecated":false,"deprecationReason":null},{"name":"ofType","description":null,"args":[],"type":{"kind":"OBJECT","name":"__Type","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"specifiedBy","description":null,"args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"isOneOf","description":null,"args":[],"type":{"kind":"SCALAR","name":"Boolean","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":null,"interfaces":[],"enumValues":null,"possibleTypes":null},{"kind":"ENUM","name":"__TypeKind","description":null,"isOneOf":null,"fields":null,"inputFields":null,"interfaces":null,"enumValues":[{"name":"ENUM","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INPUT_OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"INTERFACE","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"LIST","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"NON_NULL","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"OBJECT","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"SCALAR","description":null,"isDeprecated":false,"deprecationReason":null},{"name":"UNION","description":null,"isDeprecated":false,"deprecationReason":null}],"possibleTypes":null}],"directives":[{"name":"skip","description":"The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"include","description":"The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"specifiedBy","description":"The @specifiedBy directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar types. The URL should point to a human-readable specification of the data format, serialization, and coercion rules. It must not appear on built-in scalar types.","locations":["SCALAR"],"args":[{"name":"url","description":null,"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}]},{"name":"oneOf","description":"The `@oneOf` directive is used within the type system definition language to indicate an Input Object is a OneOf Input Object.","locations":["INPUT_OBJECT"],"args":[]}]}}""" ) } }, @@ -167,8 +178,8 @@ object IntrospectionSpec extends ZIOSpecDefault { } """) - interpreter.flatMap(_.execute(query)).map { response => - assertTrue(response.data.toString == """{"__type":{"name":"Captain"}}""") + interpreter.flatMap(_.execute(query)).map(_.data.toString).map { response => + assertTrue(response == """{"__type":{"name":"Captain"}}""") } }, test("introspect non-existent type") { @@ -181,8 +192,8 @@ object IntrospectionSpec extends ZIOSpecDefault { } """) - interpreter.flatMap(_.execute(query)).map { response => - assertTrue(response.data.toString == """{"__type":null}""") + interpreter.flatMap(_.execute(query)).map(_.data.toString).map { response => + assertTrue(response == """{"__type":null}""") } }, test("introspect the introspection itself") { @@ -203,11 +214,33 @@ object IntrospectionSpec extends ZIOSpecDefault { } """) - interpreter.flatMap(_.execute(query)).map { response => + interpreter.flatMap(_.execute(query)).map(_.data.toString).map { response => assertTrue( - response.data.toString == """{"__type":{"name":"__Schema","fields":[{"name":"description"},{"name":"queryType"},{"name":"mutationType"},{"name":"subscriptionType"},{"name":"types"},{"name":"directives"}]},"__schema":{"types":[{"name":"Boolean"},{"name":"Captain"},{"name":"CaptainShipName"},{"name":"Character"},{"name":"Engineer"},{"name":"Float"},{"name":"Int"},{"name":"Mechanic"},{"name":"Origin"},{"name":"Pilot"},{"name":"QueryIO"},{"name":"Role"},{"name":"String"},{"name":"__Directive"},{"name":"__DirectiveLocation"},{"name":"__EnumValue"},{"name":"__Field"},{"name":"__InputValue"},{"name":"__Schema"},{"name":"__Type"},{"name":"__TypeKind"}]}}""" + response == """{"__type":{"name":"__Schema","fields":[{"name":"description"},{"name":"queryType"},{"name":"mutationType"},{"name":"subscriptionType"},{"name":"types"},{"name":"directives"}]},"__schema":{"types":[{"name":"Boolean"},{"name":"Captain"},{"name":"CaptainShipName"},{"name":"Character"},{"name":"Engineer"},{"name":"Float"},{"name":"Int"},{"name":"Mechanic"},{"name":"Origin"},{"name":"Pilot"},{"name":"QueryIO"},{"name":"Role"},{"name":"String"},{"name":"__Directive"},{"name":"__DirectiveLocation"},{"name":"__EnumValue"},{"name":"__Field"},{"name":"__InputValue"},{"name":"__Schema"},{"name":"__Type"},{"name":"__TypeKind"}]}}""" ) } + }, + test("introspect OneOf input object types") { + val interpreter = graphQL(resolverOneOf).interpreter + + def query(name: String) = + s""" + query { + __type(name: "$name") { + name + isOneOf + } + } + """ + + for { + i <- interpreter + r1 <- i.execute(query("NameOrOriginInput")) + r2 <- i.execute(query("CharacterInput")) + } yield assertTrue( + r1.data.toString == """{"__type":{"name":"NameOrOriginInput","isOneOf":true}}""", + r2.data.toString == """{"__type":{"name":"CharacterInput","isOneOf":false}}""" + ) } ) } diff --git a/core/src/test/scala/caliban/schema/ArgBuilderSpec.scala b/core/src/test/scala/caliban/schema/ArgBuilderSpec.scala index 6c92e9a0e9..d50a759383 100644 --- a/core/src/test/scala/caliban/schema/ArgBuilderSpec.scala +++ b/core/src/test/scala/caliban/schema/ArgBuilderSpec.scala @@ -5,6 +5,7 @@ import caliban.InputValue import caliban.InputValue.ObjectValue import caliban.schema.ArgBuilder.auto._ import caliban.Value.{ IntValue, NullValue, StringValue } +import caliban.schema.Annotations.GQLOneOfInput import zio.test.Assertion._ import zio.test._ @@ -96,6 +97,56 @@ object ArgBuilderSpec extends ZIOSpecDefault { assertTrue(derivedAB.build(ObjectValue(Map("a" -> NullValue))) == Right(Wrapper(NullNullable))) && assertTrue(derivedAB.build(ObjectValue(Map("a" -> StringValue("x")))) == Right(Wrapper(SomeNullable("x")))) } - ) + ), + suite("oneOf") { + @GQLOneOfInput + sealed trait Foo + + object Foo { + case class Arg1(stringValue: String) extends Foo + case class Arg2(fooString2: String) extends Foo + case class Arg3(intValue: Foo1) extends Foo + case class Arg4(fooInt2: Foo2) extends Foo + + } + + case class Foo1(foo1: Int) + case class Foo2(foo2: Int) + + implicit val foo1Ab: ArgBuilder[Foo1] = ArgBuilder.gen + implicit val foo2Ab: ArgBuilder[Foo2] = ArgBuilder.gen + implicit val f1Ab: ArgBuilder[Foo.Arg1] = ArgBuilder.gen + implicit val f2Ab: ArgBuilder[Foo.Arg2] = ArgBuilder.gen + implicit val f3Ab: ArgBuilder[Foo.Arg3] = ArgBuilder.gen + implicit val f4Ab: ArgBuilder[Foo.Arg4] = ArgBuilder.gen + val fooAb: ArgBuilder[Foo] = ArgBuilder.gen + + List( + test("valid input") { + val inputs = List( + Map("stringValue" -> StringValue("foo")) -> Foo.Arg1("foo"), + Map("fooString2" -> StringValue("foo2")) -> Foo.Arg2("foo2"), + Map("intValue" -> ObjectValue(Map("foo1" -> IntValue(42)))) -> Foo.Arg3(Foo1(42)), + Map("fooInt2" -> ObjectValue(Map("foo2" -> IntValue(24)))) -> Foo.Arg4(Foo2(24)) + ) + + inputs.foldLeft(assertCompletes) { case (acc, (input, expected)) => + acc && assertTrue(fooAb.build(ObjectValue(input)) == Right(expected)) + } + }, + test("invalid input") { + List( + Map("invalid" -> StringValue("foo")), + Map("stringValue" -> StringValue("foo"), "fooString2" -> StringValue("foo2")), + Map("stringValue" -> ObjectValue(Map("value" -> StringValue("foo")))), + Map("stringValue" -> NullValue), + Map("stringValue" -> StringValue("foo"), "invalid" -> NullValue) + ) + .map(input => assertTrue(fooAb.build(ObjectValue(input)).isLeft)) + .foldLeft(assertCompletes)(_ && _) + + } + ) + } ) } diff --git a/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala b/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala index f365c1f7c0..cabbe2c5d3 100644 --- a/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala +++ b/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala @@ -5,7 +5,8 @@ import caliban._ import caliban.TestUtils._ import caliban.TestUtils.InvalidSchemas._ import caliban.introspection.adt._ -import caliban.schema.Types +import caliban.schema.Annotations.{ GQLDefault, GQLOneOfInput } +import caliban.schema.{ ArgBuilder, Schema, Types } import caliban.schema.Schema.auto._ import caliban.schema.ArgBuilder.auto._ import caliban.{ GraphQL, RootResolver } @@ -334,6 +335,67 @@ object ValidationSchemaSpec extends ZIOSpecDefault { ) } ) - } + }, + suite("OneOf input objects")( + test("must have at least one input field") { + @GQLOneOfInput + sealed trait Foo + case object ArgA extends Foo + case class Queries(foo: Foo => String) + + implicit val ab: ArgBuilder[Foo] = ArgBuilder.gen + implicit val schema: Schema[Any, Queries] = Schema.gen + + check( + graphQL(RootResolver(Queries(_.toString))), + "OneOf InputObject 'FooInput' does not have fields" + ) + }, + test("case classes extending OneOf sealed traits must have a single argument") { + @GQLOneOfInput + sealed trait Foo + case class ArgA(foo: String, bar: String) extends Foo + case class ArgB(baz: String) extends Foo + case class Queries(foo: Foo => String) + + implicit val ab: ArgBuilder[Foo] = ArgBuilder.gen + implicit val schema: Schema[Any, Queries] = Schema.gen + + check( + graphQL(RootResolver(Queries(_.toString))), + "OneOf InputObject 'FooInput' is extended by a case class with multiple arguments: caliban.validation.ValidationSchemaSpec.spec.ArgA" + ) + }, + test("cannot have default values") { + @GQLOneOfInput + sealed trait Foo + case class ArgA(@GQLDefault("foo") foo: String) extends Foo + case class ArgB(bar: String) extends Foo + case class Queries(foo: Foo => String) + + implicit val ab: ArgBuilder[Foo] = ArgBuilder.gen + implicit val schema: Schema[Any, Queries] = Schema.gen + + check( + graphQL(RootResolver(Queries(_.toString))), + "OneOf InputObject 'FooInput' argument has a default value" + ) + }, + test("must have unique field names") { + @GQLOneOfInput + sealed trait Foo + case class ArgA(foo: Int) extends Foo + case class ArgB(foo: String) extends Foo + case class Queries(foo: Foo => String) + + implicit val ab: ArgBuilder[Foo] = ArgBuilder.gen + implicit val schema: Schema[Any, Queries] = Schema.gen + + check( + graphQL(RootResolver(Queries(_.toString))), + "OneOf InputObject 'FooInput' has repeated fields: foo" + ) + } + ) ) } diff --git a/core/src/test/scala/caliban/validation/ValidationSpec.scala b/core/src/test/scala/caliban/validation/ValidationSpec.scala index 81e2f4eb0d..c23e54b34f 100644 --- a/core/src/test/scala/caliban/validation/ValidationSpec.scala +++ b/core/src/test/scala/caliban/validation/ValidationSpec.scala @@ -2,10 +2,12 @@ package caliban.validation import caliban._ import caliban.CalibanError.ValidationError +import caliban.InputValue.ObjectValue import caliban.Macros.gqldoc import caliban.TestUtils._ -import caliban.Value.{ BooleanValue, IntValue, StringValue } -import caliban.schema.Annotations.GQLDefault +import caliban.Value.{ BooleanValue, IntValue, NullValue, StringValue } +import caliban.schema.Annotations.{ GQLDefault, GQLOneOfInput } +import caliban.schema.{ ArgBuilder, Schema } import zio.{ IO, UIO, ZIO } import zio.test.Assertion._ import zio.test._ @@ -383,6 +385,155 @@ object ValidationSpec extends ZIOSpecDefault { api.interpreter.flatMap(_.execute(query)).map { response => assertTrue(response.errors.isEmpty) } + }, + suite("OneOf input objects") { + import caliban.schema.Schema.auto._ + import caliban.schema.ArgBuilder.auto._ + + @GQLOneOfInput + sealed trait Foo + object Foo { + case class FooString(fooString: String) extends Foo + case class FooInt(fooInt: IntObj) extends Foo + case class Wrapper(fooInput: Foo) + } + + case class IntObj(intValue: Int) + case class Bar(foo2: Foo => String) + case class Queries(foo: Foo.Wrapper => String, fooUnwrapped: Foo => String, bar: Bar) + + implicit val intObjAb: ArgBuilder[IntObj] = ArgBuilder.gen + implicit val fooStringAb: ArgBuilder[Foo.FooString] = ArgBuilder.gen + implicit val fooIntAb: ArgBuilder[Foo.FooInt] = ArgBuilder.gen + implicit val fooAb: ArgBuilder[Foo] = ArgBuilder.gen + implicit val barSchema: Schema[Any, Bar] = Schema.gen + implicit val schema: Schema[Any, Queries] = Schema.gen + + val api: GraphQL[Any] = graphQL(RootResolver(Queries(_.fooInput.toString, _.toString, Bar(_.toString)))) + + def argumentsQuery(arg1: ObjectValue, arg2: ObjectValue, arg3: ObjectValue) = + s""" + |{ + | foo(fooInput: ${arg1.toInputString}) + | + | fooUnwrapped(value: ${arg2.toInputString}) + | + | bar { + | foo2(value: ${arg3.toInputString} ) + | } + |} + |""".stripMargin + + val variablesQuery = + """ + |query Foo($args1: FooInput!, $args2: FooInput!, $args3: FooInput!){ + | foo(fooInput: $args1) + | + | fooUnwrapped(value: $args2) + | + | bar { + | foo2(value: $args3 ) + | } + |} + |""".stripMargin + + val validInputs = List( + Map("fooString" -> StringValue("hello")), + Map("fooInt" -> ObjectValue(Map("intValue" -> IntValue(42)))) + ).map(ObjectValue(_)) + + val invalidInputs = List( + Map.empty[String, InputValue], + Map("fooString" -> NullValue), + Map("fooString" -> StringValue("foo"), "fooInt" -> NullValue), + Map("fooString" -> StringValue("foo"), "fooInt" -> ObjectValue(Map("intValue" -> IntValue(42)))) + ).map(ObjectValue(_)) + + val validVariablesCases = validInputs.map(arg => Map("args1" -> arg, "args2" -> arg, "args3" -> arg)) + + List( + test("valid field arguments") { + val cases = validInputs.map(v => argumentsQuery(v, v, v)) + ZIO.foldLeft(cases)(assertCompletes) { case (acc, query) => + api.interpreter + .flatMap(_.execute(query)) + .map(resp => acc && assertTrue(resp.errors.isEmpty)) + } + }, + test("valid variables") { + ZIO.foldLeft(validVariablesCases)(assertCompletes) { case (acc, variables) => + api.interpreter + .flatMap(_.execute(variablesQuery, variables = variables)) + .map(resp => acc && assertTrue(resp.errors.isEmpty)) + } + }, + test("invalid field arguments") { + val cases = for { + invalid <- invalidInputs + valid = validInputs.head + _case <- List( + argumentsQuery(invalid, valid, valid), + argumentsQuery(valid, invalid, valid), + argumentsQuery(valid, valid, invalid) + ) + } yield _case + + ZIO.foldLeft(cases)(assertCompletes) { case (acc, query) => + api.interpreter + .flatMap(_.execute(query)) + .map(resp => + acc && assertTrue( + resp.errors.nonEmpty && resp.errors.forall { + case ValidationError(msg, _, _, _) => msg.contains("is not a valid OneOf Input Object") + case _ => false + } + ) + ) + } + }, + test("invalid variables") { + val cases = for { + argName <- validVariablesCases.head.keys + invalid <- invalidInputs + } yield validVariablesCases.head.updated(argName, invalid) + + ZIO.foldLeft(cases)(assertCompletes) { case (acc, variables) => + api.interpreter + .flatMap(_.execute(variablesQuery, variables = variables)) + .map(resp => + acc && assertTrue( + resp.errors.nonEmpty && resp.errors.forall { + case ValidationError(msg, _, _, _) => msg.contains("is not a valid OneOf Input Object") + case _ => false + } + ) + ) + } + }, + test("OneOf variables cannot be nullable") { + val variablesQuery = + """ + |query Foo($args1: FooInput){ + | foo(fooInput: $args1) + |} + |""".stripMargin + + api.interpreter + .flatMap(_.execute(variablesQuery, variables = Map("args1" -> validInputs.head))) + .map(resp => + assertTrue( + resp.errors.nonEmpty, + resp.errors.forall { + case ValidationError("Variable 'args1' cannot be nullable.", _, _, _) => true + case _ => false + } + ) + ) + }, + test("schema is valid") { + api.validateRootSchema.as(assertCompletes) + } + ) } ) }