The SimpleRestJson protocol
Smithy4s provides a custom Json-in/Json-out protocol that smithy services can be annotated with.
Smithy4s comes with opt-in http4s-specific module, that contains functions that are aware of this protocol, and can be used to quickly derive http services and clients.
As for the json aspect of the protocol, jsoniter-scala is used for the (de)serialisation of the http bodies.
Semantics
In this protocol, the values in shapes are bound to http metadata or body according to the specification of the Http Binding traits. However, the @mediaType
trait has no incidence, and all bodies (when present) are serialised in JSON.
Example spec
namespace smithy4s.example
use alloy#simpleRestJson
@simpleRestJson
service HelloWorldService {
version: "1.0.0"
// Indicates that all operations in `HelloWorldService`,
// here limited to the Hello operation, can return `GenericServerError`.
errors: [GenericServerError]
operations: [Hello]
}
@error("server")
@httpError(500)
structure GenericServerError {
message: String
}
@http(method: "POST", uri: "/{name}", code: 200)
operation Hello {
input: Person
output: Greeting
}
structure Person {
@httpLabel
@required
name: String
@httpQuery("town")
town: String
}
structure Greeting {
@required
message: String
}
Supported traits
This protocol and its interpreters, are aware of the following traits provided out of the box:
- all simple shapes
- composite data shapes, including collections, unions, structures.
- operations and services
- enumerations
- error trait
- http traits
- timestampFormat trait
For the full list, see below.
Decoding and encoding unions
The SimpleRestJson
protocol supports 3 different union encodings :
- tagged (default)
- untagged
- discriminated
See the section about unions for a detailed description.
Json Array Arity
- By default there is a limit on the arity of an array, which is 1024. This is to prevent the server from being overloaded with a large array as this is a vector for attacks.
- This limit can be changed by setting the maxArity
smithy4s.http4s.SimpleRestJsonBuilder.withMaxArity(.)
to the desired value. - an example can be seen in the client example
Explicit Null Encoding
By default, optional properties (headers, query parameters, structure fields) that are set to None
and optional properties that are set to default value will be excluded during encoding process. If you wish to change this so that instead they are included and set to null
explicitly, you can do so by calling .withExplicitDefaultsEncoding(true)
.
Other customisations of JSON codec behaviour
The underlying JSON codecs can be configured with a number of options to cater to niche usecases, via the .transformJsonCodecs
method, which takes a function that takes in and returns a
JsonPayloadCodecCompiler
. For instance, by default, the NaN
and Infinity
values are not considered valid during parsing Float
or Double
values. This can be amended via
.transformJsonCodecs(_.configureJsoniterCodecCompiler(_.withInfinitySupport(true)))
.
The customisations are bound to evolve as we uncover new niche cases that warrant adding new pieces of opt-in behaviour. The default behaviour is kept rather strict as it helps keep competitive performance and safety.
Supported traits
Here is the list of traits supported by SimpleRestJson
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
alloy#jsonUnknown
Currently, @cors
is not supported. This is because the @cors
annotation is too restrictive. You can still use it in your model and configure your API using the information found in the generated code. See the Cors.scala
file in the guides
module for an example.
Structured Strings
As of smithy4s version 0.18.x
, you are able to create strings which are parsed directly into structures for you. This can be accomplished using the alloy#structurePattern
trait. For example:
@structurePattern(pattern: "{foo}_{bar}", target: FooBar)
string FooBarString
structure FooBar {
@required
foo: String
@required
bar: Integer
}
Now wherever FooBarString
is used, it will really be parsing the string into the structure FooBar
. As such, the generated code will replace instances of FooBarString
with FooBar
such that the parsing logic is abstracted away from your implementation. See the alloy documentation for more information.