Skip to content

Commit

Permalink
[gen] dictionaries with referenced keys extension (zio#3043)
Browse files Browse the repository at this point in the history
  • Loading branch information
hochgi authored Aug 30, 2024
1 parent b4dde6a commit ed4ce0f
Show file tree
Hide file tree
Showing 15 changed files with 643 additions and 54 deletions.
31 changes: 28 additions & 3 deletions zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import zio.Chunk
import zio.http.Method
import zio.http.endpoint.openapi.OpenAPI.ReferenceOr
import zio.http.endpoint.openapi.{JsonSchema, OpenAPI}
import zio.http.gen.scala.Code.{CodecType, Collection, PathSegmentCode, ScalaType}
import zio.http.gen.scala.Code.{CodecType, Collection, PathSegmentCode, ScalaType, TypeRef}
import zio.http.gen.scala.{Code, CodeGen}

object EndpointGen {
Expand Down Expand Up @@ -268,7 +268,7 @@ final case class EndpointGen(config: Config) {
case tref: Code.TypeRef => f(tref)
case Collection.Seq(inner, nonEmpty) => Collection.Seq(mapTypeRef(inner)(f), nonEmpty)
case Collection.Set(inner, nonEmpty) => Collection.Set(mapTypeRef(inner)(f), nonEmpty)
case Collection.Map(inner) => Collection.Map(mapTypeRef(inner)(f))
case Collection.Map(inner, keysType) => Collection.Map(mapTypeRef(inner)(f), keysType)
case Collection.Opt(inner) => Collection.Opt(mapTypeRef(inner)(f))
case _ => sType
}
Expand Down Expand Up @@ -1302,11 +1302,36 @@ final case class EndpointGen(config: Config) {
throw new Exception("Object with properties and additionalProperties is not supported")
case JsonSchema.Object(properties, additionalProperties, _)
if properties.isEmpty && additionalProperties.isRight =>
val (vSchema, kSchemaOpt) = {
val vs = additionalProperties.toOption.get
val (ks, annotations) = JsonSchema.Object.extractKeySchemaFromAnnotations(vs)
vs.withoutAnnotations.annotate(annotations) -> ks
}

Some(
Code.Field(
name,
Code.Collection.Map(
schemaToField(additionalProperties.toOption.get, openAPI, name, annotations).get.fieldType,
schemaToField(vSchema, openAPI, name, annotations).get.fieldType,
kSchemaOpt.collect {
case ss: JsonSchema.String =>
schemaToField(ss, openAPI, name, annotations).get.fieldType
case JsonSchema.RefSchema(ref) =>
val baref = ref.replaceFirst("^#/components/schemas/", "")
resolveSchemaRef(openAPI, baref) match {
case ks: JsonSchema.String =>
if (config.generateSafeTypeAliases) TypeRef(baref + ".Type")
else schemaToField(ks, openAPI, name, annotations).get.fieldType
case nonStringSchema =>
throw new IllegalArgumentException(
s"x-string-key-schema must reference a string schema, but got: ${nonStringSchema.toJson}",
)
}
case nonStringSchema =>
throw new IllegalArgumentException(
s"x-string-key-schema must be a string schema, but got: ${nonStringSchema.toJson}",
)
},
),
),
)
Expand Down
10 changes: 5 additions & 5 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 @@ -13,7 +13,7 @@ object Code {
sealed trait ScalaType extends Code { self =>
def seq(nonEmpty: Boolean): Collection.Seq = Collection.Seq(self, nonEmpty)
def set(nonEmpty: Boolean): Collection.Set = Collection.Set(self, nonEmpty)
def map: Collection.Map = Collection.Map(self)
def map: Collection.Map = Collection.Map(self, None)
def opt: Collection.Opt = Collection.Opt(self)
}

Expand Down Expand Up @@ -165,10 +165,10 @@ object Code {
}

object Collection {
final case class Seq(elementType: ScalaType, nonEmpty: Boolean) extends Collection
final case class Set(elementType: ScalaType, nonEmpty: Boolean) extends Collection
final case class Map(elementType: ScalaType) extends Collection
final case class Opt(elementType: ScalaType) extends Collection
final case class Seq(elementType: ScalaType, nonEmpty: Boolean) extends Collection
final case class Set(elementType: ScalaType, nonEmpty: Boolean) extends Collection
final case class Map(elementType: ScalaType, keysType: Option[ScalaType]) extends Collection
final case class Opt(elementType: ScalaType) extends Collection
}

sealed trait Primitive extends ScalaType
Expand Down
9 changes: 6 additions & 3 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 @@ -198,9 +198,12 @@ object CodeGen {
val (imports, tpe) = render(basePackage)(elementType)
if (nonEmpty) (Code.Import("zio.prelude.NonEmptySet") :: imports) -> s"NonEmptySet[$tpe]"
else imports -> s"Set[$tpe]"
case Code.Collection.Map(elementType) =>
val (imports, tpe) = render(basePackage)(elementType)
imports -> s"Map[String, $tpe]"
case Code.Collection.Map(elementType, keysType) =>
val (vImports, vType) = render(basePackage)(elementType)
keysType.fold(vImports -> s"Map[String, $vType]") { keyType =>
val (kImports, kType) = render(basePackage)(keyType)
(kImports ::: vImports).distinct -> s"Map[$kType, $vType]"
}
case Code.Collection.Opt(elementType) =>
val (imports, tpe) = render(basePackage)(elementType)
imports -> s"Option[$tpe]"
Expand Down
9 changes: 9 additions & 0 deletions zio-http-gen/src/test/resources/ComponentAliasOrderId.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package test.component

import zio.prelude.Newtype
import zio.schema.Schema
import java.util.UUID

object OrderId extends Newtype[UUID] {
implicit val schema: Schema[OrderId.Type] = Schema.primitive[UUID].transform(wrap, unwrap)
}
9 changes: 9 additions & 0 deletions zio-http-gen/src/test/resources/ComponentAliasUserId.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package test.component

import zio.prelude.Newtype
import zio.schema.Schema
import java.util.UUID

object UserId extends Newtype[UUID] {
implicit val schema: Schema[UserId.Type] = Schema.primitive[UUID].transform(wrap, unwrap)
}
14 changes: 14 additions & 0 deletions zio-http-gen/src/test/resources/ComponentOrder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test.component

import zio.schema._
import java.util.UUID

case class Order(
id: UUID,
product: String,
@zio.schema.annotation.validate[Int](zio.schema.validation.Validation.greaterThan(0)) quantity: Int,
@zio.schema.annotation.validate[Double](zio.schema.validation.Validation.greaterThan(-1.0)) price: Double,
)
object Order {
implicit val codec: Schema[Order] = DeriveSchema.gen[Order]
}
13 changes: 13 additions & 0 deletions zio-http-gen/src/test/resources/ComponentOrderWithAliases.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package test.component

import zio.schema._

case class Order(
id: OrderId.Type,
product: String,
@zio.schema.annotation.validate[Int](zio.schema.validation.Validation.greaterThan(0)) quantity: Int,
@zio.schema.annotation.validate[Double](zio.schema.validation.Validation.greaterThan(-1.0)) price: Double,
)
object Order {
implicit val codec: Schema[Order] = DeriveSchema.gen[Order]
}
12 changes: 12 additions & 0 deletions zio-http-gen/src/test/resources/ComponentUserOrderHistory.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package test.component

import zio.schema._
import java.util.UUID

case class UserOrderHistory(
user_id: UUID,
history: Map[UUID, Order],
)
object UserOrderHistory {
implicit val codec: Schema[UserOrderHistory] = DeriveSchema.gen[UserOrderHistory]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package test.component

import zio.schema._

case class UserOrderHistory(
user_id: UserId.Type,
history: Map[OrderId.Type, Order],
)
object UserOrderHistory {
implicit val codec: Schema[UserOrderHistory] = DeriveSchema.gen[UserOrderHistory]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
info:
title: Shop Service
version: 0.0.1
servers:
- url: http://127.0.0.1:5000/
tags:
- name: Order_API
paths:
/api/v1/shop/history/{id}:
get:
operationId: get_user_history
parameters:
- in: path
name: id
schema:
type: string
format: uuid
required: true
tags:
- Order_API
description: Get user order history by user id
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/UserOrderHistory'
description: OK
openapi: 3.0.3
components:
schemas:
UserOrderHistory:
type: object
required:
- user_id
- history
properties:
user_id:
type: string
format: uuid
history:
type: object
additionalProperties:
$ref: '#/components/schemas/Order'
x-string-key-schema:
type: string
format: uuid
Order:
type: object
required:
- id
- product
- quantity
- price
properties:
id:
type: string
format: uuid
product:
type: string
quantity:
type: integer
format: int32
minimum: 1
price:
type: number
minimum: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
info:
title: Shop Service
version: 0.0.1
servers:
- url: http://127.0.0.1:5000/
tags:
- name: Order_API
paths:
/api/v1/shop/history/{id}:
get:
operationId: get_user_history
parameters:
- in: path
name: id
schema:
$ref: '#/components/schemas/UserId'
required: true
tags:
- Order_API
description: Get user order history by user id
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/UserOrderHistory'
description: OK
openapi: 3.0.3
components:
schemas:
UserOrderHistory:
type: object
required:
- user_id
- history
properties:
user_id:
$ref: '#/components/schemas/UserId'
history:
type: object
additionalProperties:
$ref: '#/components/schemas/Order'
x-string-key-schema:
$ref: '#/components/schemas/OrderId'
Order:
type: object
required:
- id
- product
- quantity
- price
properties:
id:
$ref: '#/components/schemas/OrderId'
product:
type: string
quantity:
type: integer
format: int32
minimum: 1
price:
type: number
minimum: 0
OrderId:
type: string
format: uuid
UserId:
type: string
format: uuid
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
info:
title: Shop Service
version: 0.0.1
servers:
- url: http://127.0.0.1:5000/
tags:
- name: Order_API
paths:
/api/v1/shop/history/{id}:
get:
operationId: get_user_history
parameters:
- in: path
name: id
schema:
$ref: '#/components/schemas/UserId'
required: true
tags:
- Order_API
description: Get user order history by user id
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/UserOrderHistory'
description: OK
openapi: 3.0.3
components:
schemas:
UserOrderHistory:
type: object
required:
- user_id
- history
properties:
user_id:
$ref: '#/components/schemas/UserId'
history:
type: object
additionalProperties:
$ref: '#/components/schemas/Order'
x-string-key-schema:
$ref: '#/components/schemas/OrderId'
Order:
type: object
required:
- id
- product
- quantity
- price
properties:
id:
$ref: '#/components/schemas/OrderId'
product:
type: string
quantity:
type: integer
format: int32
minimum: 1
price:
type: number
minimum: 0
OrderId:
type: integer
format: int32
UserId:
type: string
format: uuid
Loading

0 comments on commit ed4ce0f

Please sign in to comment.