A collection of commonly used Smithy shapes.
Alloy Smithy shapes and validators are published to Maven Central under the following artifact names:
"com.disneystreaming.alloy:alloy-core:x.x.x"
"com.disneystreaming.alloy:alloy-openapi:x.x.x"
Alloy was created to unify the Smithy shapes that we use across our projects, including for example smithy4s
and smithy-translate
. Having the shapes defined in one spot means that we can use them everywhere and our tooling will interop seamlessly.
Alloy currently includes shapes related to the following two protocols:
alloy#simpleRestJson
alloy#grpc
That being said, you can use the shapes in Alloy without using these protocols if you want to customize your protocol differently from what we have defined here.
This is the protocol that was formerly known as smithy4s.api#simpleRestJson
.
All operations referenced by a alloy#simpleRestJson
service must be annotated with the http trait.
Errors referenced by any operation that's itself referenced by a @simpleRestJson
service should be uniquely annotated by a status code (within the context of that operation), using the @httpError
trait. It means that two errors referenced by a same operation cannot have the same statusCode
. If several error shapes can be raised using a single statusCode
, a union
should be used to represent the alternatives.
The alloy#simpleRestJson
protocol uses a default Content-Type of application/json
.
Input or output shapes that apply the @httpPayload
trait on one of their top-level members MUST use a Content-Type that is appropriate for the payload. The following table defines the expected Content-Type header for requests and responses based on the shape targeted by the member marked with the @httpPayload
trait:
Targeted shape | Content-Type |
---|---|
Has mediaType trait | Use the value of the mediaType trait if present. |
string | text/plain |
blob | application/octet-stream |
document | application/json |
structure | application/json |
union | application/json |
list/set/map | application/json |
Smithy type | traits | Json format | Example |
---|---|---|---|
blob | Json string value, base64 encoded | ImhlbGxvIg== |
|
boolean | Json boolean | true | |
byte | Json number | 1 | |
short | Json number | 1 | |
integer | Json number | 1 | |
long | Json number | 1 | |
float | Json number | 1.1 | |
double | Json number | 1.1 | |
bigDecimal | Json number | 111111 | |
bigInteger | Json number | 111111 | |
string | Json string | "hello" | |
timestamp | (none, or @timestampFormat("date-time") ) |
Json string, following the date-time section of RFC3339, section 5.6 | 1985-04-12T23:20:50.52Z |
timestamp | @timestampFormat("http-date") |
Json string, following the IMF-fixdate section of RFC 7231 |
Sun, 02 Jan 2000 20:34:56.000 GMT |
timestamp | @timestamp |
Json number, following Unix-time semantics, with optional fractional precision | 1515531081.1234 |
document | Json value (arbitrary shape) | [{"a": "b"}] | |
list | Json array | [1,2,2,3] | |
set | Json array (with unique values) | [1, 2, 3] | |
map | Json object | {"a" : 1, "b" : 2} | |
structure | Json object. Each member of the structure translates to a Json property when the name of the property is the same as the member name, unless that member is annotated with the jsonName. Members that are not annotated with the required trait can be omitted, or set to null to indicate an absence of value. Otherwise, the property values must be set |
{"int": 1, "str": "hello"} | |
union | Same as structures, except that only a single member can be set to a non-null value. | {"foo": {"int": 1, "str": "hello" } | |
union | @discriminated("type") | Same as unions, except a discriminator field is included. This field specifies which branch of the union is included in the encoded JSON. All member shapes in a discriminated union must be structures. | {"type": "foo","int": 1, "str": "hello"} |
union | @untagged | Same as structure, but the encode/decoding logic does not know what to deserialize to. untagged is not recommended. It is the least efficient approach, it's available to support existing APIs. |
{"int": 1, "str": "hello"} |
The alloy#simpleRestJson
protocol supports all of the HTTP binding traits defined in smithy's HTTP protocol
bindings specification.
The serialization formats and and behaviors described for each trait are supported as defined in the
alloy#simpleRestJson
protocol.
Error responses in the simpleRestJson
protocol are serialized identically to successful responses, with the caveat that the status code of the http response should match what is set by the error
and httpError
traits.
@error("client")
structure InvalidInputError {
}
@error("server")
structure UnexpectedServerError {
}
@error("client")
@httpError(403)
structure UnauthorisedError {
}
In the example above, InvalidInputError
should be accompanied by the 400
status code, UnexpectedServer
should be accompanied by the 500
status code, and UnauthorisedError
should be accompanied by the 403
status code.
Because multiple errors can be encoded with the same status code, services implementing this protocol should include an X-Error-Type
header that can be used to discriminate between them. For example, the following error...
@error("client")
@httpError(403)
structure UnauthorisedError {
}
...should be given the header X-Error-Type
with a value of UnauthorisedError
. Clients can use this value to discriminate and provide the correct error to drive needed logic. If this header is not provided, clients will need to make a best-effort assumption about what error is intended using the status code.
This protocol is aware of the following smithy.api
traits provided out of the box:
- all simple shapes
- composite data shapes, including collections, unions, structures.
- operations and services
- enumerations
- error trait
- http traits, including http, httpError, httpLabel, httpHeader, httpPayload, httpQuery, httpPrefixHeaders, httpQueryParams.
- timestampFormat trait
Furthermore, it contains several traits for customizing your APIs.
Unions in this protocol can be encoded in three different ways: tagged, discriminated, and untagged.
By default, the specification of the Smithy language hints that the tagged-union
encoding should be used. This is arguably the best encoding for unions, as it works with members of any type (not just structures), and does not require backtracking during parsing, which makes it more efficient.
However, alloy#simpleRestJson
supports two additional encodings: discriminated
and untagged
, which users can opt-in via the alloy#discriminated
and alloy#untagged
trait, respectively. These are mostly offered as a way to retrofit existing APIs in Smithy.
This is the default behavior, and happens to visually match how Smithy unions are declared. In this encoding, the union is encoded as a JSON object with a single key-value pair, the key signalling which alternative has been encoded.
union Tagged {
first: String
second: IntWrapper
}
structure IntWrapper {
int: Integer
}
The following instances of Tagged
Tagged.FirstCase("alloy")
Tagged.SecondCase(IntWrapper(42)))
are encoded as such :
{ "first": "alloy" }
{ "second": { "int": 42 } }
Untagged unions are supported via an annotation: @untagged
. Despite the smaller payload size this encoding produces, it is arguably the worst way of encoding unions, as it may require backtracking multiple times on the parsing side. Use this carefully, preferably only when you need to retrofit an existing API into Smithy.
use alloy#untagged
@untagged
union Untagged {
first: String
second: IntWrapper
}
structure IntWrapper {
int: Integer
}
The following instances of Untagged
Untagged.FirstCase("alloy")
Untagged.SecondCase(Two(42)))
are encoded as such :
"alloy"
{ "int": 42 }
Discriminated union are supported via an annotation: @discriminated("tpe")
, and work only when all members of the union are structures.
In this encoding, the discriminator is inlined as a JSON field within JSON object resulting from the encoding of the member.
Despite the JSON payload exhibiting less nesting than in the tagged union
encoding, this encoding often leads to bigger payloads, and requires backtracking once during parsing.
use alloy#discriminated
@discriminated("tpe")
union Discriminated {
first: StringWrapper
second: IntWrapper
}
structure StringWrapper {
myString: String
}
structure IntWrapper {
myInt: Integer
}
The following instances of Discriminated
Discriminated.FirstCase(StringWrapper("alloy"))
Discriminated.SecondCase(IntWrapper(42)))
are encoded as such
{ "tpe": "first", "myString": "alloy" }
{ "tpe": "second", "myInt": 42 }
- smithy.api#error
- smithy.api#required
- smithy.api#pattern
- smithy.api#range
- smithy.api#length
- 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#jsonName
- smithy.api#timestampFormat
- alloy#uncheckedExamples
- alloy#uuidFormat
- alloy#discriminated
- alloy#untagged
For full documentation on what each of these traits does, see the smithy specifications here.
This protocol represents the GRPC protocol as defined at grpc.io.
The following shapes are provided as a means of customizing how your Smithy shapes correlate to proto ones.
- alloy.proto#grpc
- alloy.proto#protoIndex
- alloy.proto#protoNumType
- alloy.proto#protoEnabled
- alloy.proto#protoReservedFields
- alloy.proto#uncheckedExamples
Marks an explicit index to be used for a structure member when it is interpreted as protobuf. For example:
structure Test {
str: String
}
Is equivalent to:
message Test {
string str = 1;
}
Where the following:
structure Test {
@protoIndex(2)
str: String
}
Is equivalent to:
message Test {
string str = 2;
}
When one field is annotated with a @protoIndex
, all fields have to be annotated with it. This includes the fields of any structure used within the structure.
Specifies the type of signing that should be used for integers and longs. Options are:
- SIGNED
- UNSIGNED
- FIXED
- FIXED_SIGNED
This trait can be used to enable protobuf conversion on services or structures that are not a part of a GRPC service. This is used, for example, by smithy-translate.
Marks certain field indexes as unusable by the smithy specification. For example, if a range is provided of 1 to 10 then the proto indexes for any fields in that structure must fall outside of that range. Ranges are inclusive.
For full documentation on what each of these traits does, see the smithy specification here.
> ./mill __.publishLocal
> ./mill __.test