Skip to content

Commit

Permalink
fix: Handle additional default value edgecases
Browse files Browse the repository at this point in the history
  • Loading branch information
frekw committed Sep 15, 2021
1 parent 15d6ff5 commit b56117a
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 15 deletions.
39 changes: 25 additions & 14 deletions core/src/main/scala/caliban/validation/DefaultValueValidator.scala
Expand Up @@ -67,20 +67,11 @@ object DefaultValueValidator {
}
case ENUM =>
argValue match {
case EnumValue(value) =>
val possible = inputType
.enumValues(__DeprecatedArgs(Some(true)))
.getOrElse(List.empty)
.map(_.name)
val exists = possible.exists(_ == value)

IO.unless(exists)(
failValidation(
s"$errorContext has invalid enum value: $value",
s"Was supposed to be one of ${possible.mkString(", ")}"
)
)
case _ =>
case EnumValue(value) =>
validateEnum(value, inputType, errorContext)
case StringValue(value) =>
validateEnum(value, inputType, errorContext)
case _ =>
failValidation(
s"$errorContext has invalid type: $argValue",
"Input field was supposed to be an enum value."
Expand All @@ -94,33 +85,53 @@ object DefaultValueValidator {
)
}

def validateEnum(value: String, inputType: __Type, errorContext: String) = {
val possible = inputType
.enumValues(__DeprecatedArgs(Some(true)))
.getOrElse(List.empty)
.map(_.name)
val exists = possible.exists(_ == value)

IO.unless(exists)(
failValidation(
s"$errorContext has invalid enum value: $value",
s"Was supposed to be one of ${possible.mkString(", ")}"
)
)
}

def validateScalar(inputType: __Type, argValue: InputValue, errorContext: String) =
inputType.name.getOrElse("") match {
case "String" =>
argValue match {
case StringValue(value) =>
IO.unit
case NullValue => IO.unit
case t => failValidation(s"$errorContext has invalid type $t", "Expected 'String'")
}
case "ID" =>
argValue match {
case StringValue(value) =>
IO.unit
case NullValue => IO.unit
case t => failValidation(s"$errorContext has invalid type $t", "Expected 'ID'")
}
case "Int" =>
argValue match {
case _: Value.IntValue => IO.unit
case NullValue => IO.unit
case t => failValidation(s"$errorContext has invalid type $t", "Expected 'Int'")
}
case "Float" =>
argValue match {
case _: Value.FloatValue => IO.unit
case NullValue => IO.unit
case t => failValidation(s"$errorContext has invalid type $t", "Expected 'Float'")
}
case "Boolean" =>
argValue match {
case BooleanValue(value) => IO.unit
case NullValue => IO.unit
case t => failValidation(s"$errorContext has invalid type $t", "Expected 'Boolean'")
}
// We can't really validate custom scalars here (since we can't summon a correct ArgBuilder instance), so just pass them along
Expand Down
31 changes: 31 additions & 0 deletions core/src/test/scala/caliban/execution/DefaultValueSpec.scala
Expand Up @@ -12,6 +12,11 @@ import zio.test.environment.TestEnvironment
import java.util.UUID

object DefaultValueSpec extends DefaultRunnableSpec {
sealed trait COLOR
object COLOR {
case object GREEN extends COLOR
case object BLUE extends COLOR
}
override def spec: ZSpec[TestEnvironment, Any] =
suite("DefaultValueSpec")(
suite("default value validation")(
Expand Down Expand Up @@ -55,6 +60,26 @@ object DefaultValueSpec extends DefaultRunnableSpec {
fails(isSubtype[CalibanError.ValidationError](anything))
)
},
testM("valid enum validation") {
case class TestInput(@GQLDefault("GREEN") c: COLOR)
case class Query(test: TestInput => COLOR)
val gql = graphQL(RootResolver(Query(i => i.c)))
assertM(gql.interpreter.run)(anything)
},
testM("valid enum validation accepts strings") {
case class TestInput(@GQLDefault("\"GREEN\"") c: COLOR)
case class Query(test: TestInput => COLOR)
val gql = graphQL(RootResolver(Query(i => i.c)))
assertM(gql.interpreter.run)(anything)
},
testM("valid enum validation") {
case class TestInput(@GQLDefault("PINK") c: COLOR)
case class Query(test: TestInput => COLOR)
val gql = graphQL(RootResolver(Query(i => i.c)))
assertM(gql.interpreter.run)(
fails(isSubtype[CalibanError.ValidationError](anything))
)
},
testM("invalid nullable validation") {
case class TestInput(@GQLDefault("1") s: Option[String])
case class Query(test: TestInput => String)
Expand All @@ -69,6 +94,12 @@ object DefaultValueSpec extends DefaultRunnableSpec {
val gql = graphQL(RootResolver(Query(i => i.s.getOrElse("default"))))
assertM(gql.interpreter)(anything)
},
testM("valid nullable validation for null") {
case class TestInput(@GQLDefault("null") s: Option[String])
case class Query(test: TestInput => String)
val gql = graphQL(RootResolver(Query(i => i.s.getOrElse("default"))))
assertM(gql.interpreter)(anything)
},
testM("invalid list validation") {
case class TestInput(@GQLDefault("\"string\"") string: List[String])
case class Query(test: TestInput => List[String])
Expand Down
3 changes: 2 additions & 1 deletion tools/src/main/scala/caliban/tools/IntrospectionClient.scala
Expand Up @@ -12,6 +12,7 @@ import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition._
import caliban.parsing.adt.Definition.TypeSystemDefinition.{ DirectiveDefinition, SchemaDefinition, TypeDefinition }
import caliban.parsing.adt.Type.{ ListType, NamedType }
import caliban.parsing.adt.{ Directive, Document, Type }
import io.circe.parser.decode
import sttp.client3._
import sttp.client3.asynchttpclient.zio._
import sttp.model.Uri
Expand Down Expand Up @@ -54,7 +55,7 @@ object IntrospectionClient {
`type`: Type,
defaultValue: Option[String]
): InputValueDefinition = {
val default = defaultValue.flatMap(v => Parser.parseInputValue(v).toOption)
val default = defaultValue.flatMap(v => decode[InputValue](v).toOption)
InputValueDefinition(description, name, `type`, default, Nil)
}

Expand Down

0 comments on commit b56117a

Please sign in to comment.