Skip to content

Commit

Permalink
[gen] normalized fields with annotations holding original field name (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
hochgi authored Sep 4, 2024
1 parent 602ddce commit 200e816
Show file tree
Hide file tree
Showing 19 changed files with 452 additions and 192 deletions.
13 changes: 7 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ lazy val zioHttpExample = (project in file("zio-http-example"))
.settings(libraryDependencies ++= Seq(`jwt-core`, `zio-schema-json`))
.settings(
libraryDependencies ++= Seq(
"dev.zio" %% "zio-config" % ZioConfigVersion,
"dev.zio" %% "zio-config-typesafe" % ZioConfigVersion,
"dev.zio" %% "zio-config-magnolia" % ZioConfigVersion,
`zio-config`,
`zio-config-magnolia`,
`zio-config-typesafe`,
"dev.zio" %% "zio-metrics-connectors" % "2.3.1",
"dev.zio" %% "zio-metrics-connectors-prometheus" % "2.3.1",
),
Expand All @@ -305,6 +305,7 @@ lazy val zioHttpGen = (project in file("zio-http-gen"))
`zio`,
`zio-test`,
`zio-test-sbt`,
`zio-config`,
scalafmt.cross(CrossVersion.for3Use2_13),
scalametaParsers
.cross(CrossVersion.for3Use2_13)
Expand Down Expand Up @@ -404,9 +405,9 @@ lazy val docs = project
libraryDependencies ++= Seq(
`jwt-core`,
"dev.zio" %% "zio-test" % ZioVersion,
"dev.zio" %% "zio-config" % ZioConfigVersion,
"dev.zio" %% "zio-config-magnolia" % ZioConfigVersion,
"dev.zio" %% "zio-config-typesafe" % ZioConfigVersion,
`zio-config`,
`zio-config-magnolia`,
`zio-config-typesafe`,
),
publish / skip := true,
mdocVariables ++= Map(
Expand Down
4 changes: 4 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object Dependencies {
val ZioVersion = "2.1.9"
val ZioCliVersion = "0.5.0"
val ZioJsonVersion = "0.7.1"
val ZioParserVersion = "0.1.10"
val ZioSchemaVersion = "1.4.1"
val SttpVersion = "3.3.18"
val ZioConfigVersion = "4.0.2"
Expand Down Expand Up @@ -36,6 +37,9 @@ object Dependencies {

val zio = "dev.zio" %% "zio" % ZioVersion
val `zio-cli` = "dev.zio" %% "zio-cli" % ZioCliVersion
val `zio-config` = "dev.zio" %% "zio-config" % ZioConfigVersion
val `zio-config-magnolia` = "dev.zio" %% "zio-config-magnolia" % ZioConfigVersion
val `zio-config-typesafe` = "dev.zio" %% "zio-config-typesafe" % ZioConfigVersion
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
Expand Down
97 changes: 71 additions & 26 deletions zio-http-gen/src/main/scala/zio/http/gen/openapi/Config.scala
Original file line number Diff line number Diff line change
@@ -1,38 +1,83 @@
package zio.http.gen.openapi

import zio.config.ConfigOps

import zio.http.gen.openapi.Config.NormalizeFields

// format: off
/**
* @param commonFieldsOnSuperType oneOf expressions in openapi result in sealed traits in generated scala code.
* if this flag is set to true, and all oneOf's "subtypes" are defined in terms of
* an allOf expression, and all share same object(s) included in the allOf expression,
* then the common fields from that shared object(s) will result in abstract fields
* defined on the sealed trait.
*
* @param generateSafeTypeAliases Referencing primitives, and giving them a name makes the openapi spec more readable.
* By default, the generated scala code will resolve the referenced name,
* and replace it with the primitive type.
* By setting this flag to true, the generator will create components with zio.prelude Newtype
* definitions wrapping over the aliased primitive type.
*
* Note: only aliased primitives are supported for now.
*
* TODO: in the future we can consider an enum instead of boolean for different aliased types.
* e.g: scala 3 opaque types, neotype, prelude's newtype, etc'…
*
* @param fieldNamesNormalization OpenAPI can declare fields that have unconventional "casing" in scala,
* like snake_case, or kebab-case.
* This configuration allows to normalize these fields.
* The original casing will be preserved via a @fieldName("<original-name>") annotation.
*/
// format: on
final case class Config(
commonFieldsOnSuperType: Boolean /*
* oneOf expressions in openapi result in sealed traits in generated scala code.
* if this flag is set to true, and all oneOf's "sub types" are defined in terms of
* an allOf expression, and all share same object(s) included in the allOf expression,
* then the common fields from that shared object(s) will result in abstract fields
* defined on the sealed trait.
*/,
generateSafeTypeAliases: Boolean, /*
* Referencing primitives, and giving them a name makes the openapi spec more readable.
* By default, the generated scala code will resolve the referenced name,
* and replace it with the primitive type.
* By setting this flag to true, the generator will create components with zio.prelude Newtype
* definitions wrapping over the aliased primitive type.
*
* Note: only aliased primitives are supported for now.
*
* TODO: in the future we can consider an enum instead of boolean for different aliased types.
* e.g: scala 3 opaque types, neotype, prelude's newtype, etc'…
*/
commonFieldsOnSuperType: Boolean,
generateSafeTypeAliases: Boolean,
fieldNamesNormalization: NormalizeFields,
)
object Config {

// format: off
/**
* @param enableAutomatic If enabled, the generator will attempt to normalize field names to camelCase,
* unless original field is defined in the specialReplacements map.
*
* @param manualOverrides When normalization is enabled, a heuristic parser will attempt to normalize field names.
* But this is not always possible, or may not yield the desired result.
* Consider field names that are defined in the JSON as `"1st"`, `"2nd"`, or `"3rd"`:
* You may want to override auto normalization in this case and provide a map like: {{{
* Map(
* "1st" -> "first",
* "2nd" -> "second",
* "3rd" -> "third"
* )
* }}}
*/
// format: on
final case class NormalizeFields(
enableAutomatic: Boolean,
manualOverrides: Map[String, String],
)
object NormalizeFields {
lazy val config: zio.Config[NormalizeFields] = (
zio.Config.boolean("enabled").withDefault(Config.default.fieldNamesNormalization.enableAutomatic) ++
zio.Config
.table("special-replacements", zio.Config.string)
.withDefault(Config.default.fieldNamesNormalization.manualOverrides)
).to[NormalizeFields]
}

val default: Config = Config(
commonFieldsOnSuperType = false,
generateSafeTypeAliases = false,
fieldNamesNormalization = NormalizeFields(
enableAutomatic = false,
manualOverrides = Map.empty,
),
)

lazy val config: zio.Config[Config] = {
val c =
zio.Config.boolean("common-fields-on-super-type").withDefault(Config.default.commonFieldsOnSuperType) ++
zio.Config.boolean("generate-safe-type-aliases").withDefault(Config.default.generateSafeTypeAliases)

c.map { case (a, b) => Config(a, b) }
}
lazy val config: zio.Config[Config] = (
zio.Config.boolean("common-fields-on-super-type").withDefault(Config.default.commonFieldsOnSuperType) ++
zio.Config.boolean("generate-safe-type-aliases").withDefault(Config.default.generateSafeTypeAliases) ++
NormalizeFields.config.nested("fields-normalization")
).to[Config]
}
Loading

0 comments on commit 200e816

Please sign in to comment.