Skip to content

Commit

Permalink
Merge pull request #179 from disneystreaming/issue-107
Browse files Browse the repository at this point in the history
Translate anyOfs to mixins where possible
  • Loading branch information
lewisjkl authored Sep 5, 2023
2 parents b1b0831 + 2b2d13c commit bacaac2
Show file tree
Hide file tree
Showing 11 changed files with 740 additions and 230 deletions.
134 changes: 134 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ _Note: this library is published to work on Java 8 and above. However, you will
- [Primitives](#primitives)
- [Aggregate Shapes](#aggregate-shapes)
- [Structure](#structure)
- [Structures with Mixins](#structures-with-mixins)
- [Untagged Union](#untagged-union)
- [Tagged Union](#tagged-union)
- [Discriminated Union](#discriminated-union)
Expand Down Expand Up @@ -235,6 +236,139 @@ structure Testing {
}
```

##### Structures with Mixins

Smithy Translate will convert allOfs from OpenAPI into structures with mixins in smithy where possible. AllOfs in OpenAPI have references to other types which compose the current type. We refer to these as "parents" or "parent types" below. There are three possibilities when converting allOfs to smithy shapes:

1. The parent structures are only ever used as mixins

OpenAPI:
```yaml
openapi: '3.0.'
info:
title: doc
version: 1.0.0
paths: {}
components:
schemas:
One:
type: object
properties:
one:
type: string
Two:
type: object
properties:
two:
type: string
Three:
type: object
allOf:
- $ref: "#/components/schemas/One"
- $ref: "#/components/schemas/Two"
```
Smithy:
```smithy
@mixin
structure One {
one: String
}

@mixin
structure Two {
two: String
}

structure Three with [One, Two] {}
```

Here we can see that both parents, `One` and `Two` are converted into mixins and used as such on `Three`.

2. The parents structures are used as mixins and referenced as member targets

OpenAPI:
```yaml
openapi: '3.0.'
info:
title: doc
version: 1.0.0
paths: {}
components:
schemas:
One:
type: object
properties:
one:
type: string
Two:
type: object
allOf:
- $ref: "#/components/schemas/One"
Three:
type: object
properties:
one:
$ref: "#/components/schemas/One"
```
Smithy:
```smithy
@mixin
structure OneMixin {
one: String
}

structure One with [OneMixin] {}

structure Two with [OneMixin] {}

structure Three {
one: One
}
```

Here `One` is used as a target of the `Three$one` member and is used as a mixin in the `Two` structure. Since smithy does not allow mixins to be used as targets, we have to create a separate mixin shape, `OneMixin` which is used as a mixin for `One` which is ultimately what we use for the target in `Three`.

3. One of the parents is a document rather than a structure

OpenAPI:
```yaml
openapi: '3.0.'
info:
title: doc
version: 1.0.0
paths: {}
components:
schemas:
One:
type: object
properties: {}
Two:
type: object
properties:
two:
type: string
Three:
type: object
allOf:
- $ref: "#/components/schemas/One"
- $ref: "#/components/schemas/Two"
```
Smithy:
```smithy
document One

structure Two {
two: String
}

document Three
```

In this case, no mixins are created since none are ultimately used. Since `One` is translated to a document, `Three` must also be a document since it has `One` as a parent shape. As such, `Two` is never used as a mixin.

##### Untagged Union

The majority of `oneOf` schemas in OpenAPI represent untagged unions.
Expand Down
7 changes: 6 additions & 1 deletion modules/json-schema/src/internals/JsonSchemaToIModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,12 @@ private class JsonSchemaToIModel[F[_]: Parallel: TellShape: TellError](
schema
)
}
F.pure(OpenApiObject(local.context.addHints(hints), fields))
F.pure(
OpenApiObject(
local.context.addHints(hints, retainTopLevel = true),
fields
)
)

case Extractors.CaseArray(hints, itemSchema) =>
F.pure(
Expand Down
12 changes: 8 additions & 4 deletions modules/json-schema/tests/src/AllOfSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,16 @@ final class AllOfSpec extends munit.FunSuite {

val expectedString = """|namespace foo
|
|structure Example {
|structure Example with [Two] {
| firstName: String,
| lastName: String,
| sport: String,
| vehicle: String,
| price: Integer
| sport: String
|}
|
|@mixin
|structure Two {
| vehicle: String,
| price: Integer
|}
|
|structure Test {
Expand Down
2 changes: 2 additions & 0 deletions modules/openapi/src/internals/Hint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ object Hint {
case class JsonName(name: String) extends Hint
case class Examples(value: List[Node]) extends Hint
case class ExternalDocs(description: Option[String], url: String) extends Hint
case object IsMixin extends Hint
case class HasMixin(id: DefId) extends Hint
}
// format: on
7 changes: 7 additions & 0 deletions modules/openapi/src/internals/IModelToSmithy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,17 @@ final class IModelToSmithy(useEnumTraitSyntax: Boolean)
.addHints(hints ++ jsonNameHint)
.build()
}
val mixins = structHints.collect { case Hint.HasMixin(defId) =>
StructureShape.builder.id(defId.toSmithy).build()
}.asJava
val builder = StructureShape
.builder()
.id(id.toSmithy)
.addHints(structHints)
members.foreach(builder.addMember(_))
mixins.forEach { m =>
val _ = builder.addMixin(m)
}
builder.build()
case MapDef(id, key, value, hints) =>
MapShape
Expand Down Expand Up @@ -306,6 +312,7 @@ final class IModelToSmithy(useEnumTraitSyntax: Boolean)
case Hint.UniqueItems => List(new UniqueItemsTrait())
case Hint.Nullable => List(new NullableTrait())
case Hint.JsonName(value) => List(new JsonNameTrait(value))
case Hint.IsMixin => List(MixinTrait.builder.build)
case Hint.ExternalDocs(desc, url) =>
List(
ExternalDocumentationTrait
Expand Down
Loading

0 comments on commit bacaac2

Please sign in to comment.