From e81795794a0bcfc87ba6a575023d263c933be7a3 Mon Sep 17 00:00:00 2001 From: Jeff Lewis Date: Tue, 16 Jul 2024 15:58:57 -0600 Subject: [PATCH 1/2] fix for discriminated union transform --- .../alloy/openapi/AlloyOpenApiExtension.scala | 3 +- .../DiscriminatedUnionMemberComponents.scala | 38 +++++++---- .../openapi/DiscriminatedUnionShapeId.scala | 44 ++++++++++++ modules/openapi/test/resources/bar.json | 67 +++++++++++++++++++ modules/openapi/test/resources/bar.smithy | 22 ++++++ .../alloy/openapi/OpenApiConversionSpec.scala | 24 +++++++ 6 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 modules/openapi/src/alloy/openapi/DiscriminatedUnionShapeId.scala create mode 100644 modules/openapi/test/resources/bar.json create mode 100644 modules/openapi/test/resources/bar.smithy diff --git a/modules/openapi/src/alloy/openapi/AlloyOpenApiExtension.scala b/modules/openapi/src/alloy/openapi/AlloyOpenApiExtension.scala index 452c038..2473deb 100644 --- a/modules/openapi/src/alloy/openapi/AlloyOpenApiExtension.scala +++ b/modules/openapi/src/alloy/openapi/AlloyOpenApiExtension.scala @@ -49,7 +49,8 @@ final class AlloyOpenApiExtension() extends Smithy2OpenApiExtension { new UntaggedUnions(), new DataExamplesMapper(), new ExternalDocumentationMapperJsonSchema(), - new NullableMapper() + new NullableMapper(), + new DiscriminatedUnionShapeId() ).asJava } diff --git a/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala b/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala index 2680996..c1a1128 100644 --- a/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala +++ b/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala @@ -40,11 +40,20 @@ class DiscriminatedUnionMemberComponents() extends OpenApiMapper { .getModel() .getUnionShapesWithTrait(classOf[DiscriminatedUnionTrait]) val componentBuilder = openapi.getComponents().toBuilder() - val componentSchemas: Map[String, Schema] = openapi + val componentSchemas: Map[ShapeId, Schema] = openapi .getComponents() .getSchemas() .asScala .toMap + .flatMap { case (_, schema) => + schema + .getExtension(DiscriminatedUnionShapeId.SHAPE_ID_KEY) + .asScala + .flatMap { node => + node.toNode.asStringNode.asScala + .map(s => ShapeId.from(s.getValue) -> schema) + } + } unions.asScala.foreach { union => val unionMixinName = union.getId().getName() + "Mixin" val unionMixinId = @@ -82,19 +91,19 @@ class DiscriminatedUnionMemberComponents() extends OpenApiMapper { componentBuilder.putSchema(syntheticMemberName, syntheticUnionMember) } - val existingSchemaBuilder = componentSchemas - .get(union.toShapeId.getName) - .map(_.toBuilder()) - .getOrElse(Schema.builder()) - componentBuilder.putSchema( - union.toShapeId.getName, - updateDiscriminatedUnion( - union, - existingSchemaBuilder, - discriminatorField - ) - .build() - ) + componentSchemas.get(union.toShapeId).foreach { sch => + if (!sch.getOneOf.isEmpty) { + componentBuilder.putSchema( + union.toShapeId.getName, + updateDiscriminatedUnion( + union, + sch.toBuilder(), + discriminatorField + ) + .build() + ) + } + } } openapi.toBuilder.components(componentBuilder.build()).build() @@ -130,6 +139,7 @@ class DiscriminatedUnionMemberComponents() extends OpenApiMapper { .asJava ) schemaBuilder + .removeExtension(DiscriminatedUnionShapeId.SHAPE_ID_KEY) .oneOf(schemas) .putExtension( "discriminator", diff --git a/modules/openapi/src/alloy/openapi/DiscriminatedUnionShapeId.scala b/modules/openapi/src/alloy/openapi/DiscriminatedUnionShapeId.scala new file mode 100644 index 0000000..ac10fbc --- /dev/null +++ b/modules/openapi/src/alloy/openapi/DiscriminatedUnionShapeId.scala @@ -0,0 +1,44 @@ +/* Copyright 2022 Disney Streaming + * + * Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://disneystreaming.github.io/TOST-1.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package alloy.openapi + +import _root_.software.amazon.smithy.jsonschema.JsonSchemaConfig +import _root_.software.amazon.smithy.jsonschema.JsonSchemaMapper +import _root_.software.amazon.smithy.jsonschema.Schema.Builder +import _root_.software.amazon.smithy.model.shapes.Shape +import alloy.DiscriminatedUnionTrait + +import software.amazon.smithy.model.node.Node + +class DiscriminatedUnionShapeId() extends JsonSchemaMapper { + + import DiscriminatedUnionShapeId._ + + override def updateSchema( + shape: Shape, + schemaBuilder: Builder, + config: JsonSchemaConfig + ): Builder = if (shape.hasTrait(classOf[DiscriminatedUnionTrait])) { + schemaBuilder.putExtension( + SHAPE_ID_KEY, + Node.from(shape.toShapeId.toString) + ) + } else schemaBuilder +} + +object DiscriminatedUnionShapeId { + private[openapi] val SHAPE_ID_KEY: String = "SHAPE_ID" +} diff --git a/modules/openapi/test/resources/bar.json b/modules/openapi/test/resources/bar.json new file mode 100644 index 0000000..ea01903 --- /dev/null +++ b/modules/openapi/test/resources/bar.json @@ -0,0 +1,67 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "BarService", + "version": "" + }, + "paths": { + "/bar": { + "get": { + "operationId": "BarOp", + "responses": { + "200": { + "description": "BarOp200response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BarOpResponseContent" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "BarOpResponseContent": { + "type": "object", + "properties": { + "out": { + "$ref": "#/components/schemas/CatOrDog" + } + } + }, + "CatOrDog": { + "oneOf": [ + { + "type": "object", + "title": "one", + "properties": { + "one": { + "type": "string" + } + }, + "required": [ + "one" + ] + }, + { + "type": "object", + "title": "two", + "properties": { + "two": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "two" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/modules/openapi/test/resources/bar.smithy b/modules/openapi/test/resources/bar.smithy new file mode 100644 index 0000000..ca806c9 --- /dev/null +++ b/modules/openapi/test/resources/bar.smithy @@ -0,0 +1,22 @@ +$version: "2" + +namespace bar + +use alloy#simpleRestJson + +@simpleRestJson +service BarService { + operations: [BarOp] +} + +@http(method: "GET", uri: "/bar") +operation BarOp { + output := { + out: CatOrDog + } +} + +union CatOrDog { + one: String + two: Integer +} diff --git a/modules/openapi/test/src/alloy/openapi/OpenApiConversionSpec.scala b/modules/openapi/test/src/alloy/openapi/OpenApiConversionSpec.scala index 2c83b43..c159519 100644 --- a/modules/openapi/test/src/alloy/openapi/OpenApiConversionSpec.scala +++ b/modules/openapi/test/src/alloy/openapi/OpenApiConversionSpec.scala @@ -48,6 +48,30 @@ final class OpenApiConversionSpec extends munit.FunSuite { assertEquals(result, expected) } + test( + "OpenAPI conversion from alloy#simpleRestJson protocol with multiple namespaces" + ) { + val model = Model + .assembler() + .addImport(getClass().getClassLoader().getResource("foo.smithy")) + .addImport(getClass().getClassLoader().getResource("bar.smithy")) + .discoverModels() + .assemble() + .unwrap() + + val result = convert(model, Some(Set("bar"))) + .map(_.contents) + .mkString + .filterNot(_.isWhitespace) + + val expected = Using + .resource(Source.fromResource("bar.json"))( + _.getLines().mkString.filterNot(_.isWhitespace) + ) + + assertEquals(result, expected) + } + test("OpenAPI conversion from testJson protocol") { val model = Model .assembler() From 52dbd74b059a86236a765080908abb9e9efe84c3 Mon Sep 17 00:00:00 2001 From: Jeff Lewis Date: Tue, 16 Jul 2024 16:06:58 -0600 Subject: [PATCH 2/2] remove extra if statement --- .../DiscriminatedUnionMemberComponents.scala | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala b/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala index c1a1128..e19e8d6 100644 --- a/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala +++ b/modules/openapi/src/alloy/openapi/DiscriminatedUnionMemberComponents.scala @@ -92,17 +92,15 @@ class DiscriminatedUnionMemberComponents() extends OpenApiMapper { } componentSchemas.get(union.toShapeId).foreach { sch => - if (!sch.getOneOf.isEmpty) { - componentBuilder.putSchema( - union.toShapeId.getName, - updateDiscriminatedUnion( - union, - sch.toBuilder(), - discriminatorField - ) - .build() + componentBuilder.putSchema( + union.toShapeId.getName, + updateDiscriminatedUnion( + union, + sch.toBuilder(), + discriminatorField ) - } + .build() + ) } }