Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gen] fix for codegen of sumtypes with reusable fields #2850

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ lazy val zioHttpGen = (project in file("zio-http-gen"))
`zio-test-sbt`,
scalafmt.cross(CrossVersion.for3Use2_13),
scalametaParsers.cross(CrossVersion.for3Use2_13).exclude("org.scala-lang.modules", "scala-collection-compat_2.13"),
`zio-json-yaml` % Test
),
)
.settings(
Expand Down
2 changes: 2 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ object Dependencies {
val ScalaCompactCollectionVersion = "2.12.0"
val ZioVersion = "2.1.1"
val ZioCliVersion = "0.5.0"
val ZioJsonVersion = "0.6.2"
val ZioSchemaVersion = "1.1.1"
val SttpVersion = "3.3.18"
val ZioConfigVersion = "4.0.2"
Expand Down Expand Up @@ -34,6 +35,7 @@ object Dependencies {

val zio = "dev.zio" %% "zio" % ZioVersion
val `zio-cli` = "dev.zio" %% "zio-cli" % ZioCliVersion
val `zio-json-yaml` = "dev.zio" %% "zio-json-yaml" % ZioJsonVersion
val `zio-streams` = "dev.zio" %% "zio-streams" % ZioVersion
val `zio-schema` = "dev.zio" %% "zio-schema" % ZioSchemaVersion
val `zio-schema-json` = "dev.zio" %% "zio-schema-json" % ZioSchemaVersion
Expand Down
15 changes: 15 additions & 0 deletions zio-http-gen/src/main/scala/zio/http/gen/openapi/Config.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package zio.http.gen.openapi

final case class Config(commonFieldsOnSuperType: Boolean)
object Config {

val default: Config = Config(
commonFieldsOnSuperType = false,
)

lazy val config: zio.Config[Config] =
zio.Config
.boolean("common-fields-on-super-type")
.withDefault(Config.default.commonFieldsOnSuperType)
.map(Config.apply)
}
240 changes: 200 additions & 40 deletions zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions zio-http-gen/src/main/scala/zio/http/gen/scala/Code.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ object Code {
Object(name, schema = false, endpoints, Nil, Nil, Nil)
}

final case class CaseClass(name: String, fields: List[Field], companionObject: Option[Object]) extends ScalaType
final case class CaseClass(name: String, fields: List[Field], companionObject: Option[Object], mixins: List[String])
extends ScalaType

object CaseClass {
def apply(name: String): CaseClass = CaseClass(name, Nil, None)
def apply(name: String, mixins: List[String]): CaseClass = CaseClass(name, Nil, None, mixins)
}

final case class Enum(
Expand All @@ -75,6 +76,7 @@ object Code {
discriminator: Option[String] = None,
noDiscriminator: Boolean = false,
schema: Boolean = true,
abstractMembers: List[Field] = Nil,
) extends ScalaType

sealed abstract case class Field private (name: String, fieldType: ScalaType) extends Code {
Expand Down
39 changes: 34 additions & 5 deletions zio-http-gen/src/main/scala/zio/http/gen/scala/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,24 @@ object CodeGen {
"\n}"
Nil -> content

case Code.CaseClass(name, fields, companionObject) =>
case Code.CaseClass(name, fields, companionObject, mixins) =>
val (imports, contents) = fields.map(render(basePackage)).unzip
val (coImports, coContent) =
companionObject.map { co =>
val (coImports, coContent) = render(basePackage)(co)
(coImports, s"\n$coContent")
}.getOrElse(Nil -> "")
val mixinsString = mixins match {
case Nil => ""
case _ => mixins.mkString(" extends ", " with ", "")
}
val content =
s"case class $name(\n" +
contents.mkString(",\n").replace("val ", " ") +
"\n)" + coContent
"\n)" + mixinsString + coContent
(imports.flatten ++ coImports).distinct -> content

case Code.Enum(name, cases, caseNames, discriminator, noDiscriminator, schema) =>
case Code.Enum(name, cases, caseNames, discriminator, noDiscriminator, schema, abstractMembers) =>
val discriminatorAnnotation =
if (noDiscriminator) "@noDiscriminator\n" else ""
val discriminatorNameAnnotation =
Expand All @@ -118,15 +122,40 @@ object CodeGen {
imports -> contents.mkString("\n")
}

val (traitBodyImports, traitBody) = {
val traitBodyBuilder = new StringBuilder().append(' ')
var pre = '{'
val imports = abstractMembers.foldLeft(List.empty[Code.Import]) {
case (importsAcc, Code.Field(name, fieldType)) =>
val (imports, tpe) = render(basePackage)(fieldType)
if (tpe.isEmpty) importsAcc
else {
traitBodyBuilder += pre
pre = '\n'
traitBodyBuilder ++= "def "
traitBodyBuilder ++= name
traitBodyBuilder ++= ": "
traitBodyBuilder ++= tpe

imports ::: importsAcc
}
}
val body =
if (pre == '{') "\n"
else traitBodyBuilder.append("\n}\n").result()

imports -> body
}

val content =
discriminatorAnnotation +
discriminatorNameAnnotation +
s"sealed trait $name\n" +
s"sealed trait $name" + traitBody +
s"object $name {\n" +
(if (schema) s"\n\n implicit val codec: Schema[$name] = DeriveSchema.gen[$name]\n" else "") +
casesContent +
"\n}"
casesImports.flatten.distinct -> content
casesImports.foldRight(traitBodyImports)(_ ::: _).distinct -> content

case col: Code.Collection =>
col match {
Expand Down
31 changes: 31 additions & 0 deletions zio-http-gen/src/test/resources/ComponentAnimal.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package test.component

import zio.schema._
import zio.schema.annotation._

@noDiscriminator
sealed trait Animal
object Animal {

implicit val codec: Schema[Animal] = DeriveSchema.gen[Animal]
case class Alligator(
age: Int,
weight: Float,
num_teeth: Int,
) extends Animal
object Alligator {

implicit val codec: Schema[Alligator] = DeriveSchema.gen[Alligator]

}
case class Zebra(
age: Int,
weight: Float,
num_stripes: Int,
) extends Animal
object Zebra {

implicit val codec: Schema[Zebra] = DeriveSchema.gen[Zebra]

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package test.component

import zio.schema._
import zio.schema.annotation._

@noDiscriminator
sealed trait Animal {
def age: Int
def weight: Float
}
object Animal {

implicit val codec: Schema[Animal] = DeriveSchema.gen[Animal]
case class Alligator(
age: Int,
weight: Float,
num_teeth: Int,
) extends Animal
object Alligator {

implicit val codec: Schema[Alligator] = DeriveSchema.gen[Alligator]

}
case class Zebra(
age: Int,
weight: Float,
num_stripes: Int,
) extends Animal
object Zebra {

implicit val codec: Schema[Zebra] = DeriveSchema.gen[Zebra]

}
}
12 changes: 12 additions & 0 deletions zio-http-gen/src/test/resources/ComponentHttpError.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package test.component

import zio.schema._

case class HttpError(
messages: Option[String],
)
object HttpError {

implicit val codec: Schema[HttpError] = DeriveSchema.gen[HttpError]

}
15 changes: 15 additions & 0 deletions zio-http-gen/src/test/resources/EndpointForZoo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test.api.v1.zoo

import test.component._
import zio.Chunk

object Animal {
import zio.http._
import zio.http.endpoint._
import zio.http.codec._
val get_animal = Endpoint(Method.GET / "api" / "v1" / "zoo" / string("animal"))
.in[Unit]
.out[Chunk[Animal]](status = Status.Ok)
.outError[HttpError](status = Status.InternalServerError)

}
14 changes: 14 additions & 0 deletions zio-http-gen/src/test/resources/EndpointForZooNoError.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test.api.v1.zoo

import test.component._
import zio.Chunk

object Animal {
import zio.http._
import zio.http.endpoint._
import zio.http.codec._
val get_animal = Endpoint(Method.GET / "api" / "v1" / "zoo" / string("animal"))
.in[Unit]
.out[Chunk[Animal]](status = Status.Ok)

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object PaymentNamedDiscriminator {
case class Card(
number: String,
cvv: String,
)
) extends PaymentNamedDiscriminator
object Card {

implicit val codec: Schema[Card] = DeriveSchema.gen[Card]
Expand All @@ -21,7 +21,7 @@ object PaymentNamedDiscriminator {
@caseName("cash")
case class Cash(
amount: Int,
)
) extends PaymentNamedDiscriminator
object Cash {

implicit val codec: Schema[Cash] = DeriveSchema.gen[Cash]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ object PaymentNoDiscriminator {
case class Card(
number: String,
cvv: String,
)
) extends PaymentNoDiscriminator
object Card {

implicit val codec: Schema[Card] = DeriveSchema.gen[Card]

}
case class Cash(
amount: Int,
)
) extends PaymentNoDiscriminator
object Cash {

implicit val codec: Schema[Cash] = DeriveSchema.gen[Cash]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
info:
title: Animals Service
version: 0.0.1
tags:
- name: Animals_API
paths:
/api/v1/zoo/{animal}:
get:
operationId: get_animal
parameters:
- in: path
name: animal
schema:
type: string
required: true
tags:
- Animals_API
description: Get animals by species name
responses:
"200":
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Animal'
description: OK
openapi: 3.0.3
components:
schemas:
Animal:
oneOf:
- $ref: '#/components/schemas/Alligator'
- $ref: '#/components/schemas/Zebra'
HasAgeAndWeight:
type: object
required:
- age
properties:
age:
type: integer
format: int32
minimum: 0
weight:
type: number
format: float
minimum: 0
HasWeight:
type: object
required:
- weight
properties:
weight:
type: number
format: double
minimum: 0
Alligator:
allOf:
- $ref: '#/components/schemas/HasAgeAndWeight'
- $ref: '#/components/schemas/HasWeight'
- type: object
required:
- num_teeth
properties:
num_teeth:
type: integer
format: int32
minimum: 0
Zebra:
allOf:
- $ref: '#/components/schemas/HasAgeAndWeight'
- $ref: '#/components/schemas/HasWeight'
- type: object
required:
- num_stripes
properties:
num_stripes:
type: integer
format: int32
minimum: 0
Loading
Loading