Skip to content

Commit

Permalink
Merge branch 'main' into define-unknown-field-retention-trait
Browse files Browse the repository at this point in the history
  • Loading branch information
dhpiggott committed Mar 6, 2024
2 parents 706e862 + d313f52 commit 185b0bd
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ It contains, in particular, traits and validators associated to the following as

### Constraints and behavioural traits

Alloy provides a number of [constraint and behavioural](./docs/misc/behaviour.md) traits that may be leverage by tooling and protocols.
Alloy provides a number of [constraint and behavioural](./docs/misc/constraints.md) traits that may be leverage by tooling and protocols.

### Serialisation

Expand Down
12 changes: 5 additions & 7 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ trait BaseScalaNoPublishModule
extends ScalaModule
with ScalafmtModule
with TpolecatModule {
def scalaVersion = T.input("2.13.12")
def scalaVersion = T.input("2.13.13")
}

trait BaseMimaModule extends BasePublishModule with Mima {
Expand Down Expand Up @@ -141,9 +141,7 @@ object core extends BaseJavaModule {
}
}

object protobuf extends BaseJavaModule {

}
object protobuf extends BaseJavaModule {}

val scalaVersionsMap =
Map("2.13" -> "2.13.7", "2.12" -> "2.12.17", "3" -> "3.3.0")
Expand Down Expand Up @@ -189,7 +187,7 @@ object `protocol-tests` extends BaseJavaModule {

object Deps {
val smithy = new {
val smithyVersion = "1.43.0"
val smithyVersion = "1.45.0"
val model = ivy"software.amazon.smithy:smithy-model:$smithyVersion"
val awsTraits = ivy"software.amazon.smithy:smithy-aws-traits:$smithyVersion"
val awsProtocolTestTraits =
Expand All @@ -208,8 +206,8 @@ object Deps {
}

val munit = new {
val munit = ivy"org.scalameta::munit::1.0.0-M10"
val scalaCheck = ivy"org.scalameta::munit-scalacheck::1.0.0-M10"
val munit = ivy"org.scalameta::munit::1.0.0-M11"
val scalaCheck = ivy"org.scalameta::munit-scalacheck::1.0.0-M11"
val all = Agg(munit, scalaCheck)
}
}
2 changes: 2 additions & 0 deletions docs/protocols/SimpleRestJson.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The `alloy#simpleRestJson` protocol is a REST protocol in which all HTTP bodies
- alloy#uuidFormat
- alloy#discriminated
- alloy#untagged
- alloy#nullable

#### Protocol Behavior and Semantics

Expand Down Expand Up @@ -120,3 +121,4 @@ Furthermore, implementors of the protocol have to take into consideration additi
- `alloy#untagged`
- `alloy#discriminated`
- `alloy#uuidFormat`
- `alloy#nullable`
42 changes: 42 additions & 0 deletions docs/serialisation/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,45 @@ are encoded as such
{ "tpe": "first", "myString": "alloy" }
{ "tpe": "second", "myInt": 42 }
```

### Null values

The standard Smithy toolset does not provide any semantics for distinguishing between a JSON field being set to `null` and the same field being absent from its carrying JSON object. However, depending on the use-case, the difference can be meaningful. In order to support such use-cases, the additional trait `alloy.nullable` is provided. Annotating the member of a structure field with this indicates that a value serialised to `null` was a conscious decision (as opposed to omitting the value altogether), and that deserialisation should retain this information.

For example, assuming the following smithy structure

```smithy
use alloy#nullable
structure Foo {
@nullable
nullable: Integer
regular: Integer
}
```

The JSON objects

```json
{ "nullable": null, "regular": null }
{ "nullable": 4, "regular": 4 }
{}
```

are respectively decoded as follows in Scala (when using [smithy4s](https://disneystreaming.github.io/smithy4s/)):

```scala
Foo(Some(Nullable.Null), None)
Foo(Some(Nullable.Value(4)), Some(4))
Foo(None, None)
```

or some similar type which preserves the information that an explicit `null` was passed. These objects are in turn encoded as

```json
{ "nullable": null }
{ "nullable": 4, "regular": 4 }
{}
```

This means that `@nullable` allows round-tripping null values.
48 changes: 26 additions & 22 deletions modules/core/resources/META-INF/smithy/restjson.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@ namespace alloy
/// in HTTP requests and responses. These are encoded with
/// the content type `application/json`.
/// See Alloy documentation for more information.
@protocolDefinition(traits: [
smithy.api#default
smithy.api#error
smithy.api#http
smithy.api#httpError
smithy.api#httpHeader
smithy.api#httpLabel
smithy.api#httpPayload
smithy.api#httpPrefixHeaders
smithy.api#httpQuery
smithy.api#httpQueryParams
smithy.api#httpResponseCode
smithy.api#jsonName
smithy.api#length
smithy.api#pattern
smithy.api#range
smithy.api#required
smithy.api#timestampFormat
alloy#uuidFormat
alloy#discriminated
alloy#untagged
])
@protocolDefinition(
traits: [

smithy.api#default
smithy.api#error
smithy.api#http
smithy.api#httpError
smithy.api#httpHeader
smithy.api#httpLabel
smithy.api#httpPayload
smithy.api#httpPrefixHeaders
smithy.api#httpQuery
smithy.api#httpQueryParams
smithy.api#httpResponseCode
smithy.api#jsonName
smithy.api#length
smithy.api#pattern
smithy.api#range
smithy.api#required
smithy.api#timestampFormat
alloy#uuidFormat
alloy#discriminated
alloy#nullable
alloy#untagged
]
)
@trait(selector: "service")
structure simpleRestJson {}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ final class AlloyOpenApiExtension() extends Smithy2OpenApiExtension {
new DiscriminatedUnions(),
new UntaggedUnions(),
new DataExamplesMapper(),
new ExternalDocumentationMapperJsonSchema()
new ExternalDocumentationMapperJsonSchema(),
new NullableMapper()
).asJava

}
42 changes: 42 additions & 0 deletions modules/openapi/src/alloy/openapi/NullableMapper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* 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 alloy.NullableTrait
import software.amazon.smithy.jsonschema.{
JsonSchemaMapper,
JsonSchemaMapperContext,
Schema
}
import software.amazon.smithy.model.node.Node

class NullableMapper extends JsonSchemaMapper {
override def updateSchema(
context: JsonSchemaMapperContext,
schemaBuilder: Schema.Builder
): Schema.Builder = {
context.getShape
.getTrait(classOf[NullableTrait])
.asScala
.map { _ =>
schemaBuilder.putExtension(
"nullable",
Node.from(true)
)
}
.getOrElse(schemaBuilder)
}
}
5 changes: 5 additions & 0 deletions modules/openapi/test/resources/foo.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@
"properties": {
"message": {
"type": "string"
},
"count": {
"type": "integer",
"format": "int32",
"nullable": true
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions modules/openapi/test/resources/foo.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace foo

use alloy#simpleRestJson
use alloy#discriminated
use alloy#nullable
use alloy#untagged
use alloy#dataExamples

Expand Down Expand Up @@ -80,6 +81,9 @@ structure Greeting {
@httpError(500)
structure GeneralServerError {
message: String,

@nullable
count: Integer
}

structure GetUnionResponse {
Expand Down

0 comments on commit 185b0bd

Please sign in to comment.