From ceaad5bae72ca3fa535c86979bf0cea9d03fe117 Mon Sep 17 00:00:00 2001 From: jeejeeone Date: Fri, 22 Mar 2024 04:39:22 +0200 Subject: [PATCH] Add excludeDeprecated option to client codegen (#2163) * Add excludeDeprecated option to client codegen * Fix codeformat * Fix ConfigSpec * Maybe fix codegen-sbt --- .../caliban/codegen/CalibanSettings.scala | 1 + .../scala/caliban/codegen/OptionsParser.scala | 6 +- .../caliban/codegen/OptionsParserSpec.scala | 44 ++++++ .../caliban/tools/CalibanCommonSettings.scala | 13 +- .../scala/caliban/tools/ClientWriter.scala | 73 ++++++---- .../main/scala/caliban/tools/Codegen.scala | 4 +- .../main/scala/caliban/tools/Options.scala | 3 +- .../caliban/tools/compiletime/Config.scala | 9 +- .../caliban/tools/ClientWriterSpec.scala | 134 +++++++++++++++++- .../scala/caliban/tools/CodegenSpec.scala | 3 +- .../tools/compiletime/ConfigSpec.scala | 15 +- 11 files changed, 259 insertions(+), 46 deletions(-) diff --git a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala index 95653e181..18288ea43 100644 --- a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala +++ b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSettings.scala @@ -28,6 +28,7 @@ sealed trait CalibanSettings { final def preserveInputNames(value: Boolean): Self = withSettings(_.preserveInputNames(value)) final def addDerives(value: Boolean): Self = withSettings(_.addDerives(value)) final def envForDerives(value: String): Self = withSettings(_.envForDerives(value)) + final def excludeDeprecated(value: Boolean): Self = withSettings(_.excludeDeprecated(value)) } final case class CalibanFileSettings(file: File, settings: CalibanCommonSettings) extends CalibanSettings { diff --git a/codegen-sbt/src/main/scala/caliban/codegen/OptionsParser.scala b/codegen-sbt/src/main/scala/caliban/codegen/OptionsParser.scala index 61b1b7f4e..db80b5aa7 100644 --- a/codegen-sbt/src/main/scala/caliban/codegen/OptionsParser.scala +++ b/codegen-sbt/src/main/scala/caliban/codegen/OptionsParser.scala @@ -22,7 +22,8 @@ object OptionsParser { preserveInputNames: Option[Boolean], supportIsRepeatable: Option[Boolean], addDerives: Option[Boolean], - envForDerives: Option[String] + envForDerives: Option[String], + excludeDeprecated: Option[Boolean] ) private object DescriptorUtils { @@ -74,7 +75,8 @@ object OptionsParser { rawOpts.preserveInputNames, rawOpts.supportIsRepeatable, rawOpts.addDerives, - rawOpts.envForDerives + rawOpts.envForDerives, + rawOpts.excludeDeprecated ) }.option case _ => ZIO.none diff --git a/codegen-sbt/src/test/scala/caliban/codegen/OptionsParserSpec.scala b/codegen-sbt/src/test/scala/caliban/codegen/OptionsParserSpec.scala index 40a164a9a..eb09dce6c 100644 --- a/codegen-sbt/src/test/scala/caliban/codegen/OptionsParserSpec.scala +++ b/codegen-sbt/src/test/scala/caliban/codegen/OptionsParserSpec.scala @@ -30,6 +30,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -59,6 +60,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -89,6 +91,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -137,6 +140,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -167,6 +171,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -197,6 +202,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -227,6 +233,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -257,6 +264,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -287,6 +295,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -317,6 +326,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -347,6 +357,7 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) @@ -377,6 +388,7 @@ object OptionsParserSpec extends ZIOSpecDefault { Some(true), None, None, + None, None ) ) @@ -407,11 +419,43 @@ object OptionsParserSpec extends ZIOSpecDefault { None, None, None, + None, None ) ) ) } + }, + test("provide excludeDeprecated") { + val input = List("schema", "output", "--excludeDeprecated", "true") + OptionsParser.fromArgs(input).map { result => + assertTrue( + result == + Some( + Options( + "schema", + "output", + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(true) + ) + ) + ) + } } ) } diff --git a/tools/src/main/scala/caliban/tools/CalibanCommonSettings.scala b/tools/src/main/scala/caliban/tools/CalibanCommonSettings.scala index 40695d7f2..3c36490b1 100644 --- a/tools/src/main/scala/caliban/tools/CalibanCommonSettings.scala +++ b/tools/src/main/scala/caliban/tools/CalibanCommonSettings.scala @@ -19,7 +19,8 @@ final case class CalibanCommonSettings( preserveInputNames: Option[Boolean], supportIsRepeatable: Option[Boolean], addDerives: Option[Boolean], - envForDerives: Option[String] + envForDerives: Option[String], + excludeDeprecated: Option[Boolean] ) { private[caliban] def toOptions(schemaPath: String, toPath: String): Options = @@ -41,7 +42,8 @@ final case class CalibanCommonSettings( preserveInputNames = preserveInputNames, supportIsRepeatable = supportIsRepeatable, addDerives = addDerives, - envForDerives = envForDerives + envForDerives = envForDerives, + excludeDeprecated = excludeDeprecated ) private[caliban] def combine(r: => CalibanCommonSettings): CalibanCommonSettings = @@ -62,7 +64,8 @@ final case class CalibanCommonSettings( preserveInputNames = r.preserveInputNames.orElse(this.preserveInputNames), supportIsRepeatable = r.supportIsRepeatable.orElse(this.supportIsRepeatable), addDerives = r.addDerives.orElse(this.addDerives), - envForDerives = r.envForDerives.orElse(this.envForDerives) + envForDerives = r.envForDerives.orElse(this.envForDerives), + excludeDeprecated = r.excludeDeprecated.orElse(this.excludeDeprecated) ) def clientName(value: String): CalibanCommonSettings = this.copy(clientName = Some(value)) @@ -86,6 +89,7 @@ final case class CalibanCommonSettings( this.copy(supportIsRepeatable = Some(supportIsRepeatable)) def addDerives(addDerives: Boolean): CalibanCommonSettings = this.copy(addDerives = Some(addDerives)) def envForDerives(envForDerives: String): CalibanCommonSettings = this.copy(envForDerives = Some(envForDerives)) + def excludeDeprecated(value: Boolean): CalibanCommonSettings = this.copy(excludeDeprecated = Some(value)) } object CalibanCommonSettings { @@ -107,6 +111,7 @@ object CalibanCommonSettings { preserveInputNames = None, supportIsRepeatable = None, addDerives = None, - envForDerives = None + envForDerives = None, + excludeDeprecated = None ) } diff --git a/tools/src/main/scala/caliban/tools/ClientWriter.scala b/tools/src/main/scala/caliban/tools/ClientWriter.scala index 079a5c304..a1d0ac908 100644 --- a/tools/src/main/scala/caliban/tools/ClientWriter.scala +++ b/tools/src/main/scala/caliban/tools/ClientWriter.scala @@ -20,7 +20,8 @@ object ClientWriter { additionalImports: Option[List[String]] = None, splitFiles: Boolean = false, extensibleEnums: Boolean = false, - scalarMappings: Option[Map[String, String]] = None + scalarMappings: Option[Map[String, String]] = None, + excludeDeprecated: Boolean = false ): List[(String, String)] = { require(packageName.isDefined || !splitFiles, "splitFiles option requires a package name") @@ -430,11 +431,16 @@ object ClientWriter { s"type $objectName" } - def writeObject(typedef: ObjectTypeDefinition, genView: Boolean): String = { + def writeObject(typedef: ObjectTypeDefinition, genView: Boolean, excludeDeprecated: Boolean): String = { + val allFields = + if (excludeDeprecated) + typedef.fields.filterNot(field => field.directives.find(_.name == "deprecated").isDefined) + else + typedef.fields val objectName: String = safeTypeName(typedef.name) - val optionalUnionTypeFields = typedef.fields.flatMap { field => + val optionalUnionTypeFields = allFields.flatMap { field => if (isOptionalUnionType(field)) Some( collectFieldInfo( @@ -448,7 +454,7 @@ object ClientWriter { else None } - val optionalInterfaceTypeFields = typedef.fields.flatMap { field => + val optionalInterfaceTypeFields = allFields.flatMap { field => if (isOptionalInterfaceType(field)) Vector( collectFieldInfo( @@ -469,15 +475,14 @@ object ClientWriter { else Vector.empty } - val fields = typedef.fields.map( - collectFieldInfo(_, objectName, optionalUnion = false, optionalInterface = false, commonInterface = false) - ) - val view = if (genView) "\n " + writeView(typedef.name, fields.map(_.typeInfo)) else "" + val fields = allFields + .map(collectFieldInfo(_, objectName, optionalUnion = false, optionalInterface = false, commonInterface = false)) + val view = if (genView && fields.nonEmpty) "\n " + writeView(typedef.name, fields.map(_.typeInfo)) else "" - val allFields = fields ++ optionalUnionTypeFields ++ optionalInterfaceTypeFields + val objectFields = fields ++ optionalUnionTypeFields ++ optionalInterfaceTypeFields s"""object $objectName {$view - | ${allFields.distinct.map(writeFieldInfo).mkString("\n ")} + | ${objectFields.distinct.map(writeFieldInfo).mkString("\n ")} |} |""".stripMargin } @@ -690,22 +695,28 @@ object ClientWriter { .map(v => s"""case ${typedef.name}.${safeEnumValue(v.enumValue)} => __EnumValue("${v.enumValue}")""") ++ (if (extensibleEnums) Some(s"case ${typedef.name}.__Unknown (value) => __EnumValue(value)") else None) - s"""sealed trait $enumName extends scala.Product with scala.Serializable { def value: String } - object $enumName { - ${enumCases.mkString("\n")} - - implicit val decoder: ScalarDecoder[$enumName] = { - ${decoderCases.mkString("\n")} - case other => Left(DecodingError(s"Can't build ${typedef.name} from input $$other")) - } - implicit val encoder: ArgEncoder[${typedef.name}] = { - ${encoderCases.mkString("\n")} - } + val enumObject = + if (typedef.enumValuesDefinition.nonEmpty) + s"""object $enumName { + ${enumCases.mkString("\n")} + + implicit val decoder: ScalarDecoder[$enumName] = { + ${decoderCases.mkString("\n")} + case other => Left(DecodingError(s"Can't build ${typedef.name} from input $$other")) + } + implicit val encoder: ArgEncoder[${typedef.name}] = { + ${encoderCases.mkString("\n")} + } + + val values: scala.collection.immutable.Vector[$enumName] = scala.collection.immutable.Vector(${typedef.enumValuesDefinition + .map(v => safeEnumValue(v.enumValue)) + .mkString(", ")}) + }""" + else + "" - val values: scala.collection.immutable.Vector[$enumName] = scala.collection.immutable.Vector(${typedef.enumValuesDefinition - .map(v => safeEnumValue(v.enumValue)) - .mkString(", ")}) - } + s"""sealed trait $enumName extends scala.Product with scala.Serializable { def value: String } + $enumObject """ } @@ -785,7 +796,7 @@ object ClientWriter { directives = typedef.directives, fields = typedef.fields ) - val content = writeObject(objDef, genView) + val content = writeObject(objDef, genView, excludeDeprecated) val fullContent = if (splitFiles) s"""import caliban.client.FieldBuilder._ @@ -820,7 +831,7 @@ object ClientWriter { schemaDef.exists(_.subscription.getOrElse("Subscription") == obj.name) ) .map { typedef => - val content = writeObject(typedef, genView) + val content = writeObject(typedef, genView, excludeDeprecated) val fullContent = if (splitFiles) s"""import caliban.client.FieldBuilder._ @@ -851,6 +862,14 @@ object ClientWriter { val enums = schema.enumTypeDefinitions .filter(e => !scalarMappingsWithDefaults.contains(e.name)) + .map { + case typedef if excludeDeprecated => + val valuesWithoutDeprecated = + typedef.enumValuesDefinition.filterNot(value => value.directives.find(_.name == "deprecated").isDefined) + + typedef.copy(enumValuesDefinition = valuesWithoutDeprecated) + case typedef => typedef + } .map { typedef => val content = writeEnum(typedef, extensibleEnums = extensibleEnums) val fullContent = diff --git a/tools/src/main/scala/caliban/tools/Codegen.scala b/tools/src/main/scala/caliban/tools/Codegen.scala index 3137d9756..04c659fdb 100644 --- a/tools/src/main/scala/caliban/tools/Codegen.scala +++ b/tools/src/main/scala/caliban/tools/Codegen.scala @@ -34,6 +34,7 @@ object Codegen { splitFiles = arguments.splitFiles.getOrElse(false) enableFmt = arguments.enableFmt.getOrElse(true) extensibleEnums = arguments.extensibleEnums.getOrElse(false) + excludeDeprecated = arguments.excludeDeprecated.getOrElse(false) code = genType match { case GenType.Schema => List( @@ -58,7 +59,8 @@ object Codegen { arguments.imports, splitFiles, extensibleEnums, - scalarMappings + scalarMappings, + excludeDeprecated ) } formatted <- if (enableFmt) Formatter.format(code, arguments.fmtPath) else ZIO.succeed(code) diff --git a/tools/src/main/scala/caliban/tools/Options.scala b/tools/src/main/scala/caliban/tools/Options.scala index c6443fee7..6dfa9b2ed 100644 --- a/tools/src/main/scala/caliban/tools/Options.scala +++ b/tools/src/main/scala/caliban/tools/Options.scala @@ -18,7 +18,8 @@ final case class Options( preserveInputNames: Option[Boolean], supportIsRepeatable: Option[Boolean], addDerives: Option[Boolean], - envForDerives: Option[String] + envForDerives: Option[String], + excludeDeprecated: Option[Boolean] ) object Options { diff --git a/tools/src/main/scala/caliban/tools/compiletime/Config.scala b/tools/src/main/scala/caliban/tools/compiletime/Config.scala index c610f0bba..80ac34eea 100644 --- a/tools/src/main/scala/caliban/tools/compiletime/Config.scala +++ b/tools/src/main/scala/caliban/tools/compiletime/Config.scala @@ -14,7 +14,8 @@ trait Config { splitFiles: Boolean = false, enableFmt: Boolean = true, extensibleEnums: Boolean = false, - supportIsRepeatable: Boolean = true + supportIsRepeatable: Boolean = true, + excludeDeprecated: Boolean = false ) { private[caliban] def toCalibanCommonSettings: CalibanCommonSettings = CalibanCommonSettings( @@ -34,7 +35,8 @@ trait Config { preserveInputNames = None, supportIsRepeatable = Some(supportIsRepeatable), addDerives = None, - envForDerives = None + envForDerives = None, + excludeDeprecated = Some(excludeDeprecated) ) private[caliban] def asScalaCode: String = { @@ -50,7 +52,8 @@ trait Config { | splitFiles = $splitFiles, | enableFmt = $enableFmt, | extensibleEnums = $extensibleEnums, - | supportIsRepeatable = $supportIsRepeatable + | supportIsRepeatable = $supportIsRepeatable, + | excludeDeprecated = $excludeDeprecated |) """.stripMargin.trim } diff --git a/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala b/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala index 3062d3629..de6a4dccc 100644 --- a/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala +++ b/tools/src/test/scala/caliban/tools/ClientWriterSpec.scala @@ -10,7 +10,9 @@ object ClientWriterSpec extends ZIOSpecDefault { schema: String, scalarMappings: Map[String, String] = Map.empty, additionalImports: List[String] = List.empty, - extensibleEnums: Boolean = false + extensibleEnums: Boolean = false, + excludeDeprecated: Boolean = false, + genView: Boolean = false ): Task[String] = Parser .parseQuery(schema) .flatMap(doc => @@ -20,7 +22,9 @@ object ClientWriterSpec extends ZIOSpecDefault { doc, additionalImports = Some(additionalImports), extensibleEnums = extensibleEnums, - scalarMappings = Some(scalarMappings) + scalarMappings = Some(scalarMappings), + excludeDeprecated = excludeDeprecated, + genView = genView ) .head ._2, @@ -66,6 +70,65 @@ object Client { _root_.caliban.client.SelectionBuilder.Field("nicknames", ListOf(Scalar())) } +} +""" + ) + } + }, + test("simple object type with exclude deprecated and genView") { + val schema = + """ + type Character { + name: String! + nicknames: [String!]! @deprecated + } + """.stripMargin + + gen(schema = schema, excludeDeprecated = true, genView = true).map { str => + assertTrue( + str == + """import caliban.client.FieldBuilder._ +import caliban.client._ + +object Client { + + type Character + object Character { + + final case class CharacterView(name: String) + + type ViewSelection = SelectionBuilder[Character, CharacterView] + + def view: ViewSelection = name.map(name => CharacterView(name)) + + def name: SelectionBuilder[Character, String] = _root_.caliban.client.SelectionBuilder.Field("name", Scalar()) + } + +} +""" + ) + } + }, + test("simple object type with exclude deprecated and genView, only deprecated fields") { + val schema = + """ + type Character { + name: String! @deprecated + nicknames: [String!]! @deprecated(reason: "blah") + } + """.stripMargin + + gen(schema = schema, excludeDeprecated = true, genView = true).map { str => + assertTrue( + str == + """import caliban.client.FieldBuilder._ +import caliban.client._ + +object Client { + + type Character + object Character {} + } """ ) @@ -262,6 +325,73 @@ object Client { val values: scala.collection.immutable.Vector[Origin] = scala.collection.immutable.Vector(EARTH, MARS, BELT) } +} +""" + ) + } + }, + test("enum with exclude deprecated") { + val schema = + """ + enum Origin { + EARTH + MARS @deprecated + BELT + } + """.stripMargin + + gen(schema = schema, excludeDeprecated = true).map { str => + assertTrue( + str == + """import caliban.client.CalibanClientError.DecodingError +import caliban.client._ +import caliban.client.__Value._ + +object Client { + + sealed trait Origin extends scala.Product with scala.Serializable { def value: String } + object Origin { + case object EARTH extends Origin { val value: String = "EARTH" } + case object BELT extends Origin { val value: String = "BELT" } + + implicit val decoder: ScalarDecoder[Origin] = { + case __StringValue("EARTH") => Right(Origin.EARTH) + case __StringValue("BELT") => Right(Origin.BELT) + case other => Left(DecodingError(s"Can't build Origin from input $other")) + } + implicit val encoder: ArgEncoder[Origin] = { + case Origin.EARTH => __EnumValue("EARTH") + case Origin.BELT => __EnumValue("BELT") + } + + val values: scala.collection.immutable.Vector[Origin] = scala.collection.immutable.Vector(EARTH, BELT) + } + +} +""" + ) + } + }, + test("enum with exclude deprecated, only deprecated values") { + val schema = + """ + enum Origin { + MARS @deprecated + BELT @deprecated(reason: "blah") + } + """.stripMargin + + gen(schema = schema, excludeDeprecated = true).map { str => + assertTrue( + str == + """import caliban.client.CalibanClientError.DecodingError +import caliban.client._ +import caliban.client.__Value._ + +object Client { + + sealed trait Origin extends scala.Product with scala.Serializable { def value: String } + } """ ) diff --git a/tools/src/test/scala/caliban/tools/CodegenSpec.scala b/tools/src/test/scala/caliban/tools/CodegenSpec.scala index 713721682..aa066d9b9 100644 --- a/tools/src/test/scala/caliban/tools/CodegenSpec.scala +++ b/tools/src/test/scala/caliban/tools/CodegenSpec.scala @@ -67,7 +67,8 @@ object CodegenSpec extends ZIOSpecDefault { preserveInputNames = None, supportIsRepeatable = None, addDerives = None, - envForDerives = None + envForDerives = None, + excludeDeprecated = None ) getPackageAndObjectName(arguments) diff --git a/tools/src/test/scala/caliban/tools/compiletime/ConfigSpec.scala b/tools/src/test/scala/caliban/tools/compiletime/ConfigSpec.scala index 78a114112..06532b633 100644 --- a/tools/src/test/scala/caliban/tools/compiletime/ConfigSpec.scala +++ b/tools/src/test/scala/caliban/tools/compiletime/ConfigSpec.scala @@ -18,7 +18,8 @@ object ConfigSpec extends ZIOSpecDefault { splitFiles = true, enableFmt = false, extensibleEnums = true, - supportIsRepeatable = true + supportIsRepeatable = true, + excludeDeprecated = true ) private val toCalibanCommonSettingsSpec = @@ -43,7 +44,8 @@ object ConfigSpec extends ZIOSpecDefault { preserveInputNames = None, supportIsRepeatable = Some(true), addDerives = None, - envForDerives = None + envForDerives = None, + excludeDeprecated = Some(true) ) ) ) @@ -65,7 +67,8 @@ object ConfigSpec extends ZIOSpecDefault { | splitFiles = false, | enableFmt = true, | extensibleEnums = false, - | supportIsRepeatable = true + | supportIsRepeatable = true, + | excludeDeprecated = false |) """.stripMargin.trim ) @@ -84,7 +87,8 @@ object ConfigSpec extends ZIOSpecDefault { | splitFiles = true, | enableFmt = false, | extensibleEnums = true, - | supportIsRepeatable = true + | supportIsRepeatable = true, + | excludeDeprecated = true |) """.stripMargin.trim ) @@ -105,7 +109,8 @@ object ConfigSpec extends ZIOSpecDefault { imports = List.empty, splitFiles = false, enableFmt = true, - extensibleEnums = false + extensibleEnums = false, + excludeDeprecated = false ) ) )