From be11dc422d833c97f65fe9e43d3b26a445ed4300 Mon Sep 17 00:00:00 2001 From: Tobias Pfeifer Date: Fri, 28 Feb 2020 16:26:20 +0100 Subject: [PATCH] refactor & reuse existing validations after merge of #244 --- .../scala/caliban/validation/Validator.scala | 178 +++++++++--------- .../validation/ValidationSchemaSpec.scala | 2 +- 2 files changed, 93 insertions(+), 87 deletions(-) diff --git a/core/src/main/scala/caliban/validation/Validator.scala b/core/src/main/scala/caliban/validation/Validator.scala index 238e06cd6..4b07c7f84 100644 --- a/core/src/main/scala/caliban/validation/Validator.scala +++ b/core/src/main/scala/caliban/validation/Validator.scala @@ -521,56 +521,6 @@ object Validator { ) } - private def validateInterface(t: __Type): IO[ValidationError, Unit] = { - def validateName(name: String) = { - if (name.isEmpty) { - failValidation("msg", "explanatory") - } else if (name.startsWith("__")) { - failValidation("msg", "explanatory") - } else { - IO.unit - } - } - def validateFieldName(field: __Field): IO[ValidationError, Unit] = { - validateName(field.name) - } - def validateFieldArgumentsName(args: List[__InputValue]): IO[ValidationError, Unit] = { - IO.foreach(args){ arg => validateName(arg.name) }.unit - } - - def validateFieldReturnOutputType(`type`: () => __Type): IO[ValidationError, Unit] = IO.unit - def validateFieldArgumentsInputType(args: List[__InputValue]): IO[ValidationError, Unit] = IO.unit - - def validateInterfaceField(field: __Field): IO[ValidationError, Unit] = - for { - _ <- validateFieldName(field) - _ <- validateFieldReturnOutputType(field.`type`) - _ <- validateFieldArgumentsName(field.args) - _ <- validateFieldArgumentsInputType(field.args) - } yield () - - def duplicateFieldName(fields: List[__Field]): Option[__Field] = - fields.groupBy(_.name).collectFirst { case (_, f :: _ :: _) => f } - - t.fields(__DeprecatedArgs(Some(true))) match { - case None | Some(Nil) => - failValidation( - s"message", - "explanatory" - ) - case Some(fields) => - duplicateFieldName(fields) match { - case Some(value) => - failValidation( - s"message with $value", - "explanatory" - ) - case None => - IO.foreach(fields){ validateInterfaceField }.unit - } - } - } - private def validateUnion(t: __Type): IO[ValidationError, Unit] = { def isObject(t: __Type): Boolean = t.kind match { @@ -596,61 +546,117 @@ object Validator { } private def validateInputObject(t: __Type): IO[ValidationError, Unit] = { - // https://spec.graphql.org/June2018/#IsInputType() - def isInputType(t: __Type): Either[__Type, Unit] = t.kind match { - case __TypeKind.LIST | __TypeKind.NON_NULL => t.ofType.fold[Either[__Type, Unit]](Left(t))(isInputType) - case __TypeKind.SCALAR | __TypeKind.ENUM | __TypeKind.INPUT_OBJECT => Right(()) - case _ => Left(t) + def validateFields(fields: List[__InputValue]): IO[ValidationError, Unit] = + noDuplicateInputValueName(fields) <* + IO.foreach(fields)(field => + for { + _ <- doesNotStartWithUnderscore(field.name, "input value", "InputObject") + _ <- onlyInputType(field.`type`()) + } yield () + ) + + t.inputFields match { + case None | Some(Nil) => + failValidation( + s"InputObject ${t.name.getOrElse("")} does not have fields", + "An Input Object type must define one or more input fields" + ) + case Some(fields) => validateFields(fields) } + } - def validateFields(fields: List[__InputValue]): IO[ValidationError, Unit] = - duplicateFieldName(fields) <* + private def validateInterface(t: __Type): IO[ValidationError, Unit] = { + def validateInterfaceArgument(arg: __InputValue): IO[ValidationError, Unit] = + for { + _ <- doesNotStartWithUnderscore(arg.name, "argument input value", "Interface") + _ <- onlyOutputType(arg.`type`()) + } yield () + + def validateFields(fields: List[__Field]): IO[ValidationError, Unit] = + noDuplicateFieldName(fields) <* IO.foreach(fields)(field => for { - _ <- doesNotStartWithUnderscore(field) - _ <- onlyInputFieldType(field) + _ <- doesNotStartWithUnderscore(field.name, "field", "Interface") + _ <- onlyOutputType(field.`type`()) + _ <- IO.foreach(field.args)(validateInterfaceArgument) } yield () ) - def duplicateFieldName(fields: List[__InputValue]): IO[ValidationError, Unit] = - fields - .groupBy(_.name) - .collectFirst { case (_, f :: _ :: _) => f } - .fold[IO[ValidationError, Unit]](IO.unit)(duplicateField => - failValidation( - s"InputObject has repeated fields: ${duplicateField.name}", - "The input field must have a unique name within that Input Object type; no two input fields may share the same name" - ) + t.fields(__DeprecatedArgs(Some(true))) match { + case None | Some(Nil) => + failValidation( + s"message", + "explanatory" ) + case Some(fields) => validateFields(fields) + } + } - def doesNotStartWithUnderscore(field: __InputValue): IO[ValidationError, Unit] = - IO.when(field.name.startsWith("__"))( + private def onlyInputType(`type`: __Type): IO[ValidationError, Unit] = { + // https://spec.graphql.org/June2018/#IsInputType() + def isInputType(t: __Type): Either[__Type, Unit] = { + import __TypeKind._ + t.kind match { + case LIST | NON_NULL => t.ofType.fold[Either[__Type, Unit]](Left(t))(isInputType) + case SCALAR | ENUM | INPUT_OBJECT => Right(()) + case _ => Left(t) + } + } + + IO.whenCase(isInputType(`type`)) { + case Left(errorType) => failValidation( - s"InputObject can't start with '__': ${field.name}", - """The input field must not have a name which begins with the -characters {"__"} (two underscores)""" + s"${errorType.name.getOrElse("")} is of kind ${errorType.kind}, must be an InputType", + """The input field must accept a type where IsInputType(type) returns true, https://spec.graphql.org/June2018/#IsInputType()""" ) - ) + } + } - def onlyInputFieldType(field: __InputValue): IO[ValidationError, Unit] = - IO.whenCase(isInputType(field.`type`())) { - case Left(errorType) => - failValidation( - s"${errorType.name.getOrElse("")} is of kind ${errorType.kind}, must be an InputType", - """The input field must accept a type where IsInputType(inputFieldType) returns true, https://spec.graphql.org/June2018/#IsInputType()""" - ) + private def onlyOutputType(`type`: __Type): IO[ValidationError, Unit] = { + // https://spec.graphql.org/June2018/#IsOutputType() + def isOutputType(t: __Type): Either[__Type, Unit] = { + import __TypeKind._ + t.kind match { + case LIST | NON_NULL => t.ofType.fold[Either[__Type, Unit]](Left(t))(isOutputType) + case SCALAR | OBJECT | INTERFACE | UNION | ENUM => Right(()) + case _ => Left(t) } + } - t.inputFields match { - case None | Some(Nil) => + IO.whenCase(isOutputType(`type`)) { + case Left(errorType) => failValidation( - s"InputObject ${t.name.getOrElse("")} does not have fields", - "An Input Object type must define one or more input fields" + s"${errorType.name.getOrElse("")} is of kind ${errorType.kind}, must be an OutputType", + """The input field must accept a type where IsOutputType(type) returns true, https://spec.graphql.org/June2018/#IsInputType()""" ) - case Some(fields) => validateFields(fields) } } + private def noDuplicateFieldName(fields: List[__Field]) = + noDuplicateName[__Field](fields, _.name) + + private def noDuplicateInputValueName(inputValues: List[__InputValue]) = + noDuplicateName[__InputValue](inputValues, _.name) + + private def noDuplicateName[T](listOfNamed: List[T], nameExtractor: T => String): IO[ValidationError, Unit] = + listOfNamed + .groupBy(nameExtractor(_)) + .collectFirst { case (_, f :: _ :: _) => f } + .fold[IO[ValidationError, Unit]](IO.unit)(duplicate => + failValidation( + s"InputObject has repeated fields: ${nameExtractor(duplicate)}", + "The input field must have a unique name within that Input Object type; no two input fields may share the same name" + ) + ) + + private def doesNotStartWithUnderscore(name: String, typeName: String, inType: String): IO[ValidationError, Unit] = + IO.when(name.startsWith("__"))( + failValidation( + s"A $typeName in $inType can't start with '__': $name", + s"""The $typeName must not have a name which begins with the characters {"__"} (two underscores)""" + ) + ) + case class Context( document: Document, rootType: RootType, diff --git a/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala b/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala index 80a5c2249..aa71edda5 100644 --- a/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala +++ b/core/src/test/scala/caliban/validation/ValidationSchemaSpec.scala @@ -18,7 +18,7 @@ object ValidationSchemaSpec testM("name can't start with '__'") { check( graphQL(resolverWrongMutationUnderscore), - "InputObject can't start with '__': __name" + "A input value in InputObject can't start with '__': __name" ) }, testM("should only contain types for which IsInputType(type) is true") {