Skip to content

Commit

Permalink
Deconstruct tupled examples (zio#2609)
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil committed Jan 19, 2024
1 parent a9a2204 commit 750e824
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import zio.json.ast.Json
import zio.json.{EncoderOps, JsonEncoder}
import zio.test._
import zio.{Scope, ZIO}

import zio.schema.annotation.{caseName, discriminatorName, noDiscriminator, optionalField, transientField}
import zio.schema.codec.JsonCodec
import zio.schema.{DeriveSchema, Schema}

import zio.http.Method.{GET, POST}
import zio.http._
import zio.http.codec.PathCodec.string
import zio.http.codec.{Doc, HttpCodec, QueryCodec}
import zio.http.endpoint._

import scala.util.chaining.scalaUtilChainingOps

object OpenAPIGenSpec extends ZIOSpecDefault {

final case class SimpleInputBody(name: String, age: Int)
Expand Down Expand Up @@ -114,6 +115,12 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
case class NestedThree(name: String) extends SimpleNestedSealedTrait
}

case class Payload(content: String)

object Payload {
implicit val schema: Schema[Payload] = DeriveSchema.gen[Payload]
}

private val simpleEndpoint =
Endpoint(
(GET / "static" / int("id") / uuid("uuid") ?? Doc.p("user id") / string("name")) ?? Doc.p("get path"),
Expand All @@ -139,7 +146,7 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
.outError[NotFoundError](Status.NotFound)

def toJsonAst(str: String): Json =
Json.decoder.decodeJson(str).toOption.get
Json.decoder.decodeJson(str).tap(println).toOption.get

def toJsonAst(api: OpenAPI): Json =
toJsonAst(api.toJson)
Expand Down Expand Up @@ -2357,6 +2364,95 @@ object OpenAPIGenSpec extends ZIOSpecDefault {
)(buf => ZIO.attemptBlockingIO(buf.close()).orDie)(buf => ZIO.attemptBlockingIO(buf.mkString))
} yield assertTrue(json == toJsonAst(expectedJson))
},
test("examples for combined input"){

val endpoint =
Endpoint(Method.GET / "root" / string("name"))
.in[Payload]
.out[String]
.examplesIn("hi" -> ("name_value", Payload("input")))

val openApi =
OpenAPIGen.fromEndpoints(
title = "Combined input examples",
version = "1.0",
endpoint,
)
val json = toJsonAst(openApi)
val expectedJson = """"{
| "openapi": "3.1.0",
| "info": {
| "title": "Combined input examples",
| "version": "1.0"
| },
| "paths": {
| "/root/{name}": {
| "get": {
| "parameters": [
| {
| "name": "name",
| "in": "path",
| "required": true,
| "deprecated": false,
| "schema": {
| "type": "string"
| },
| "explode": false,
| "style": "simple"
| }
| ],
| "requestBody": {
| "content": {
| "application/json": {
| "schema": {
| "$ref": "#/components/schemas/Payload"
| },
| "examples": {
| "hi": {
| "value": {
| "content": "input"
| }
| }
| }
| }
| },
| "required": true
| },
| "responses": {
| "200": {
| "description": "",
| "content": {
| "application/json": {
| "schema": {
| "type": "string"
| }
| }
| }
| }
| },
| "deprecated": false
| }
| }
| },
| "components": {
| "schemas": {
| "Payload": {
| "type": "object",
| "properties": {
| "content": {
| "type": "string"
| }
| },
| "additionalProperties": true,
| "required": [
| "content"
| ]
| }
| }
| }
|}""".stripMargin
assertTrue(json == toJsonAst(expectedJson))
}
)

}
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package zio.http.endpoint.openapi

import java.util.UUID

import scala.annotation.tailrec
import scala.collection.{immutable, mutable}

import zio.Chunk
import zio.http._
import zio.http.codec.HttpCodec.Metadata
import zio.http.codec._
import zio.http.endpoint._
import zio.http.endpoint.openapi.JsonSchema.SchemaStyle
import zio.http.endpoint.openapi.OpenAPIGen.AtomizedMetaCodecs.reduceExamplesLeft
import zio.json.EncoderOps
import zio.json.ast.Json

import zio.schema.Schema.Record
import zio.schema.codec.JsonCodec
import zio.schema.{Schema, TypeId}

import zio.http._
import zio.http.codec.HttpCodec.Metadata
import zio.http.codec._
import zio.http.endpoint._
import zio.http.endpoint.openapi.JsonSchema.SchemaStyle
import java.util.UUID
import scala.annotation.tailrec
import scala.collection.{immutable, mutable}

object OpenAPIGen {
private val PathWildcard = "pathWildcard"
Expand Down Expand Up @@ -133,13 +131,9 @@ object OpenAPIGen {
def contentExamples: Map[String, OpenAPI.ReferenceOr.Or[OpenAPI.Example]] =
content.flatMap {
case mc @ MetaCodec(HttpCodec.Content(schema, _, _, _), _) =>
mc.examples.map { case (name, value) =>
name -> OpenAPI.ReferenceOr.Or(OpenAPI.Example(toJsonAst(schema, value)))
}
mc.examples(schema)
case mc @ MetaCodec(HttpCodec.ContentStream(schema, _, _, _), _) =>
mc.examples.map { case (name, value) =>
name -> OpenAPI.ReferenceOr.Or(OpenAPI.Example(toJsonAst(schema, value)))
}
mc.examples(schema)
case _ =>
Map.empty[String, OpenAPI.ReferenceOr.Or[OpenAPI.Example]]
}.toMap
Expand Down Expand Up @@ -194,17 +188,44 @@ object OpenAPIGen {
annotations: Chunk[HttpCodec.Metadata[Any]] = Chunk.empty,
): Chunk[MetaCodec[_]] =
in match {
case HttpCodec.Combine(left, right, _) =>
flattenedAtoms(left, annotations) ++ flattenedAtoms(right, annotations)
case path: HttpCodec.Path[_] => Chunk.fromIterable(path.pathCodec.segments.map(metaCodecFromSegment))
case atom: HttpCodec.Atom[_, _] => Chunk(MetaCodec(atom, annotations))
case HttpCodec.Combine(left, right, combiner) =>
flattenedAtoms(left, reduceExamplesLeft(annotations, combiner)) ++
flattenedAtoms(right, reduceExamplesRight(annotations, combiner))
case path: HttpCodec.Path[_] => Chunk.fromIterable(path.pathCodec.segments.map(metaCodecFromSegment))
case atom: HttpCodec.Atom[_, _] => Chunk(MetaCodec(atom, annotations))
case map: HttpCodec.TransformOrFail[_, _, _] => flattenedAtoms(map.api, annotations)
case HttpCodec.Empty => Chunk.empty
case HttpCodec.Halt => Chunk.empty
case _: HttpCodec.Fallback[_, _, _] => in.alternatives.map(_._1).flatMap(flattenedAtoms(_, annotations))
case HttpCodec.Annotated(api, annotation) =>
flattenedAtoms(api, annotations :+ annotation.asInstanceOf[HttpCodec.Metadata[Any]])
}

def reduceExamplesLeft(
annotations: Chunk[HttpCodec.Metadata[Any]],
combiner: Combiner[_, _],
): Chunk[HttpCodec.Metadata[Any]] =
annotations.map {
case HttpCodec.Metadata.Examples(examples) =>
HttpCodec.Metadata.Examples(examples.map { case (name, value) =>
name -> combiner.separate(value.asInstanceOf[combiner.Out])._1
})
case other =>
other
}

def reduceExamplesRight(
annotations: Chunk[HttpCodec.Metadata[Any]],
combiner: Combiner[_, _],
): Chunk[HttpCodec.Metadata[Any]] =
annotations.map {
case HttpCodec.Metadata.Examples(examples) =>
HttpCodec.Metadata.Examples(examples.map { case (name, value) =>
name -> combiner.separate(value.asInstanceOf[combiner.Out])._2
})
case other =>
other
}
}

private def metaCodecFromSegment(segment: SegmentCodec[_]) = {
Expand Down

0 comments on commit 750e824

Please sign in to comment.