18. Smithy IDL#
Smithy models are defined using either the Smithy interface definition language (IDL) or the JSON abstract syntax tree (AST). This document defines the ABNF grammar and syntax for defining models with the Smithy IDL.
18.1. Smithy IDL overview#
The Smithy IDL is made up of 3, ordered sections, each of which is optional:
- Control section; defines parser directives like which version of the IDL to use.
- Metadata section; applies metadata to the entire model.
- Shape section; where shapes and traits are defined. A namespace MUST
be defined before any shapes or traits can be defined.
smithy:UseStatement
s can be defined after a namespace and before shapes or traits to refer to shapes in other namespaces using a shorter name.
The following example defines a model file with each section:
// (1) Control section
$version: "2"
// (2) Metadata section
metadata foo = "bar"
// (3) Shape section
namespace smithy.example
use smithy.other.namespace#MyString
structure MyStructure {
@required
foo: MyString
}
{
"smithy": "2",
"metadata": {
"foo": "bar"
},
"shapes": {
"smithy.example#MyStructure": {
"type": "structure",
"members": {
"foo": {
"target": "smithy.other.namespace#MyString",
"traits": {
"smithy.api#required": {}
}
}
}
}
}
}
18.2. Lexical notes#
- Smithy models MUST be encoded using UTF-8 and SHOULD use Unix style
line endings (
\n
). - The Smithy ABNF is whitespace sensitive.
- Except for within strings, commas in the Smithy IDL are considered whitespace. Commas can be used anywhere where they make the model easier to read (for example, in complex traits defined on a single line).
18.3. Smithy IDL ABNF#
The Smithy IDL is defined by the following ABNF which uses case-sensitive string support defined in RFC 7405.
idl = [WS
]ControlSection
MetadataSection
ShapeSection
Whitespace
WS = 1*(SP
/NL
/Comment
/Comma
) ; whitespace Comma = "," SP = 1*(%x20 / %x09) ; one or more spaces or tabs NL = %x0A / %x0D.0A ; Newline: \n and \r\n NotNL = %x09 / %x20-10FFFF ; Any character except newline BR = [SP
] 1*(Comment
/NL
) [WS
]; line break followed by whitespace
Comments
Comment =DocumentationComment
/LineComment
DocumentationComment = "///" *NotNL
NL
LineComment = "//" [(%x09 / %x20-2E / %x30-10FFF) *NotNL
]NL
; First character after "//" can't be "/"
Control
ControlSection = *(ControlStatement
) ControlStatement = "$"NodeObjectKey
[SP
] ":" [SP
]NodeValue
BR
Metadata
MetadataSection = *(MetadataStatement
) MetadataStatement = %s"metadata"SP
NodeObjectKey
[SP
] "=" [SP
]NodeValue
BR
Node values
NodeValue =NodeArray
/NodeObject
/Number
/NodeKeyword
/NodeStringValue
NodeArray = "[" [WS
] *(NodeValue
[WS
]) "]" NodeObject = "{" [WS
] [NodeObjectKvp
*(WS
NodeObjectKvp
)] [WS
] "}" NodeObjectKvp =NodeObjectKey
[WS
] ":" [WS
]NodeValue
NodeObjectKey =QuotedText
/Identifier
Number = [Minus
]Int
[Frac
] [Exp
] DecimalPoint = %x2E ; . DigitOneToNine = %x31-39 ; 1-9 E = %x65 / %x45 ; e E Exp =E
[Minus
/Plus
] 1*DIGIT Frac =DecimalPoint
1*DIGIT Int =Zero
/ (DigitOneToNine
*DIGIT) Minus = %x2D ; - Plus = %x2B ; + Zero = %x30 ; 0 NodeKeyword = %s"true" / %s"false" / %s"null" NodeStringValue =ShapeId
/TextBlock
/QuotedText
QuotedText = DQUOTE *QuotedChar
DQUOTE QuotedChar = %x09 ; tab / %x20-21 ; space - "!" / %x23-5B ; "#" - "[" / %x5D-10FFFF ; "]"+ /EscapedChar
/NL
EscapedChar =Escape
(Escape
/ DQUOTE / %s"b" / %s"f" / %s"n" / %s"r" / %s"t" / "/" /UnicodeEscape
) UnicodeEscape = %s"u"Hex
Hex
Hex
Hex
Hex = DIGIT / %x41-46 / %x61-66 Escape = %x5C ; backslash TextBlock =ThreeDquotes
[SP
]NL
*TextBlockContent
ThreeDquotes
TextBlockContent =QuotedChar
/ (1*2DQUOTE 1*QuotedChar
) ThreeDquotes = DQUOTE DQUOTE DQUOTE
Shapes
ShapeSection = [NamespaceStatement
UseSection
[ShapeStatements
]] NamespaceStatement = %s"namespace"SP
Namespace
BR
UseSection = *(UseStatement
) UseStatement = %s"use"SP
AbsoluteRootShapeId
BR
ShapeStatements =ShapeOrApplyStatement
*(BR
ShapeOrApplyStatement
) ShapeOrApplyStatement =ShapeStatement
/ApplyStatement
ShapeStatement =TraitStatements
Shape
Shape =SimpleShape
/EnumShape
/AggregateShape
/EntityShape
/OperationShape
SimpleShape =SimpleTypeName
SP
Identifier
[Mixins
] SimpleTypeName = %s"blob" / %s"boolean" / %s"document" / %s"string" / %s"byte" / %s"short" / %s"integer" / %s"long" / %s"float" / %s"double" / %s"bigInteger" / %s"bigDecimal" / %s"timestamp" Mixins = [SP
] %s"with" [WS
] "[" [WS
] 1*(ShapeId
[WS
]) "]" EnumShape =EnumTypeName
SP
Identifier
[Mixins
] [WS
]EnumShapeMembers
EnumTypeName = %s"enum" / %s"intEnum" EnumShapeMembers = "{" [WS
] 1*(EnumShapeMember
[WS
]) "}" EnumShapeMember =TraitStatements
Identifier
[ValueAssignment
] ValueAssignment = [SP
] "=" [SP
]NodeValue
[SP
] [Comma
]BR
AggregateShape =AggregateTypeName
SP
Identifier
[ForResource
] [Mixins
] [WS
]ShapeMembers
AggregateTypeName = %s"list" / %s"map" / %s"union" / %s"structure" ForResource =SP
%s"for"SP
ShapeId
ShapeMembers = "{" [WS
] *(ShapeMember
[WS
]) "}" ShapeMember =TraitStatements
(ExplicitShapeMember
/ElidedShapeMember
) [ValueAssignment
] ExplicitShapeMember =Identifier
[SP
] ":" [SP
]ShapeId
ElidedShapeMember = "$"Identifier
EntityShape =EntityTypeName
SP
Identifier
[Mixins
] [WS
]NodeObject
EntityTypeName = %s"service" / %s"resource" OperationShape = %s"operation"SP
Identifier
[Mixins
] [WS
]OperationBody
OperationBody = "{" [WS
] *(OperationProperty
[WS
]) "}" OperationProperty =OperationInput
/OperationOutput
/OperationErrors
OperationInput = %s"input" [WS
] (InlineAggregateShape
/ (":" [WS
]ShapeId
)) OperationOutput = %s"output" [WS
] (InlineAggregateShape
/ (":" [WS
]ShapeId
)) OperationErrors = %s"errors" [WS
] ":" [WS
] "[" [WS
] *(ShapeId
[WS
]) "]" InlineAggregateShape = ":=" [WS
]TraitStatements
[ForResource
] [Mixins
] [WS
]ShapeMembers
Traits
TraitStatements = *(Trait
[WS
]) Trait = "@"ShapeId
[TraitBody
] TraitBody = "(" [WS
] [TraitStructure
/TraitNode
] ")" TraitStructure = 1*(NodeObjectKvp
[WS
]) TraitNode =NodeValue
[WS
] ApplyStatement =ApplyStatementSingular
/ApplyStatementBlock
ApplyStatementSingular = %s"apply"SP
ShapeId
WS
Trait
ApplyStatementBlock = %s"apply"SP
ShapeId
WS
"{" [WS
]TraitStatements
"}"
Shape ID
See also
Refer to Shape ID for the ABNF grammar of shape IDs.
18.4. Comments#
A comment
can appear at any place between tokens where
whitespace (smithy:WS
) can appear. Comments in Smithy are defined using two
forward slashes followed by any character. A newline terminates a comment.
$version: "2"
// This is a comment
namespace com.foo // This is also a comment
// Another comment
string MyString
Note
Three forward slashes can be used to define the documentation of a shape using a special documentation comment.
18.5. Control section#
The control section
of a model contains
control statements
that apply parser directives
to a specific IDL file. Because control statements influence parsing, they
MUST appear at the beginning of a file before any other statements and have
no effect on the semantic model.
The following control statements are currently supported:
Name | Type | Description |
---|---|---|
version | string | Defines the version of the Smithy IDL used in the model file. |
operationInputSuffix | string | Defines the suffix used when generating names for inline operation input. |
operationOutputSuffix | string | Defines the suffix used when generating names for inline operation output. |
Implementations MUST ignore unknown control statements.
18.5.1. Version statement#
The Smithy specification is versioned using a major
. minor
versioning scheme. A version requirement is specified for a model file using
the $version
control statement. When no version number is specified in
the IDL, an implementation SHOULD assume that the model can be loaded.
Because this can lead to unexpected parsing errors, models SHOULD always
include a version.
The value provided in a version control statement is a string that MUST adhere to the following ABNF:
version_string = 1*DIGIT [ "." 1*DIGIT ]
The following example sets the version to 2
, meaning that tooling MUST
support a version greater than or equal to 2.0
and less than 3.0
:
$version: "2"
{
"smithy": "2"
}
A minor version SHOULD be provided when a model depends on a feature released
in a minor update of the specification. The following example sets the
version requirement of a file to 2.1
, meaning that tooling MUST support a
version greater than or equal to 2.1
and less than 3.0
:
$version: "2.1"
{
"smithy": "2.1"
}
Version compatibility
A single version statement can appear in a model file, but different versions MAY be encountered when merging multiple model files together. Multiple versions are supported if and only if all of the version statements are supported by the tool loading the models.
18.6. Metadata section#
The metadata section
is used to apply untyped
metadata to the entire model. A smithy:MetadataStatement
consists of the metadata key to set, followed by =
, followed by the
node value
to assign to the key.
The following example defines metadata in the model:
$version: "2"
metadata greeting = "hello"
metadata "stringList" = ["a", "b", "c"]
{
"smithy": "2",
"metadata": {
"greeting": "hello",
"stringList": ["a", "b", "c"]
}
}
Metadata is not defined within a namespace. Unquoted object property values
are considered syntactic shape IDs and resolve
to the prelude namespace, smithy.api
.
The following Smithy IDL model:
$version: "2"
metadata exampleSyntacticShapeId = required
Is equivalent to the following JSON AST model:
{
"smithy": "2",
"metadata": {
"exampleSyntacticShapeId": "smithy.api#required"
}
}
18.7. Shape section#
The shape section
of the IDL is used to define
shapes and apply traits to shapes.
18.7.1. Namespaces#
Shapes can only be defined after a namespace is declared. A namespace is
declared using a namespace statement
. Only
one namespace can appear per file.
The following example defines a string shape named MyString
in the
smithy.example
namespace:
$version: "2"
namespace smithy.example
string MyString
{
"smithy": "2",
"shapes": {
"smithy.example#MyString": {
"type": "string"
}
}
}
18.7.2. Referring to shapes#
The use section
of the IDL is used to import shapes
into the current namespace so that they can be referred to using a
relative shape ID. The UseStatement
s
that make up this section have no effect on the semantic model.
The following example uses smithy.example#Foo
and smithy.example#Baz
so that they can be referred to using only Foo
and Baz
.
$version: "2"
namespace smithy.hello
use smithy.example#Foo
use smithy.example#Baz
map MyMap {
// Resolves to smithy.example#Foo
key: Foo
// Resolves to smithy.example#Baz
value: Baz
}
A use statement can refer to traits too. The following example
uses the smithy.example#test
and smithy.example#anotherTrait
traits so that they can be applied using relative shape IDs:
$version: "2"
namespace smithy.hello
use smithy.example#test
use smithy.example#anotherTrait
@test // <-- Resolves to smithy.example#test
string MyString
Use statement validation
- A shape cannot be defined in a file with the same name as one of the
shapes imported with a
use
statement. - Shapes IDs with members names cannot be imported with a use statement.
18.7.2.1. Relative shape ID resolution#
Relative shape IDs are resolved using the following process:
- If a
smithy:UseStatement
has imported a shape with the same name, the shape ID resolves to the imported shape ID. - If a shape is defined in the same namespace as the shape with the same name, the namespace of the shape resolves to the current namespace.
- If a shape is defined in the prelude with the same name,
the namespace resolves to
smithy.api
. - If a relative shape ID does not satisfy one of the above cases, the shape ID is invalid, and the namespace is inherited from the current namespace.
The following example Smithy model contains comments above each member of
the shape named MyStructure
that describes the shape the member resolves
to.
$version: "2"
namespace smithy.example
use foo.baz#Bar
string MyString
structure MyStructure {
// Resolves to smithy.example#MyString
// There is a shape named MyString defined in the same namespace.
a: MyString
// Resolves to smithy.example#MyString
// Absolute shape IDs do not perform namespace resolution.
b: smithy.example#MyString
// Resolves to foo.baz#Bar
// The "use foo.baz#Bar" statement imported the Bar symbol,
// allowing the shape to be referenced using a relative shape ID.
c: Bar
// Resolves to smithy.api#String
// No shape named String was imported through a use statement
// the smithy.example namespace does not contain a shape named
// String, and the prelude model contains a shape named String.
d: String
// Resolves to smithy.example#MyBoolean.
// There is a shape named MyBoolean defined in the same namespace.
// Forward references are supported both within the same file and
// across multiple files.
e: MyBoolean
// Resolves to smithy.example#InvalidShape. A shape by this name has
// not been imported through a use statement, a shape by this name
// does not exist in the current namespace, and a shape by this name
// does not exist in the prelude model.
f: InvalidShape
}
boolean MyBoolean
18.7.2.2. Syntactic shape IDs#
Unquoted string values that are not object keys in the Smithy IDL are considered lexical shape IDs and are resolved to absolute shape IDs using the process defined in Relative shape ID resolution.
The following model defines a list that references a string shape defined in another namespace.
$version: "2"
namespace smithy.example
use smithy.other#MyString
list MyList {
member: MyString
}
The above model is equivalent to the following JSON AST model:
{
"smithy": "2",
"shapes": {
"smithy.example#MyList": {
"type": "list",
"members": {
"target": "smithy.other#MyString"
}
}
}
}
Use quotes for literal strings
Values that are not meant to be shape IDs MUST be quoted. The following
model is syntactically valid but semantically incorrect because
it resolves the value of the error trait to the shape ID
"smithy.example#client"
rather than using the string literal value of
"client"
:
$version: "2"
namespace smithy.example
@error(client) // <-- This MUST be "client"
structure Error
string client
The above example is equivalent to the following incorrect JSON AST:
{
"smithy": "2",
"shapes": {
"smithy.example#Error": {
"type": "structure",
"traits": {
"smithy.api#error": "smithy.example#client"
}
},
"smithy.example#client": {
"type": "string"
}
}
}
Object keys
Object keys are not treated as shape IDs. The following example defines a
metadata object, and when loaded into the
semantic model, the object key String
remains
the same literal string value of String
while the value is treated as
a shape ID and resolves to the string literal "smithy.api#String"
.
metadata foo = {
String: String,
}
The above example is equivalent to the following JSON AST:
{
"smithy": "2",
"metadata": {
"String": "smithy.api#String"
}
}
Semantic model
Syntactic shape IDs are syntactic sugar for defining fully-qualified shape IDs inside of strings, and this difference is inconsequential in the semantic model. A syntactic shape ID SHOULD be resolved to a string that contains a fully-qualified shape ID when parsing the model.
Validation
When a syntactic shape ID is found that does not target an actual shape in the fully loaded semantic model, an implementation SHOULD emit a DANGER validation event with an ID of SyntacticShapeIdTarget. This validation brings attention to the broken reference and helps to ensure that modelers do not unintentionally use a syntactic shape ID when they should have used a string. A DANGER severity is used so that the validation can be suppressed in the rare cases that the broken reference can be ignored.
18.7.3. Defining shapes#
Shapes are defined using a smithy:ShapeStatement
.
18.7.3.1. Simple shapes#
Simple shapes are defined using a
smithy:SimpleShape
.
The following example defines a string
shape:
$version: "2"
namespace smithy.example
string MyString
{
"smithy": "2",
"shapes": {
"smithy.example#String": {
"type": "string"
}
}
}
The following example defines an integer
shape with a range trait:
$version: "2"
namespace smithy.example
@range(min: 0, max: 1000)
integer MaxResults
{
"smithy": "2",
"shapes": {
"smithy.example#MaxResults": {
"type": "integer",
"traits": {
"smithy.api#range": {
"min": 0,
"max": 100
}
}
}
}
}
18.7.3.2. Enum shapes#
The enum shape is defined using an smithy:EnumShape
.
The following example defines an enum shape:
$version: "2"
namespace smithy.example
enum Suit {
DIAMOND
CLUB
HEART
SPADE
}
Syntactic sugar can be used to assign an enumValue trait to an enum member. The following example defines an enum shape with custom values and traits:
$version: "2"
namespace smithy.example
enum Suit {
@deprecated
DIAMOND = "diamond"
CLUB = "club"
HEART = "heart"
SPADE = "spade"
}
The above enum is exactly equivalent to the following enum:
$version: "2"
namespace smithy.example
enum Suit {
@deprecated
@enumValue("diamond")
DIAMOND
@enumValue("club")
CLUB
@enumValue("heart")
HEART
@enumValue("spade")
SPADE
}
18.7.3.3. IntEnum shapes#
The intEnum shape is defined using an
smithy:EnumShape
.
Note
The enumValue trait is required on all intEnum members.
Syntactic sugar can be used to assign an enumValue trait to an intEnum member. The following example defines an intEnum shape:
$version: "2"
namespace smithy.example
intEnum Suit {
DIAMOND = 1
CLUB = 2
HEART = 3
SPADE = 4
}
The above intEnum is exactly equivalent to the following intEnum:
$version: "2"
namespace smithy.example
intEnum Suit {
@enumValue(1)
DIAMOND
@enumValue(2)
CLUB
@enumValue(3)
HEART
@enumValue(4)
SPADE
}
18.7.3.4. List shapes#
A list shape is defined using a smithy:AggregateShape
.
The following example defines a list with a string member from the prelude:
$version: "2"
namespace smithy.example
list MyList {
member: String
}
{
"smithy": "2",
"shapes": {
"smithy.example#MyList": {
"type": "list",
"member": {
"target": "smithy.api#String"
}
}
}
}
Traits can be applied to the list shape and its member:
$version: "2"
namespace smithy.example
@length(min: 3, max: 10)
list MyList {
@length(min: 1, max: 100)
member: String
}
{
"smithy": "2",
"shapes": {
"smithy.example#MyList": {
"type": "list",
"member": {
"target": "smithy.api#String",
"traits": {
"smithy.api#length": {
"min": 1,
"max": 100
}
}
},
"traits": {
"smithy.api#length": {
"min": 3,
"max": 10
}
}
}
}
}
18.7.3.5. Map shapes#
A map shape is defined using a smithy:AggregateShape
.
The following example defines a map of strings to integers:
$version: "2"
namespace smithy.example
map IntegerMap {
key: String,
value: Integer
}
{
"smithy": "2",
"shapes": {
"type": "map",
"smithy.example#IntegerMap": {
"key": {
"target": "smithy.api#String"
},
"value": {
"target": "smithy.api#String"
}
}
}
}
Traits can be applied to the map shape and its members:
$version: "2"
namespace smithy.example
@length(min: 0, max: 100)
map IntegerMap {
@length(min: 1, max: 10)
key: String,
@range(min: 1, max: 1000)
value: Integer
}
{
"smithy": "2",
"shapes": {
"smithy.example#IntegerMap": {
"type": "map",
"key": {
"target": "smithy.api#String",
"traits": {
"smithy.api#length": {
"min": 1,
"max": 10
}
}
},
"value": {
"target": "smithy.api#Integer",
"traits": {
"smithy.api#range": {
"min": 1,
"max": 1000
}
}
},
"traits": {
"smithy.api#length": {
"min": 0,
"max": 100
}
}
}
}
}
18.7.3.6. Structure shapes#
A structure shape is defined using a
smithy:AggregateShape
.
The following example defines a structure with two members:
$version: "2"
namespace smithy.example
structure MyStructure {
foo: String
baz: Integer
}
{
"smithy": "2",
"shapes": {
"smithy.example#MyStructure": {
"type": "structure",
"members": {
"foo": {
"target": "smithy.api#String"
},
"baz": {
"target": "smithy.api#Integer"
}
}
}
}
}
Traits can be applied to structure members:
$version: "2"
namespace smithy.example
/// This is MyStructure.
structure MyStructure {
/// This is documentation for `foo`.
@required
foo: String
/// This is documentation for `baz`.
@deprecated
baz: Integer
}
{
"smithy": "2",
"shapes": {
"smithy.example#MyStructure": {
"type": "structure",
"members": {
"foo": {
"target": "smithy.api#String",
"traits": {
"smithy.api#documentation": "This is documentation for `foo`.",
"smithy.api#required": {}
}
},
"baz": {
"target": "smithy.api#Integer",
"traits": {
"smithy.api#documentation": "This is documentation for `baz`.",
"smithy.api#deprecated": {}
}
}
},
"traits": {
"smithy.api#documentation": "This is MyStructure."
}
}
}
}
Syntactic sugar can be used to apply the default trait to a structure member. The following example:
structure Example {
normative: Boolean = true
}
Is exactly equivalent to:
structure Example {
@default(true)
normative: Boolean
}
18.7.3.7. Union shapes#
A union shape is defined using a smithy:AggregateShape
.
The following example defines a union shape with several members:
$version: "2"
namespace smithy.example
union MyUnion {
i32: Integer
@length(min: 1, max: 100)
string: String
time: Timestamp
}
{
"smithy": "2",
"shapes": {
"smithy.example#MyUnion": {
"type": "union",
"members": {
"i32": {
"target": "smithy.api#Integer"
},
"string": {
"target": "smithy.api#String",
"smithy.api#length": {
"min": 1,
"max": 100
}
},
"time": {
"target": "smithy.api#Timestamp"
}
}
}
}
}
18.7.3.8. Service shape#
A service shape is defined using a smithy:EntityShape
and the provided
smithy:NodeObject
supports the same properties defined in the
service specification.
The following example defines a service named ModelRepository
that binds
a resource named Model
and an operation named PingService
:
$version: "2"
namespace smithy.example
service ModelRepository {
version: "2020-07-13",
resources: [Model],
operations: [PingService]
}
{
"smithy": "2",
"shapes": {
"smithy.example#ModelRepository": {
"type": "service",
"resources": [
{
"target": "smithy.example#Model"
}
],
"operations": [
{
"target": "smithy.example#PingService"
}
]
}
}
}
18.7.3.9. Operation shape#
An operation shape is defined using an smithy:OperationShape
and
the same properties defined in the operation specification.
The following example defines an operation shape that accepts an input
structure named Input
, returns an output structure named Output
, and
can potentially return the Unavailable
or BadRequest
error structures.
$version: "2"
namespace smithy.example
operation PingService {
input: PingServiceInput,
output: PingServiceOutput,
errors: [UnavailableError, BadRequestError]
}
{
"smithy": "2",
"shapes": {
"smithy.example#PingService": {
"type": "operation",
"input": {
"target": "smithy.example#PingServiceInput"
},
"output": {
"target": "smithy.example#PingServiceOutput"
},
"errors": [
{
"target": "smithy.example#UnavailableError"
},
{
"target": "smithy.example#BadRequestError"
}
]
}
}
}
18.7.3.9.1. Inline input / output shapes#
The input and output properties of operations can be defined using a more succinct, inline syntax.
A structure defined using inline syntax is automatically marked with the input trait for inputs and the output trait for outputs.
A structure defined using inline syntax is given a generated shape name. For
inputs, the generated name is the name of the operation shape with the suffix
Input
added. For outputs, the generated name is the name of the operation
shape with the Output
suffix added.
For example, the following model:
operation GetUser {
// The generated shape name is GetUserInput
input := {
userId: String
}
// The generated shape name is GetUserOutput
output := {
username: String
userId: String
}
}
Is equivalent to:
operation GetUser {
input: GetUserInput
output: GetUserOutput
}
@input
structure GetUserInput {
userId: String
}
@output
structure GetUserOutput {
username: String
userId: String
}
Traits and mixins can be applied to the inline structure:
@mixin
structure BaseUser {
userId: String
}
operation GetUser {
input := @references([{resource: User}]) {
userId: String
}
output := with [BaseUser] {
username: String
}
}
operation PutUser {
input :=
@references([{resource: User}])
with [BaseUser] {}
}
The suffixes for the generated names can be customized using the
operationInputSuffix
and operationOutputSuffix
control statements.
$version: "2"
$operationInputSuffix: "Request"
$operationOutputSuffix: "Response"
namespace smithy.example
operation GetUser {
// The generated shape name is GetUserRequest
input := {
userId: String
}
// The generated shape name is GetUserResponse
output := {
username: String
userId: String
}
}
18.7.3.10. Resource shape#
A resource shape is defined using a smithy:EntityShape
and the
provided smithy:NodeObject
supports the same properties defined in the
resource specification.
The following example defines a resource shape that has a single identifier, and defines a read operation:
$version: "2"
namespace smithy.example
resource SprocketResource {
identifiers: {
sprocketId: String,
},
read: GetSprocket,
}
{
"smithy": "2",
"shapes": {
"smithy.example#Sprocket": {
"type": "resource",
"identifiers": {
"sprocketId": {
"target": "smithy.api#String"
}
},
"read": {
"target": "smithy.example#SprocketResource"
}
}
}
}
See also
The target elision syntax for an easy way to define structures that reference resource identifiers without having to repeat the target definition.
18.7.3.11. Mixins#
Mixins can be added to a shape using the optional
smithy:Mixins
clause of a shape definition.
For example:
@mixin
structure BaseUser {
userId: String
}
structure UserDetails with [BaseUser] {
username: String
}
@mixin
@sensitive
string SensitiveString
@pattern("^[a-zA-Z\.]*$")
string SensitiveText with [SensitiveString]
18.7.3.12. Target Elision#
Having to completely redefine a resource identifier
to use it in a structure or redefine a member from a mixin to add
additional traits can be cumbersome and potentially error-prone. Target elision
syntax can be used to cut down on that repetition by prefixing the member name
with a $
. If a member is prefixed this way, its target will automatically be
set to the target of a mixin member with the same name. The following example
shows how to elide the target for a member inherited from a mixin:
$version: "2"
namespace smithy.example
@mixin
structure IdBearer {
id: String
}
structure IdRequired with [IdBearer] {
@required
$id
}
Additionally, structure shapes can reference a resource
shape to define members that represent the resource's identifiers without having
to redefine the target shape. In addition to prefixing a member with $
, the
structure must also add for
followed by the resource referenced in
the shape's definition before any mixins are specified.
To resolve elided types, first check if any bound resource defines an identifier that case-sensitively matches the elided member name. If a match is found, the type targeted by that identifier is used for the elided type. If no identifier matches the elided member name, mixin members are case-sensitively checked, and if a match is found, the type targeted by the mixin member is used as the elided type. It is an error if neither the resource or mixin members matches an elided member name.
The following example shows a structure reusing an identifier definition from a resource:
$version: "2"
namespace smithy.example
resource User {
identifiers: {
name: String
uuid: String
}
}
structure UserSummary for User {
$name
age: Short
}
Note that the UserSummary
structure does not attempt to define the
uuid
identifier. When referencing a resource in this way, only the
identifiers that are explicitly referenced are added to the structure. This
allows structures to define subsets of identifiers, which can be useful for
operations like create operations where some of those identifiers may be
generated by the service.
Structures may only reference one resource shape in this way.
When using both mixins and a resource reference, the referenced resource will be checked first. The following example is invalid:
$version: "2"
namespace smithy.example
resource User {
identifiers: {
uuid: String
}
}
@mixin
structure UserIdentifiers {
uuid: Blob
}
// This is invalid because the `uuid` member's target is set to
// String, which then conflicts with the UserIdentifiers mixin.
structure UserSummary for User with [UserIdentifiers] {
$uuid
}
18.7.4. Documentation comment#
Documentation comments
are a
special kind of smithy:Comment
that provide
documentation for shapes. A documentation
comment is formed when three forward slashes ("///"
) appear as the
first non-whitespace characters on a line.
Documentation comments are defined using CommonMark. The text after the forward slashes is considered the contents of the line. If the text starts with a space (" "), the leading space is removed from the content. Successive documentation comments are combined together using a newline ("\n") to form the documentation of a shape.
The following Smithy IDL example,
$version: "2"
namespace smithy.example
/// This is documentation about a shape.
///
/// - This is a list
/// - More of the list.
string MyString
/// This is documentation about a trait shape.
/// More docs here.
@trait
structure myTrait {}
is equivalent to the following JSON AST model:
{
"smithy": "2",
"shapes": {
"smithy.example#MyString": {
"type": "string",
"traits": {
"smithy.api#documentation": "This is documentation about a shape.\n\n- This is a list\n- More of the list."
}
},
"smithy.example#myTrait": {
"type": "structure",
"traits": {
"smithy.api#trait": {},
"smithy.api#documentation": "This is documentation about a trait shape.\n More docs here."
}
}
}
}
Placement
Documentation comments are only treated as shape documentation when the comment appears immediately before a shape, and documentation comments MUST appear before any traits applied to the shape in order for the documentation to be applied to a shape.
The following example applies a documentation trait to the shape because the documentation comment comes before the traits applied to a shape:
/// A deprecated string.
@deprecated
string MyString
Documentation comments can also be applied to members of a shape.
/// Documentation about the structure.
structure Example {
/// Documentation about the member.
@required
foo: String,
}
Semantic model
Documentation comments are syntactic sugar equivalent to applying the documentation trait, and this difference is inconsequential in the semantic model.
18.7.5. Applying traits#
Trait values immediately preceding a shape definition are applied to the
shape. The shape ID of a trait is resolved against smithy:UseStatement
s
and the current namespace in exactly the same way as
other shape IDs.
The following example applies the length trait and
documentation trait to MyString
:
$version: "2"
namespace smithy.example
@length(min: 1, max: 100)
@documentation("Contains a string")
string MyString
{
"smithy": "2",
"shapes": {
"smithy.example#MyString": {
"type": "string",
"traits": {
"smithy.api#documentation": "Contains a string",
"smithy.api#length": {
"min": 1,
"max": 100
}
}
}
}
}
18.7.5.1. Trait values#
The value that can be provided for a trait depends on its type. A value for a trait is defined by enclosing the value in parenthesis, provided as a node value. Trait values MUST adhere to the JSON type mappings defined in Trait node values. Trait values can only appear immediately before a shape.
The following example applies various traits to a structure shape and its members.
@documentation("An animal in the animal kingdom")
structure Animal {
@required
name: smithy.api#String,
@length(min: 0)
@tags(["private-beta", "metered"])
age: smithy.api#Integer,
}
18.7.5.2. Structure, map, and union trait value syntax#
A special syntax is provided for structure, map, and union traits that allows placing key-value pairs directly inside of the trait parenthesis.
@structuredTrait(bar: "baz", qux: "true")
Is equivalent to:
@structuredTrait({bar: "baz", qux: "true"})
18.7.5.3. Omitted trait values#
An applied trait with no value, with or without empty parenthesis, assumes a default value based on the shape of the trait.
If a value is omitted for a structure
or map
, the value defaults to an empty
object ({}
).
The following applications of the foo
trait are equivalent:
$version: "2"
namespace smithy.example
// Define an example structure trait.
@trait
structure foo {}
@foo
string MyString1
@foo()
string MyString2
@foo({})
string MyString3
{
"smithy": "2",
"shapes": {
"smithy.example#foo": {
"type": "structure",
"traits": {
"smithy.api#trait": {}
}
},
"smithy.example#MyString1": {
"type": "string",
"traits": {
"smithy.api#foo": {}
}
},
"smithy.example#MyString2": {
"type": "string",
"traits": {
"smithy.api#foo": {}
}
},
"smithy.example#MyString3": {
"type": "string",
"traits": {
"smithy.api#foo": {}
}
}
}
}
If a value is omitted for a list
, the value defaults to an empty list
([]
).
The following applications of the tags trait are equivalent:
$version: "2"
namespace smithy.example
@tags
string MyString1
@tags()
string MyString2
@tags([])
string MyString3
{
"smithy": "2",
"shapes": {
"smithy.example#MyString1": {
"type": "string",
"traits": {
"smithy.api#tags": []
}
},
"smithy.example#MyString2": {
"type": "string",
"traits": {
"smithy.api#tags": []
}
},
"smithy.example#MyString3": {
"type": "string",
"traits": {
"smithy.api#tags": []
}
}
}
}
All other shapes default to null
, which may or may not be valid for the
shape of the trait.
18.7.5.4. Apply statement#
Traits can be applied to shapes outside of a shape's definition using an
smithy:ApplyStatement
.
The following example applies the documentation trait to the
smithy.example#MyString
shape:
$version: "2"
namespace smithy.example
apply MyString @documentation("This is my string!")
{
"smithy": "2",
"shapes": {
"smithy.example#MyString": {
"type": "apply",
"traits": {
"smithy.api#documentation": "This is my string!"
}
}
}
}
Multiple traits can be applied to the same shape using a block apply
statement. The following example applies the documentation trait
and length trait to the smithy.example#MyString
shape:
$version: "2"
namespace smithy.example
apply MyString {
@documentation("This is my string!")
@length(min: 1, max: 10)
}
{
"smithy": "2",
"shapes": {
"smithy.example#MyString": {
"type": "apply",
"traits": {
"smithy.api#documentation": "This is my string!",
"smithy.api#length": {
"min": 1,
"max": 10
}
}
}
}
}
Traits can be applied to members too:
$version: "2"
namespace smithy.example
apply MyStructure$foo @documentation("Structure member documentation")
apply MyUnion$foo @documentation("Union member documentation")
apply MyList$member @documentation("List member documentation")
apply MySet$member @documentation("Set member documentation")
apply MyMap$key @documentation("Map key documentation")
apply MyMap$value @documentation("Map key documentation")
See also
Refer to trait conflict resolution for information on how trait conflicts are resolved.
Note
In the semantic model, applying traits outside of a shape definition is treated exactly the same as applying the trait inside of a shape definition.
18.8. Node values#
Node values are analogous to JSON values. Node values are used to define metadata and trait values. Smithy's node values have many advantages over JSON: comments, unquoted keys, unquoted strings, text blocks, and trailing commas.
The following example defines a complex object metadata entry using a node value:
metadata foo = {
hello: 123,
"foo": "456",
testing: """
Hello!
""",
an_array: [10.5],
nested-object: {
hello-there$: true
}, // <-- Trailing comma
}
Array node
An array node is defined like a JSON array. A smithy:NodeArray
contains
zero or more heterogeneous smithy:NodeValue
s. A trailing comma is allowed
in a NodeArray
.
The following examples define arrays with zero, one, and two values:
[]
[true]
[1, "hello",]
Object node
An object node is defined like a JSON object. A smithy:NodeObject
contains
zero or more key value pairs of strings (a smithy:NodeObjectKey
) that map
to heterogeneous smithy:NodeValue
s. A trailing comma is allowed
in a NodeObject
.
The following examples define objects with zero, one, and two key value pairs:
{}
{foo: true}
{foo: "hello", "bar": [1, 2, {}]}
Number node
A node smithy:Number
contains numeric data. It is defined like a JSON
number. The following examples define several Number
values:
0
0.0
1234
-1234.1234
1e+2
1.0e-10
Node keywords
Several keywords are used when parsing smithy:NodeValue
.
true
: The value is treated as a booleantrue
false
: The value is treated as a booleanfalse
null
: The value is treated like a JSONnull
18.8.1. String values#
A NodeValue
can contain smithy:NodeStringValue
productions that all
define strings.
New lines
New lines in strings are normalized from CR (u000D) and CRLF (u000Du000A)
to LF (u000A). This ensures that strings defined in a Smithy model are
equivalent across platforms. If a literal \r
is desired, it can be added
a string value using the Unicode escape \u000d
.
String equivalence
The NodeStringValue
production defines several productions used to
define strings, and in order for these productions to work in concert with
the JSON AST format, each of these production MUST be
treated like equivalent string values when loaded into the
semantic model.
18.8.2. String escape characters#
The Smithy IDL supports escape sequences only within quoted strings. The following escape sequences are allowed:
Unicode code point | Escape | Meaning |
---|---|---|
U+0022 | \" |
double quote |
U+005C | \\ |
backslash |
U+002F | \/ |
forward slash |
U+0008 | \b |
backspace BS |
U+000C | \f |
form feed FF |
U+000A | \n |
line feed LF |
U+000D | \r |
carriage return CR |
U+0009 | \t |
horizontal tab HT |
U+HHHH | \uHHHH |
4-digit hexadecimal Unicode code point |
nothing | \\r\n , \\r , \\n |
escaped new line expands to nothing |
Any other sequence following a backslash is an error.
18.8.3. Text blocks#
A text block is a string literal that can span multiple lines and automatically removes any incidental whitespace. Smithy text blocks are heavily inspired by text blocks defined in JEP 355.
A text block is opened with three double quotes ("""), followed by a newline, zero or more content characters, and closed with three double quotes. Text blocks differentiate incidental whitespace from significant whitespace. Smithy will re-indent the content of a text block by removing all incidental whitespace.
@documentation("""
<div>
<p>Hello!</p>
</div>
""")
The four leading spaces in the above text block are considered insignificant because they are common across all lines. Because the closing delimiter appears on its own line, a trailing new line is added to the result. The content of the text block is re-indented to remove the insignificant whitespace, making it equivalent to the following:
@documentation("<div>\n <p>Hello!</p>\n</div>\n")
The closing delimiter can be placed on the same line as content if no new line is desired at the end of the result. The above example could be rewritten to not including a trailing new line:
@documentation("""
<div>
<p>Hello!</p>
</div>""")
This example is equivalent to the following:
@documentation("<div>\n <p>Hello!</p>\n</div>")
The following text blocks are ill-formed:
"""foo""" // missing new line following open delimiter
""" """ // missing new line following open delimiter
"""
" // missing closing delimiter
18.8.3.1. Incidental white space removal#
Smithy will re-indent the content of a text block by removing all incidental whitespace using the following algorithm:
Split the content of the text block at every LF, producing a list of lines. The opening LF of the text block is not considered.
Given the following example ("." is used to represent spaces),
@documentation(""" ....Foo ........Baz .. ....Bar ....""")
the following lines are produced:
[" Foo", " Baz", "", " ", " Bar", " "]
Compute the common whitespace prefix by iterating over each line, counting the number of leading spaces (" ") and taking the minimum count. Except for the last line of content, lines that are empty or consist wholly of whitespace are not considered. If the last line of content (that is, the line that contains the closing delimiter) appears on its own line, then that line's leading whitespace is considered when determining the common whitespace prefix, allowing the closing delimiter to determine the amount of indentation to remove.
Using the previous example, the common whitespace prefix is four spaces. The empty third line and the blank fourth lines are not considered when computing the common whitespace. The following uses "." to represent the common whitespace prefix:
@documentation(""" ....Foo .... Baz .... ....Bar ....""")
Remove the common white space prefix from each line.
This step produces the following values from the previous example:
["Foo", " Baz", "", "", "Bar", ""]
Remove any trailing spaces from each line.
Concatenate each line together, separated by LF.
This step produces the following result ("|" is used to represent the left margin):
18.8.3.2. Significant trailing line#
The last line of text block content is used when determining the common whitespace prefix.
Consider the following example:
@documentation("""
Foo
Baz
Bar
""")
Because the closing delimiter is at the margin and left of the rest of the content, the common whitespace prefix is 0 characters, resulting in the following equivalent string:
@documentation(" Foo\n Baz\n Bar\n")
If the closing delimiter is moved to the right of the content, then it has no bearing on the common whitespace prefix. The common whitespace prefix in the following example is visualized using "." to represent spaces:
@documentation("""
....Foo
.... Baz
....Bar
""")
Because lines are trimmed when they are added to the result, the above example is equivalent to the following:
@documentation("Foo\n Baz\nBar\n")
18.8.3.3. Escapes in text blocks#
Text blocks support all of the string escape characters
of other strings. The use of three double quotes allows unescaped double quotes
(") to appear in text blocks. The following text block is interpreted as
"hello!"
:
"""
"hello!"
"""
Three quotes can appear in a text block without being treated as the closing
delimiter as long as one of the quotes are escaped. The following text block
is interpreted as foo """\nbaz
:
"""
foo \"""
baz"""
String escapes are interpreted after incidental whitespace is removed from a text block. The following example uses "." to denote spaces:
"""
..<div>
....<p>Hi\n....bar</p>
..</div>
.."""
Because string escapes are expanded after incidental whitespace is removed, it is interpreted as:
New lines in the text block can be escaped. This allows for long, single-line
strings to be broken into multiple lines in the IDL. The following example
is interpreted as Foo Baz Bam
:
"""
Foo \
Baz \
Bam"""
Escaped new lines can be intermixed with unescaped newlines. The following
example is interpreted as Foo\nBaz Bam
:
"""
Foo
Baz \
Bam"""