From 20d27fbf7d43905686ae91ed31d2cfc4c09b02b7 Mon Sep 17 00:00:00 2001 From: Naftoli Gugenheim <98384+nafg@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:38:09 -0400 Subject: [PATCH] Fix #3101 Code gen schema import missing (#3153) --- .../zio/http/gen/openapi/EndpointGen.scala | 2 +- ...ndpointWithRequestResponseBodyInline.scala | 1 + ...tWithRequestResponseBodyInlineNested.scala | 1 + ...equestResponseBodyWithKeywordsInline.scala | 1 + .../http/gen/openapi/EndpointGenSpec.scala | 4 +- .../zio/http/gen/scala/CodeGenSpec.scala | 126 +++++++++++++++++- 6 files changed, 127 insertions(+), 8 deletions(-) diff --git a/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala b/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala index f22afb38b8..f92bdb144d 100644 --- a/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala +++ b/zio-http-gen/src/main/scala/zio/http/gen/openapi/EndpointGen.scala @@ -449,7 +449,7 @@ final case class EndpointGen(config: Config) { caseClasses = code.caseClasses, enums = code.enums, ) - Nil -> s"$method.${Inline.RequestBodyType}" + code.imports -> s"$method.${Inline.RequestBodyType}" } case OpenAPI.ReferenceOr.Reference(SchemaRef(ref), _, _) => Nil -> ref case other => throw new Exception(s"Unexpected request body schema: $other") diff --git a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInline.scala b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInline.scala index 524fc5c9e4..18592aa56a 100644 --- a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInline.scala +++ b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInline.scala @@ -1,6 +1,7 @@ package test.api.v1 import test.component._ +import zio.schema._ object Users { import zio.http._ diff --git a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineNested.scala b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineNested.scala index cc2dce0223..03a94ca498 100644 --- a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineNested.scala +++ b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyInlineNested.scala @@ -1,6 +1,7 @@ package test.api.v1 import test.component._ +import zio.schema._ object Users { import zio.http._ diff --git a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyWithKeywordsInline.scala b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyWithKeywordsInline.scala index 496f1c9284..0cfa8c3dd3 100644 --- a/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyWithKeywordsInline.scala +++ b/zio-http-gen/src/test/resources/EndpointWithRequestResponseBodyWithKeywordsInline.scala @@ -1,6 +1,7 @@ package test.api.v1 import test.component._ +import zio.schema._ object Keywords { import zio.http._ diff --git a/zio-http-gen/src/test/scala/zio/http/gen/openapi/EndpointGenSpec.scala b/zio-http-gen/src/test/scala/zio/http/gen/openapi/EndpointGenSpec.scala index b0a4bd028d..e445fd7a17 100644 --- a/zio-http-gen/src/test/scala/zio/http/gen/openapi/EndpointGenSpec.scala +++ b/zio-http-gen/src/test/scala/zio/http/gen/openapi/EndpointGenSpec.scala @@ -910,7 +910,7 @@ object EndpointGenSpec extends ZIOSpecDefault { val expected = Code.File( List("api", "v1", "Users.scala"), pkgPath = List("api", "v1"), - imports = List(Code.Import.FromBase(path = "component._")), + imports = List(Code.Import.FromBase(path = "component._"), Code.Import.Absolute("zio.schema._")), objects = List( Code.Object( "Users", @@ -1026,7 +1026,7 @@ object EndpointGenSpec extends ZIOSpecDefault { val expected = Code.File( List("api", "v1", "Users.scala"), pkgPath = List("api", "v1"), - imports = List(Code.Import.FromBase(path = "component._")), + imports = List(Code.Import.FromBase(path = "component._"), Code.Import.Absolute("zio.schema._")), objects = List( Code.Object( "Users", diff --git a/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala b/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala index e9a9e3a940..e65ca03d81 100644 --- a/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala +++ b/zio-http-gen/src/test/scala/zio/http/gen/scala/CodeGenSpec.scala @@ -9,11 +9,11 @@ import scala.meta._ import scala.meta.parsers._ import scala.util.{Failure, Success, Try} -import zio.Scope import zio.json.JsonDecoder import zio.test.Assertion.{hasSameElements, isFailure, isSuccess} import zio.test.TestAspect.{blocking, flaky} import zio.test._ +import zio.{Chunk, Scope} import zio.schema.annotation.validate import zio.schema.codec.JsonCodec @@ -23,7 +23,7 @@ import zio.schema.{DeriveSchema, Schema} import zio.http._ import zio.http.codec._ import zio.http.endpoint.Endpoint -import zio.http.endpoint.openapi.{OpenAPI, OpenAPIGen} +import zio.http.endpoint.openapi.{JsonSchema, OpenAPI, OpenAPIGen} import zio.http.gen.model._ import zio.http.gen.openapi.Config.NormalizeFields import zio.http.gen.openapi.{Config, EndpointGen} @@ -446,7 +446,8 @@ object CodeGenSpec extends ZIOSpecDefault { } } @@ TestAspect.exceptScala3, // for some reason, the temp dir is empty in Scala 3 test( - "OpenAPI spec with inline schema response body of sum-type with multiple contradicting reusable fields and super type members", + "OpenAPI spec with inline schema response body of sum-type with multiple contradicting reusable fields and " + + "super type members", ) { val openAPIString = stringFromResource("/inline_schema_sumtype_with_multiple_contradicting_reusable_fields.yaml") @@ -458,7 +459,8 @@ object CodeGenSpec extends ZIOSpecDefault { } } @@ TestAspect.exceptScala3, // for some reason, the temp dir is empty in Scala 3 test( - "OpenAPI spec with inline schema response body of sum-type with multiple non-contradicting reusable fields and super type members", + "OpenAPI spec with inline schema response body of sum-type with multiple non-contradicting reusable fields " + + "and super type members", ) { val openAPIString = stringFromResource("/inline_schema_sumtype_with_multiple_non_contradicting_reusable_fields.yaml") @@ -929,7 +931,8 @@ object CodeGenSpec extends ZIOSpecDefault { assertTrue( Try( codeGenFromOpenAPI(oapi)(_ => TestResult(TestArrow.succeed(true))), - ).failed.get.getMessage == "x-string-key-schema must reference a string schema, but got: {\"type\":\"integer\",\"format\":\"int32\"}", + ).failed.get.getMessage == "x-string-key-schema must reference a string schema, but " + + "got: {\"type\":\"integer\",\"format\":\"int32\"}", ) } }, @@ -1043,5 +1046,118 @@ object CodeGenSpec extends ZIOSpecDefault { maybeEndpointCode.is(_.some).errorsCode.length == 2, ) }, + test("schema import is added when needed") { + val oapi = + OpenAPI( + openapi = "3.0.0", + info = OpenAPI.Info( + title = "XXX", + description = None, + termsOfService = None, + contact = None, + license = None, + version = "1.0.0", + ), + paths = ListMap( + OpenAPI.Path + .fromString(name = "/api/a/b") + .map { path => + path -> OpenAPI.PathItem( + ref = None, + summary = None, + description = None, + get = None, + put = None, + post = Some( + OpenAPI.Operation( + summary = None, + description = None, + externalDocs = None, + operationId = None, + requestBody = Some( + OpenAPI.ReferenceOr.Or( + OpenAPI.RequestBody( + content = Map( + "application/json" -> OpenAPI.MediaType( + OpenAPI.ReferenceOr.Or( + JsonSchema.Object( + properties = Map( + "h" -> JsonSchema.String(None, None), + "i" -> JsonSchema.String(None, None), + "j" -> JsonSchema.String(None, None), + "k" -> JsonSchema.String(None, None), + ), + additionalProperties = Left(true), + required = Chunk("h", "i", "j", "k"), + ), + ), + ), + ), + ), + ), + ), + ), + ), + delete = None, + options = None, + head = None, + patch = None, + trace = None, + ) + } + .toSeq: _*, + ), + components = None, + externalDocs = None, + ) + + def suspendAssertion[A](assertion: => Assertion[A]) = + Assertion( + TestArrow.suspend((a: A) => TestArrow.succeed(a) >>> assertion.arrow), + ) + + val importsZioSchema: Assertion[Code.File] = + Assertion.hasField("imports", (_: Code.File).imports, Assertion.contains(Code.Import("zio.schema._"))) + val objectsOwnSchemaIsNone = Assertion.hasField("schema", (_: Code.Object).schema, Assertion.isNone) + lazy val objectCaseClassesContainNoSchema: Assertion[Code.Object] = + Assertion.hasField( + "caseClasses", + (_: Code.Object).caseClasses, + Assertion.forall(caseClassCompanionsContainNoSchema), + ) + lazy val caseClassCompanionsContainNoSchema: Assertion[Code.CaseClass] = + Assertion.hasField( + "companionObject", + (_: Code.CaseClass).companionObject, + Assertion.isNone || Assertion.isSome(objectContainsNoSchema), + ) + lazy val objectObjectsContainNoSchema: Assertion[Code.Object] = + Assertion.hasField( + "objects", + (_: Code.Object).objects, + Assertion.forall(objectContainsNoSchema), + ) + lazy val objectContainsNoSchema: Assertion[Code.Object] = + suspendAssertion { + objectsOwnSchemaIsNone && + objectObjectsContainNoSchema && + objectCaseClassesContainNoSchema + } + val fileObjectsContainNoSchema: Assertion[Code.File] = + Assertion.hasField("objects", (_: Code.File).objects, Assertion.forall(objectContainsNoSchema)) + val fileCaseClassCompanionsContainNoSchema = + Assertion.hasField( + "caseClasses", + (_: Code.File).caseClasses, + Assertion.forall(caseClassCompanionsContainNoSchema), + ) + val fileContainsNoSchema: Assertion[Code.File] = + fileObjectsContainNoSchema && + fileCaseClassCompanionsContainNoSchema + + assert(EndpointGen.fromOpenAPI(oapi, Config.default).files) { + Assertion.forall(importsZioSchema || fileContainsNoSchema) + } + }, ) @@ java11OrNewer @@ flaky @@ blocking // Downloading scalafmt on CI is flaky }