From 7d830d774e3e1e740025de823fbc771aa3d20f1b Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 23:58:09 -0800 Subject: [PATCH 1/8] WriteTree should not hide differences if the files are the same length --- modules/core/src/main/scala/dev/guardrail/WriteTree.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/WriteTree.scala b/modules/core/src/main/scala/dev/guardrail/WriteTree.scala index 09243952ba..d5ec80fc2f 100644 --- a/modules/core/src/main/scala/dev/guardrail/WriteTree.scala +++ b/modules/core/src/main/scala/dev/guardrail/WriteTree.scala @@ -30,8 +30,10 @@ object WriteTree { .zipWithIndex .find { case ((a, b), _) => a != b } .map(_._2) - .orElse(Some(Math.max(exists.length, data.length))) - .filterNot(Function.const(data.length == exists.length)) + .orElse( + Some(Math.max(exists.length, data.length)) + .filterNot(Function.const(data.length == exists.length)) + ) diffIdx.fold[Writer[List[String], WriteTreeState]](Writer.value(FileIdentical)) { diffIdx => val existSample = new String(exists, UTF_8) From 7bf21bb6d4d0f7daadebc40c209d74f6274b6ddc Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 11:45:00 -0800 Subject: [PATCH 2/8] Blindly converting String to NEL[String] --- .../scala/circe/CirceProtocolGenerator.scala | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 0abb51b9eb..4119e5458c 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -103,7 +103,7 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa components = components ) oneOf <- fromOneOf( - clsName = formattedClsName, + clsName = NonEmptyList.of(formattedClsName), model = comp, definitions.value, dtoPackage = dtoPackage, @@ -137,7 +137,7 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa components = components ) oneOf <- fromOneOf( - clsName = formattedClsName, + clsName = NonEmptyList.of(formattedClsName), model = m, definitions.value, dtoPackage = dtoPackage, @@ -186,7 +186,7 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa ) (declType, _) <- ModelResolver.determineTypeName[ScalaLanguage, Target](x, Tracker.cloneHistory(x, CustomTypeName(x, prefixes)), components) oneOf <- fromOneOf( - clsName = formattedClsName, + clsName = NonEmptyList.of(formattedClsName), model = x, definitions.value, dtoPackage = dtoPackage, @@ -592,7 +592,7 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa } private[this] def fromOneOf( - clsName: String, + clsName: NonEmptyList[String], model: Tracker[Schema[_]], definitions: List[(String, Tracker[Schema[_]])], dtoPackage: List[String], @@ -641,10 +641,10 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa .map(_.unzip) (nestedPMs, (nestedEncoders, nestedDecoders, nestedDefns)) = nestedClasses.toList.flatten.unzip.map(_.unzip3) rawTypes <- Target.fromOption( - NonEmptyList.fromList(rawTypes.toList.flatten.map((None, _)) ++ nestedPMs.map((Some(clsName), _))), + NonEmptyList.fromList(rawTypes.toList.flatten.map((None, _)) ++ nestedPMs.map((Some(clsName.last), _))), UserError("Expected at least some models") ) - fullType <- Sc.selectType(NonEmptyList.ofInitLast(dtoPackage, clsName)) + fullType <- Sc.selectType(clsName.prependList(dtoPackage)) discriminator_ = model.downField("discriminator", _.getDiscriminator()).indexedDistribute @@ -690,8 +690,8 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa for { fullInstanceType <- Sc.selectType(NonEmptyList.ofInitLast(dtoPackage ++ outer, name)) termName = Term.Name(name) - applyAdapter = q"implicit val ${Pat.Var(Term.Name(s"from${name}"))}: ${fullInstanceType} => ${Type.Name(clsName)} = members.${termName}.apply _" - member = q"case class ${tpe}(value: ${fullInstanceType}) extends ${Init.After_4_6_0(Type.Name(clsName), Name.Anonymous(), Nil)}" + applyAdapter = q"implicit val ${Pat.Var(Term.Name(s"from${name}"))}: ${fullInstanceType} => ${Type.Name(clsName.last)} = members.${termName}.apply _" + member = q"case class ${tpe}(value: ${fullInstanceType}) extends ${Init.After_4_6_0(Type.Name(clsName.last), Name.Anonymous(), Nil)}" decoder = validateDecoder(memberName, q"_root_.io.circe.Decoder[${tpe}].map(${Term.Name(s"from${name}")})") encoder = p"case members.${termName}(member) => ${injectDiscriminator(memberName, q"member")}" } yield (member, (applyAdapter, (decoder, encoder))) @@ -700,11 +700,11 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa } .map(_.unzip.map(_.unzip.map(_.unzip))) // NonEmptyList#unzip4 adapter :see_no_evil: - encoder = q"""implicit def ${Term.Name(s"encode${clsName}")}: _root_.io.circe.Encoder[${Type - .Name(clsName)}] = _root_.io.circe.Encoder.instance(${Term + encoder = q"""implicit def ${Term.Name(s"encode${clsName.last}")}: _root_.io.circe.Encoder[${Type + .Name(clsName.last)}] = _root_.io.circe.Encoder.instance(${Term .PartialFunction(encoders.toList)}) """ decoder = q""" - implicit def ${Term.Name(s"decode${clsName}")}: _root_.io.circe.Decoder[${Type.Name(clsName)}] = { + implicit def ${Term.Name(s"decode${clsName.last}")}: _root_.io.circe.Decoder[${Type.Name(clsName.last)}] = { ..${decoders.zipWithIndex.toList.map { case (decoder, idx) => q"val ${Pat.Var(Term.Name(s"dec${idx}"))} = ${decoder}" }}; @@ -713,16 +713,16 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa """ statements = List( q"object members { ..${members.toList} }", - q"def apply[A](value: A)(implicit ev: A => ${Type.Name(clsName)}): ${Type.Name(clsName)} = ev(value)" + q"def apply[A](value: A)(implicit ev: A => ${Type.Name(clsName.last)}): ${Type.Name(clsName.last)} = ev(value)" ) ++ applyAdapters.toList ++ nestedDefns ++ nestedEncoders.flatten ++ nestedDecoders.flatten - defn = q"""sealed abstract class ${Type.Name(clsName)} {}""" + defn = q"""sealed abstract class ${Type.Name(clsName.last)} {}""" staticDefns = StaticDefns[ScalaLanguage]( - className = clsName, + className = clsName.last, extraImports = List.empty, definitions = List(encoder, decoder), statements = statements ) - } yield Right(ClassDefinition[ScalaLanguage](clsName, Type.Name(clsName), fullType, defn, staticDefns, Nil)) + } yield Right(ClassDefinition[ScalaLanguage](clsName.last, Type.Name(clsName.last), fullType, defn, staticDefns, Nil)) } // NB: In OpenAPI 3.1 ObjectSchema was broadly replaced with JsonSchema. From 726f923fed8a7e93d1258484ba518c59def68723 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 11:47:21 -0800 Subject: [PATCH 3/8] We are only emitting Nested --- .../generators/scala/circe/CirceProtocolGenerator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 4119e5458c..599213c801 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -606,10 +606,10 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa Fw: FrameworkTerms[ScalaLanguage, Target], Sw: OpenAPITerms[ScalaLanguage, Target], P: ProtocolTerms[ScalaLanguage, Target] - ): Target[Either[String, StrictProtocolElems[ScalaLanguage]]] = + ): Target[Either[String, NestedProtocolElems[ScalaLanguage]]] = NonEmptyList .fromList(model.downField("oneOf", _.getOneOf()).indexedDistribute) - .fold[Target[Either[String, StrictProtocolElems[ScalaLanguage]]]](Target.pure(Left("Does not have oneOf"))) { xs => + .fold[Target[Either[String, NestedProtocolElems[ScalaLanguage]]]](Target.pure(Left("Does not have oneOf"))) { xs => for { nameGenerator <- Target.pure(new java.util.concurrent.atomic.AtomicInteger(1)) getNewName = () => Target.pure(nameGenerator).map(ng => s"Nested${ng.getAndIncrement()}") From 6f64985d737dc0b0ad17eb7403d4896809392bd9 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 11:48:07 -0800 Subject: [PATCH 4/8] Constrain fromOneOf to just ClassDefinition --- .../generators/scala/circe/CirceProtocolGenerator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 599213c801..3d9e5938f2 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -606,10 +606,10 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa Fw: FrameworkTerms[ScalaLanguage, Target], Sw: OpenAPITerms[ScalaLanguage, Target], P: ProtocolTerms[ScalaLanguage, Target] - ): Target[Either[String, NestedProtocolElems[ScalaLanguage]]] = + ): Target[Either[String, ClassDefinition[ScalaLanguage]]] = NonEmptyList .fromList(model.downField("oneOf", _.getOneOf()).indexedDistribute) - .fold[Target[Either[String, NestedProtocolElems[ScalaLanguage]]]](Target.pure(Left("Does not have oneOf"))) { xs => + .fold[Target[Either[String, ClassDefinition[ScalaLanguage]]]](Target.pure(Left("Does not have oneOf"))) { xs => for { nameGenerator <- Target.pure(new java.util.concurrent.atomic.AtomicInteger(1)) getNewName = () => Target.pure(nameGenerator).map(ng => s"Nested${ng.getAndIncrement()}") From a0f55d72a6242a1f68bbebb87fa80036e13b58c1 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 11:48:41 -0800 Subject: [PATCH 5/8] Utilize fromOneOf in prepareProperties --- .../scala/circe/CirceProtocolGenerator.scala | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 3d9e5938f2..981b1fe338 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -788,17 +788,29 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa .orRefine { case o: ComposedSchema => o }(o => for { parents <- extractParents(o, definitions, concreteTypes, dtoPackage, supportPackage, defaultPropertyRequirement, components) - maybeClassDefinition <- fromModel( - nestedClassName, - o, - parents, - concreteTypes, - definitions, - dtoPackage, - supportPackage, - defaultPropertyRequirement, - components + model <- fromModel( + clsName = nestedClassName, + model = o, + parents = parents, + concreteTypes = concreteTypes, + definitions = definitions, + dtoPackage = dtoPackage, + supportPackage = supportPackage, + defaultPropertyRequirement = defaultPropertyRequirement, + components = components + ) + oneOf <- fromOneOf( + clsName = nestedClassName, + model = o, + // parents, + definitions = definitions, + dtoPackage = dtoPackage, + supportPackage = supportPackage, + concreteTypes = concreteTypes, + defaultPropertyRequirement = defaultPropertyRequirement, + components = components ) + maybeClassDefinition = model.orElse(oneOf) } yield Option(maybeClassDefinition) ) .orRefine { case a: ArraySchema => a }(_.downField("items", _.getItems()).indexedCosequence.flatTraverse(processProperty(name, _))) From 5812da447e552a88d2d5302c4410d2aefea2453e Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 23:26:52 -0800 Subject: [PATCH 6/8] Extending wrapToObject with statements --- .../src/main/scala/dev/guardrail/terms/LanguageTerms.scala | 7 ++++--- .../dev/guardrail/generators/java/JavaGenerator.scala | 3 ++- .../generators/java/jackson/JacksonGenerator.scala | 4 ++-- .../dev/guardrail/generators/scala/ScalaGenerator.scala | 4 +++- .../generators/scala/circe/CirceProtocolGenerator.scala | 4 ++-- .../scala/circe/CirceRefinedProtocolGenerator.scala | 4 ++-- .../scala/jackson/JacksonProtocolGenerator.scala | 4 ++-- 7 files changed, 17 insertions(+), 13 deletions(-) diff --git a/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala b/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala index 2adc6f0155..99a706dfd3 100644 --- a/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala +++ b/modules/core/src/main/scala/dev/guardrail/terms/LanguageTerms.scala @@ -125,7 +125,7 @@ abstract class LanguageTerms[L <: LA, F[_]] { self => server: Server[L] ): F[List[WriteTree]] - def wrapToObject(name: L#TermName, imports: List[L#Import], definitions: List[L#Definition]): F[Option[L#ObjectDefinition]] + def wrapToObject(name: L#TermName, imports: List[L#Import], definitions: List[L#Definition], statements: List[L#Statement]): F[Option[L#ObjectDefinition]] def copy( litString: String => F[L#Term] = self.litString _, @@ -209,7 +209,7 @@ abstract class LanguageTerms[L <: LA, F[_]] { self => self.writeClient _, writeServer: (Path, NonEmptyList[String], List[L#Import], List[L#TermName], Option[NonEmptyList[String]], Server[L]) => F[List[WriteTree]] = self.writeServer _, - wrapToObject: (L#TermName, List[L#Import], List[L#Definition]) => F[Option[L#ObjectDefinition]] = self.wrapToObject _ + wrapToObject: (L#TermName, List[L#Import], List[L#Definition], List[L#Statement]) => F[Option[L#ObjectDefinition]] = self.wrapToObject _ ) = { val newLitString = litString val newLitFloat = litFloat @@ -383,7 +383,8 @@ abstract class LanguageTerms[L <: LA, F[_]] { self => dtoComponents: Option[NonEmptyList[String]], server: Server[L] ) = newWriteServer(pkgPath, pkgName, customImports, frameworkImplicitNames, dtoComponents, server) - def wrapToObject(name: L#TermName, imports: List[L#Import], definitions: List[L#Definition]) = newWrapToObject(name, imports, definitions) + def wrapToObject(name: L#TermName, imports: List[L#Import], definitions: List[L#Definition], statements: List[L#Statement]) = + newWrapToObject(name, imports, definitions, statements) } } } diff --git a/modules/java-support/src/main/scala/dev/guardrail/generators/java/JavaGenerator.scala b/modules/java-support/src/main/scala/dev/guardrail/generators/java/JavaGenerator.scala index 57f1482bfb..3cf652031a 100644 --- a/modules/java-support/src/main/scala/dev/guardrail/generators/java/JavaGenerator.scala +++ b/modules/java-support/src/main/scala/dev/guardrail/generators/java/JavaGenerator.scala @@ -427,7 +427,8 @@ class JavaGenerator private extends LanguageTerms[JavaLanguage, Target] { override def wrapToObject( name: com.github.javaparser.ast.expr.Name, imports: List[com.github.javaparser.ast.ImportDeclaration], - definitions: List[com.github.javaparser.ast.body.BodyDeclaration[_ <: com.github.javaparser.ast.body.BodyDeclaration[_]]] + definitions: List[com.github.javaparser.ast.body.BodyDeclaration[_ <: com.github.javaparser.ast.body.BodyDeclaration[_]]], + statements: List[com.github.javaparser.ast.Node] ): Target[Option[Nothing]] = Target.pure(Option.empty[Nothing]) } diff --git a/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala b/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala index ae0554d08b..1dcdd22d61 100644 --- a/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala +++ b/modules/java-support/src/main/scala/dev/guardrail/generators/java/jackson/JacksonGenerator.scala @@ -764,14 +764,14 @@ class JacksonGenerator private (implicit Cl: CollectionsLibTerms[JavaLanguage, T for { widenClass <- widenClassDefinition(classDefinition.cls) companionTerm <- pureTermName(classDefinition.name) - companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(classDefinition.staticDefns.definitions)(List(_)) case enumDefinition: EnumDefinition[JavaLanguage] => for { widenClass <- widenClassDefinition(enumDefinition.cls) companionTerm <- pureTermName(enumDefinition.name) - companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(enumDefinition.staticDefns.definitions)(List(_)) } diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/ScalaGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/ScalaGenerator.scala index a20b5b887c..4e397ac2c2 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/ScalaGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/ScalaGenerator.scala @@ -510,12 +510,14 @@ class ScalaGenerator private extends LanguageTerms[ScalaLanguage, Target] { override def wrapToObject( name: scala.meta.Term.Name, imports: List[scala.meta.Import], - definitions: List[scala.meta.Defn] + definitions: List[scala.meta.Defn], + statements: List[scala.meta.Stat] ): Target[Option[scala.meta.Defn.Object]] = Target.pure(Some(q""" object $name { ..$imports ..$definitions + ..$statements } """)) } diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 981b1fe338..3ddd07c345 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -573,14 +573,14 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa for { widenClass <- widenClassDefinition(classDefinition.cls) companionTerm <- pureTermName(classDefinition.name) - companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(classDefinition.staticDefns.definitions)(List(_)) case enumDefinition: EnumDefinition[ScalaLanguage] => for { widenClass <- widenClassDefinition(enumDefinition.cls) companionTerm <- pureTermName(enumDefinition.name) - companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(enumDefinition.staticDefns.definitions)(List(_)) } diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceRefinedProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceRefinedProtocolGenerator.scala index 0069209b2e..6089f21624 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceRefinedProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceRefinedProtocolGenerator.scala @@ -662,14 +662,14 @@ class CirceRefinedProtocolGenerator private (circeVersion: CirceModelGenerator, for { widenClass <- widenClassDefinition(classDefinition.cls) companionTerm <- pureTermName(classDefinition.name) - companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(classDefinition.staticDefns.definitions)(List(_)) case enumDefinition: EnumDefinition[ScalaLanguage] => for { widenClass <- widenClassDefinition(enumDefinition.cls) companionTerm <- pureTermName(enumDefinition.name) - companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(enumDefinition.staticDefns.definitions)(List(_)) } diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/jackson/JacksonProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/jackson/JacksonProtocolGenerator.scala index e5cda79c2b..23079b8c99 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/jackson/JacksonProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/jackson/JacksonProtocolGenerator.scala @@ -610,14 +610,14 @@ class JacksonProtocolGenerator private extends ProtocolTerms[ScalaLanguage, Targ for { widenClass <- widenClassDefinition(classDefinition.cls) companionTerm <- pureTermName(classDefinition.name) - companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(classDefinition.staticDefns.definitions)(List(_)) case enumDefinition: EnumDefinition[ScalaLanguage] => for { widenClass <- widenClassDefinition(enumDefinition.cls) companionTerm <- pureTermName(enumDefinition.name) - companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions) + companionDefinition <- wrapToObject(companionTerm, enumDefinition.staticDefns.extraImports, enumDefinition.staticDefns.definitions, Nil) widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(enumDefinition.staticDefns.definitions)(List(_)) } From ef1d38263d815bfd72548c3c05d5348390ed843b Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Sun, 24 Dec 2023 23:35:21 -0800 Subject: [PATCH 7/8] Extending oneOf with unwrapped primitive types --- .../scala/circe/CirceProtocolGenerator.scala | 96 +++++++++++++------ 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala index 3ddd07c345..ed7d3ea81e 100644 --- a/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala +++ b/modules/scala-support/src/main/scala/dev/guardrail/generators/scala/circe/CirceProtocolGenerator.scala @@ -571,10 +571,15 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa nestedClasses <- nestedDefinitions.flatTraverse { case classDefinition: ClassDefinition[ScalaLanguage] => for { - widenClass <- widenClassDefinition(classDefinition.cls) - companionTerm <- pureTermName(classDefinition.name) - companionDefinition <- wrapToObject(companionTerm, classDefinition.staticDefns.extraImports, classDefinition.staticDefns.definitions, Nil) - widenCompanion <- companionDefinition.traverse(widenObjectDefinition) + widenClass <- widenClassDefinition(classDefinition.cls) + companionTerm <- pureTermName(classDefinition.name) + companionDefinition <- wrapToObject( + companionTerm, + classDefinition.staticDefns.extraImports, + classDefinition.staticDefns.definitions, + classDefinition.staticDefns.statements + ) + widenCompanion <- companionDefinition.traverse(widenObjectDefinition) } yield List(widenClass) ++ widenCompanion.fold(classDefinition.staticDefns.definitions)(List(_)) case enumDefinition: EnumDefinition[ScalaLanguage] => for { @@ -612,36 +617,70 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa .fold[Target[Either[String, ClassDefinition[ScalaLanguage]]]](Target.pure(Left("Does not have oneOf"))) { xs => for { nameGenerator <- Target.pure(new java.util.concurrent.atomic.AtomicInteger(1)) - getNewName = () => Target.pure(nameGenerator).map(ng => s"Nested${ng.getAndIncrement()}") + getNewName = () => Target.pure(nameGenerator).map(ng => NonEmptyList.ofInitLast(dtoPackage :+ clsName.last, s"Nested${ng.getAndIncrement()}")) + fullyQualifyPackageName = { case (outer, PropMeta(name, tpe)) => + tpe match { + case Type.Name(tn) => + for { + fullInstanceType <- Sc.selectType(NonEmptyList.ofInitLast(dtoPackage ++ outer, tn)) + } yield PropMeta(name, fullInstanceType) + case other => + Target.raiseUserError(s"Unknown type structure: ${other}") + } + }: (Option[String], PropMeta[ScalaLanguage]) => Target[PropMeta[ScalaLanguage]] (rawTypes, nestedClasses) <- xs .traverse { model => for { - resolved <- ModelResolver.propMetaWithName[ScalaLanguage, Target](() => getNewName().flatMap(Sc.pureTypeName), model, components) + resolved <- ModelResolver.propMetaWithName[ScalaLanguage, Target](() => getNewName().flatMap(Sc.selectType), model, components) (rawType, nestedClasses) <- resolved .bitraverse( - deferred => Target.fromOption(concreteTypes.find(_.clsName == deferred.value), UserError("Not supported")), + deferred => + for { + propMeta <- Target.fromOption(concreteTypes.find(_.clsName == deferred.value), UserError("Not supported")) + fqPropMeta <- fullyQualifyPackageName(None, propMeta) + } yield Left(fqPropMeta), { - case core.Resolved(Type.Name(dtoName), _, _, _) => - renderIntermediate( - model, - dtoName, - concreteTypes, - definitions, - dtoPackage, - supportPackage, - defaultPropertyRequirement, - components - ) + // There's not a great way to close the loop in ModelResolver.propMetaWithName to the fact that + // we are generating a new class, and that we need a whole bunch of other infrastructure here. + // We rely on ModelResolver, but only generate a class if the ModelResolver can't find a class. + case core.Resolved(tpe, _, _, _) if tpe.syntax.startsWith((dtoPackage :+ clsName.last).mkString(".")) => + for { + dtoName <- tpe match { + case Type.Name(x) => Target.pure(x) + case Type.Select(_, Type.Name(x)) => Target.pure(x) + case other => Target.raiseUserError(s"Unexpected type ${other}") + } + (pm, defns) <- renderIntermediate( + model, + dtoName, + concreteTypes, + definitions, + dtoPackage, + supportPackage, + defaultPropertyRequirement, + components + ) + fqPropMeta <- fullyQualifyPackageName(Some(clsName.last), pm) + } yield Right((fqPropMeta, defns)) + case core.Resolved(tpe, _, _, _) => + for { + dtoName <- getNewName() + + prefixes <- Cl.vendorPrefixes() + customTpe = CustomTypeName(model, prefixes).getOrElse(dtoName.last) + pm = PropMeta[ScalaLanguage](customTpe, tpe) + + } yield Left(pm) case other => Target.raiseUserError(s"Unexpected case ${other}") } ) - .map(e => List(e).partitionEither(identity _)) + .map(e => List(e.merge).partitionEither(identity _)) } yield (rawType, nestedClasses) } .map(_.unzip) (nestedPMs, (nestedEncoders, nestedDecoders, nestedDefns)) = nestedClasses.toList.flatten.unzip.map(_.unzip3) rawTypes <- Target.fromOption( - NonEmptyList.fromList(rawTypes.toList.flatten.map((None, _)) ++ nestedPMs.map((Some(clsName.last), _))), + NonEmptyList.fromList(rawTypes.toList.flatten ++ nestedPMs), UserError("Expected at least some models") ) fullType <- Sc.selectType(clsName.prependList(dtoPackage)) @@ -686,16 +725,17 @@ class CirceProtocolGenerator private (circeVersion: CirceModelGenerator, applyVa (members, (applyAdapters, (decoders, encoders))) <- rawTypes .traverse { - case (outer, PropMeta(memberName, tpe @ Type.Name(name))) => + case PropMeta(memberName, tpe) => for { - fullInstanceType <- Sc.selectType(NonEmptyList.ofInitLast(dtoPackage ++ outer, name)) - termName = Term.Name(name) - applyAdapter = q"implicit val ${Pat.Var(Term.Name(s"from${name}"))}: ${fullInstanceType} => ${Type.Name(clsName.last)} = members.${termName}.apply _" - member = q"case class ${tpe}(value: ${fullInstanceType}) extends ${Init.After_4_6_0(Type.Name(clsName.last), Name.Anonymous(), Nil)}" - decoder = validateDecoder(memberName, q"_root_.io.circe.Decoder[${tpe}].map(${Term.Name(s"from${name}")})") - encoder = p"case members.${termName}(member) => ${injectDiscriminator(memberName, q"member")}" + () <- Target.pure(()) + termName = Term.Name(memberName) + typeName = Type.Name(memberName) + applyAdapter = q"implicit val ${Pat.Var(Term.Name(s"from${memberName}"))}: ${tpe} => ${Type.Name(clsName.last)} = members.${termName}.apply _" + member = q"case class ${typeName}(value: ${tpe}) extends ${Init.After_4_6_0(fullType, Name.Anonymous(), Nil)}" + decoder = validateDecoder(memberName, q"_root_.io.circe.Decoder[${tpe}].map(${Term.Name(s"from${memberName}")})") + encoder = p"case members.${termName}(member) => ${injectDiscriminator(memberName, q"member")}" } yield (member, (applyAdapter, (decoder, encoder))) - case (_, other) => + case other => Target.raiseUserError(s"Unsupported case in oneOf, ${other}. Somehow got a complex type, we expected a singular Type.Name.") } .map(_.unzip.map(_.unzip.map(_.unzip))) // NonEmptyList#unzip4 adapter :see_no_evil: From b3e0edd45bb498a036d855b79ed3af6b0a96712c Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Mon, 25 Dec 2023 00:00:58 -0800 Subject: [PATCH 8/8] Catch anonymous oneOf instances --- .../src/test/scala/core/issues/Issue195.scala | 45 +++++++++++++------ .../src/main/resources/issues/issue195.yaml | 10 +++++ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/modules/sample-akkaHttp/src/test/scala/core/issues/Issue195.scala b/modules/sample-akkaHttp/src/test/scala/core/issues/Issue195.scala index e88ee52824..dd9c99b08f 100644 --- a/modules/sample-akkaHttp/src/test/scala/core/issues/Issue195.scala +++ b/modules/sample-akkaHttp/src/test/scala/core/issues/Issue195.scala @@ -7,26 +7,45 @@ import io.circe._, io.circe.syntax._ import issues.issue195.server.akkaHttp.definitions._ class Issue195 extends AnyFunSpec with Matchers { - def emit[A](label: String, func: () => A, expected: Json)(implicit ev: A => Foo): Unit = - describe(label) { - it("should encode") { - Foo(func()).asJson shouldBe expected - } + describe("oneOf mapping") { + def emit[A](label: String, func: () => A, expected: Json)(implicit ev: A => Foo): Unit = + describe(label) { + it("should encode") { + Foo(func()).asJson shouldBe expected + } - it("should decode") { - expected.as[Foo] shouldBe Right(Foo(func())) - } + it("should decode") { + expected.as[Foo] shouldBe Right(Foo(func())) + } - it("should round-trip") { - Foo(func()).asJson.as[Foo] shouldBe Right(Foo(func())) + it("should round-trip") { + Foo(func()).asJson.as[Foo] shouldBe Right(Foo(func())) + } } - } - - describe("oneOf mapping") { emit("TypeA", () => TypeA("foo"), Json.obj("type" -> Json.fromString("foo"), "beepBorp" -> Json.fromString("typea"))) emit("TypeB", () => TypeB("foo"), Json.obj("type" -> Json.fromString("foo"), "beepBorp" -> Json.fromString("TypeB"))) emit("TypeC", () => Foo.WrappedC(TypeC("foo")), Json.obj("C" -> Json.obj("type" -> Json.fromString("foo")), "beepBorp" -> Json.fromString("WrappedC"))) emit("typed", () => Foo.Nested2(Typed("foo")), Json.obj("D" -> Json.obj("type" -> Json.fromString("foo")), "beepBorp" -> Json.fromString("Nested2"))) emit("TypeE", () => Foo.Nested3(TypeE("foo")), Json.obj("E" -> Json.obj("type" -> Json.fromString("foo")), "beepBorp" -> Json.fromString("Nested3"))) } + + describe("anonymous oneOf mapping") { + def emit[A](label: String, func: () => A, expected: Json)(implicit ev: A => X.Links): Unit = + describe(label) { + it("should encode") { + X(func()).asJson shouldBe expected + } + + it("should decode") { + expected.as[X] shouldBe Right(X(func())) + } + + it("should round-trip") { + X(func()).asJson.as[X] shouldBe Right(X(func())) + } + } + + emit("first branch, Vector[String]", () => X.Links(Vector("1", "2")), Json.obj("links" -> Json.arr(Json.fromString("1"), Json.fromString("2")))) + emit("second branch, object", () => X.Links(Json.obj("a" -> Json.fromString("hey"))), Json.obj("links" -> Json.obj("a" -> Json.fromString("hey")))) + } } diff --git a/modules/sample/src/main/resources/issues/issue195.yaml b/modules/sample/src/main/resources/issues/issue195.yaml index ba4d4b7385..707b8379fc 100644 --- a/modules/sample/src/main/resources/issues/issue195.yaml +++ b/modules/sample/src/main/resources/issues/issue195.yaml @@ -51,6 +51,16 @@ components: propertyName: beepBorp mapping: typea: '#/components/schemas/TypeA' + X: + type: object + required: [ links ] + properties: + links: + oneOf: + - type: array + items: + type: string + - type: object paths: /foobar: get: