17. Model validation#
Smithy provides a customizable validation system that can be used by API designers and organizations to ensure that their APIs adhere to their own standards, best practices, and constraints.
17.1. Introduction#
APIs require a great deal of care and discipline to ensure that they provide a coherent interface to customers, particularly after an API is released and new features are added. This specification defines metadata that is used to validate a model against configurable validator definitions, ensuring that developers adhere to an organization's API standards.
Tools like Checkstyle and Findbugs help to ensure that developers avoid common bugs and pitfalls when writing code. This is a very powerful concept, particularly for developers that are new to a programming language. This concept is even more powerful when teams use the configurability of these tools to communicate the coding standards of an organization and automate their enforcement. This validation standard allows the same level of conformity and rigor to be applied to Smithy models and API definitions.
17.2. Validators#
The validators
metadata property contains an array of validator
objects that are used to constrain a model. Each object in the
validators
array supports the following properties:
Property | Type | Description |
---|---|---|
name | string |
Required. The name of the validator to apply. This name is used in implementations to find and configure the appropriate validator implementation. Validators only take effect if a Smithy processor implements the validator. |
id | string |
Defines a custom identifier for the validator. Multiple instances of a single validator can be configured for a model.
Providing an If IDs that contain dots (.) are hierarchical. For example, the ID "Foo.Bar" contains the ID "Foo". Event ID hierarchies can be leveraged to group validation events and allow more granular suppressions. |
message | string |
Provides a custom message to use when emitting validation events. The
special {super} string can be added to a custom message to inject
the original error message of the validation event into the custom
message. |
severity | string |
Provides a custom severity level to use when a validation event occurs. If no severity is provided, then the default severity of the validator is used. Note The severity of user-defined validators cannot be set to ERROR. |
namespaces | [ string ] |
Provides a list of the namespaces that are targeted by the validator. The validator will ignore any validation events encountered that are not specific to the given namespaces. |
selector | string |
A valid selector that causes the validator to only validate shapes that match the selector. The validator will ignore any validation events encountered that do not satisfy the selector. |
configuration | object |
Object that provides validator configuration. The available properties are defined by each validator. Validators MAY require that specific configuration properties are provided. |
The following Smithy document applies a custom validator named "SomeValidator":
$version: "2"
metadata validators = [
{
// The name of the validator.
name: "SomeValidator"
// Uses a custom event ID for each validation event emitted.
id: "CustomEventId"
// Uses a custom message that also includes the default message.
message: "My custom message name. {super}"
// Applies the rule only to the following namespaces.
namespaces: ["foo.baz", "bar.qux"]
// The following properties are specific to the validator.
configuration: {
"someProperty": "foo"
}
}
]
namespace smithy.example
// shapes are defined here...
17.2.1. Missing validators#
The actual implementation of a validator is defined in code and is
not defined in the Smithy model itself. If a Smithy implementation does not
have an implementation for a specific validator by name, the Smithy
implementation MUST emit a WARNING validation event with an event ID that is
the concatenation of UnknownValidator_
and the name
property of the
validator that could not be found. For example, given a custom validator
that could not be found named Foo
, the implementation MUST emit a
validation event with an event ID of UnknownValidator_Foo
and a
severity of WARNING.
17.3. Severity#
When a model is in violation of a validator, a validation event is emitted. This validation event contains metadata about the violation, including the optional shape that was in violation, the validator ID, and the severity of the violation. Severity is used to define the importance or impact of a violation.
- ERROR
Indicates that something is structurally wrong with the model and cannot be suppressed.
Validation events with a severity of ERROR are reserved for enforcing that models adhere to the Smithy specification. Validators cannot emit a validation event with a severity of ERROR.
- DANGER
- Indicates that something is very likely wrong with the model. Unsuppressed DANGER validation events indicate that a model is invalid.
- WARNING
- Indicates that something might be wrong with the model.
- NOTE
- Informational message that does not imply anything is wrong with the model.
17.4. Suppressions#
Suppressions are used to suppress specific validation events. Suppressions are created using the suppress trait and suppressions metadata.
17.4.1. suppress
trait#
- Summary
- The suppress trait is used to suppress validation events(s) for a
specific shape. Each value in the
suppress
trait is a validation event ID to suppress for the shape. - Trait selector
*
- Value type
[string]
The following example suppresses the Foo
and Bar
validation events
for the smithy.example#MyString
shape:
$version: "2"
namespace smithy.example
@suppress(["Foo", "Bar"])
string MyString
17.4.2. Suppression metadata#
The suppressions
metadata property contains an array of suppression objects
that are used to suppress validation events for the entire model or for an
entire namespace.
Each suppression object in the suppressions
array supports the
following properties:
Property | Type | Description |
---|---|---|
id | string |
Required. The hierarchical validation event ID to suppress. |
namespace | string |
Required. The validation event is only suppressed if it matches the
supplied namespace. A value of * can be provided to match any namespace.
* is useful for suppressing validation events that are not bound to any
specific shape. |
reason | string |
Provides an optional reason for the suppression. |
The following example suppresses all validation events on shapes
in the foo.baz
namespace with an ID of UnreferencedShape
:
$version: "2"
metadata suppressions = [
{
id: "UnreferencedShape"
namespace: "foo.baz"
reason: "This is a test namespace."
}
]
The following example suppresses all validation events with an
ID of OverlyBroadValidator
:
$version: "2"
metadata suppressions = [
{
id: "OverlyBroadValidator"
namespace: "*"
}
]
17.4.3. Matching suppression event IDs#
Both the suppress trait and suppressions defined in metadata match events hierarchically based on dot (.) segments. For example, given a validation event ID of "Foo.Bar", and a suppression ID of "Foo", the suppression ID matches the event ID because "Foo.Bar" begins with the segment "Foo". However, a suppression ID of "Foo.Bar" does not match an event ID of "Foo" because "Foo.Bar" is more specific. Further, a suppression ID of "ABC" does not match an event ID of "ABC123" because "ABC" is not a segment of the event ID.
Event ID | Suppression ID | Is match |
---|---|---|
Foo |
Foo |
Yes |
Foo.Bar |
Foo |
Yes |
Foo.Bar.Baz |
Foo |
Yes |
Foo. |
Foo. |
Yes |
Foo. |
Foo |
Yes |
Foo |
Foo. |
No |
Foosball |
Foo |
No |
Foo |
Foo.Bar |
No |
Abc.Foo.Bar |
Foo.Bar |
No |
17.5. Severity overrides#
The severityOverrides
metadata property is used to elevate the severity
of non-suppressed validation events. This property contains an array of
severity override objects that support the following properties:
Property | Type | Description |
---|---|---|
id | string |
Required. The hierarchical validation event ID to elevate. |
namespace | string |
Required. The validation event is only elevated if it matches the
supplied namespace. A value of * can be provided to match any namespace. |
severity | string |
Defines the severity to elevate matching
events to. This value can only be set to WARNING or DANGER . |
The following example elevates the events of SomeValidator
to DANGER
in any namespace, and OtherValidator
is elevated to WARNING
but only
for events emitted for shapes in the smithy.example
namespace:
$version: "2"
metadata severityOverrides = [
{
namespace: "*"
id: "SomeValidator"
severity: "DANGER"
}
{
namespace: "smithy.example"
id: "OtherValidator"
severity: "WARNING"
}
]
namespace smithy.example
17.6. Built-in validators#
Smithy provides built-in validators that can be used in any model in
the validators
metadata property. Implementations MAY support
additional validators.
17.6.1. EmitEachSelector#
Emits a validation event for each shape that matches the given selector.
- Rationale
- Detecting shapes that violate a validation rule using customizable validators allows organizations to create custom Smithy validators without needing to write code.
- Default severity
DANGER
- Configuration
Property Type Description selector string
Required. A valid selector. A validation event is emitted for each shape in the model that matches the selector
.bindToTrait string
An optional string that MUST be a valid shape ID that targets a trait definition. A validation event is only emitted for shapes that have this trait. messageTemplate string
A custom template that is expanded for each matching shape and assigned as the message for the emitted validation event.
The following example detects if a shape is missing documentation with the following constraints:
- Shapes that have the documentation trait are excluded.
- Members that target shapes that have the documentation trait are excluded.
- Simple types are excluded.
- List and map members are excluded.
$version: "2"
metadata validators = [{
name: "EmitEachSelector"
id: "MissingDocumentation"
message: "This shape is missing documentation"
configuration: {
selector: """
:not([trait|documentation])
:not(simpleType)
:not(member :test(< :test(list, map)))
:not(member > [trait|documentation])"""
}
}]
The following example emits a validation event for each structure referenced as input/output that has a shape name that does not case-insensitively end with "Input"/"Output":
$version: "2"
metadata validators = [
{
name: "EmitEachSelector"
id: "OperationInputName"
message: "This shape is referenced as input but the name does not end with 'Input'"
configuration: {
selector: "operation -[input]-> :not([id|name$=Input i])"
}
}
{
name: "EmitEachSelector"
id: "OperationOutputName"
message: "This shape is referenced as output but the name does not end with 'Output'"
configuration: {
selector: "operation -[output]-> :not([id|name$=Output i])"
}
}
]
The following example emits a validation event for each operation referenced as lifecycle 'read' or 'delete' that has a shape name that does not start with "Get" or "Delete":
$version: "2"
metadata validators = [
{
name: "EmitEachSelector"
id: "LifecycleGetName"
message: "Lifecycle 'read' operation shape names should start with 'Get'"
configuration: {
selector: "operation [read]-> :not([id|name^=Get i])"
}
}
{
name: "EmitEachSelector"
id: "LifecycleDeleteName"
message: "Lifecycle 'delete' operation shape names should start with 'Delete'"
configuration: {
selector: "operation -[delete]-> :not([id|name^=Delete i])"
}
}
]
17.6.1.1. Binding events to traits#
The bindToTrait
property contains a shape ID that MUST
reference a trait definition shape. When set, this
property causes the EmitEachSelector
validator to only emit validation
events for shapes that have the referenced trait. The contextual location of
where the violation occurred in the model SHOULD point to the location where
the trait is applied to the matched shape.
Consider the following model:
metadata validators = [
{
name: "EmitEachSelector"
id: "DocumentedString"
configuration: {
// matches all shapes
selector: "*"
// Only emitted for shapes with the documentation
// trait, and each event points to where the
// trait is defined.
bindToTrait: documentation
}
}
]
namespace smithy.example
@documentation("Hello")
string A // <-- Emits an event
string B // <-- Does not emit an event
The DocumentedString
validator will only emit an event for
smithy.example#A
because smithy.example#B
does not have the
documentation trait.
17.6.1.2. Message templates#
A messageTemplate
is used to create more granular error messages. The
template consists of literal spans and selector context value
templates (for example, @{id}
). A selector context value MAY be escaped
by placing a @
before a @
character (for example, @@
expands to
@
). @
characters in the message template that are not escaped MUST
form a valid selector_context_value
production.
For each shaped matched by the selector
of an EmitEachSelector
, a
selector attribute is created from the shape
along with all of the selector variables that were
assigned when the shape was matched. Each selector_context_value
in the
template is then expanded by retrieving nested properties from the shape
using a pipe-delimited path (for example, @{id|name}
expands to the
name of the matching shape's shape ID).
Consider the following model:
metadata validators = [
{
name: "EmitEachSelector"
configuration: {
selector: "[trait|documentation]"
messageTemplate: """
This shape has a name of @{id|name} and a @@documentation \
trait of "@{trait|documentation}"."""
}
}
]
namespace smithy.example
@documentation("Hello")
string A
@documentation("Goodbye")
string B
The above selector will emit two validation events:
Shape ID | Expanded message |
---|---|
smithy.example#A |
This shape has a name of A and a @documentation trait of "Hello". |
smithy.example#B |
This shape has a name of B and a @documentation trait of "Goodbye". |
Selector variables can be used in the selector to make message templates more descriptive. Consider the following example:
metadata validators = [
{
name: "EmitEachSelector"
id: "UnstableTrait"
configuration: {
selector: """
$matches(-[trait]-> [trait|unstable])
${matches}"""
messageTemplate: "This shape applies traits(s) that are unstable: @{var|matches|id}"
}
}
]
namespace smithy.example
@trait
@unstable
structure doNotUseMe {}
@doNotUseMe
string A
The above selector will emit the following validation event:
Shape ID | Expanded message |
---|---|
smithy.example#A |
This shape applies traits(s) that are unstable: [smithy.example#doNotUseMe] |
17.6.1.3. Variable message formatting#
Different types of variables expand to different kinds of strings in message templates.
Attribute | Expansion |
---|---|
empty values | An empty value expands to nothingness [1]. Empty values are created when a selector context value attempts to access a variable or nested property that does not exist. Consider the following message template: |
id | Expands to the absolute shape ID of a shape [1]. |
literal values | Literal values are created when descending into nested properties of
an id , service , or projection attribute. A literal string is
expanded to the contents of the string with no wrapping quotes.
A literal integer is expanded to the string representation of the
number. [1] |
node | A JSON formatted string representation of a trait or nested property
of a trait. The JSON is not pretty-printed, meaning there is no
indentation or newlines inserted into the JSON output for formatting.
For example, a template of |
projection | Expands to a list that starts with |
service | Expands to the absolute shape ID of a service shape [1]. |
trait | Expands to nothingness [1]. |
[1] | (1, 2, 3, 4, 5) This is the same behavior that is used when the attribute is used in a string comparison. |
17.6.2. EmitNoneSelector#
Emits a validation event if no shape in the model matches the given selector.
- Rationale
- Detecting the omission of a specific trait, pattern, or other requirement can help developers to remember to apply constraint traits, documentation, etc.
- Default severity
DANGER
- Configuration
Property Type Description selector string
Required. A valid selector. If no shape in the model is returned by the selector, then a validation event is emitted.
The following example detects if the model does not contain any constraint traits.
$version: "2"
metadata validators = [{
name: "EmitNoneSelector"
id: "MissingConstraintTraits"
message: """
No instances of the enum, pattern, length, or range trait
could be found. Did you forget to apply these traits?"""
configuration: {
selector: ":is([trait|enum], [trait|pattern], [trait|length], [trait|range])"
}
}]
17.7. traitValidators
trait#
It's sometimes necessary to constrain the set of shapes that can be
referenced when certain traits are applied to a shape. For example, some
protocols don't support event streams or document types. When that kind of
protocol trait is applied to a service and the service references such a
shape, a validation event should be emitted automatically. This can be
achieved without writing code by applying a traitValidators
trait to a
trait definition.
- Summary
- A meta-trait used to limit the kinds of shapes that can be referenced by a shape when a trait is applied to the shape.
- Trait selector
[trait|trait]
- Value type
- Map of event ID strings to validator definition objects.
Example
The following example defines a protocol that does not support document types.
$version: "2"
namespace smithy.example
@trait(selector: "service")
@traitValidators(
"myCustomProtocol.NoDocuments": {
selector: "~> member :test(> document)"
message: "myCustomProtocol does not support document types."
}
)
@protocolDefinition
structure myCustomProtocol {}
If the trait is applied to the following service:
@myCustomProtocol
service MyService {
operations: [GetFoo]
}
operation GetFoo {
input := {
document: Document
}
}
It will emit the following event:
── ERROR ──────────────────────────────────────── myCustomProtocol.NoDocuments
Shape: smithy.example#GetFooInput$document
File: example.smithy:22:9
21| input := {
22| document: Document
| ^
Found an incompatible shape when validating the constraints of the
smithy.example#myCustomProtocol trait attached to smithy.example#MyService:
myCustomProtocol does not support document types.
17.7.1. Map key event ID#
The key of the map is the event ID to use with each emitted validation event.
The event MUST adhere to the smithy:Namespace
syntax.
17.7.2. Validator definition#
Property | Type | Description |
---|---|---|
selector | string |
Required. A Smithy selector that receives only the applied shape. Any shape yielded by the selector is considered incompatible with the trait and causes a validation event. For example, the following selector would emit an event for any member in the closure of the applied shape that targets a boolean shape: ~> member :test(> boolean)
The following selector would emit an event only if the trait is bound to an operation that defines an input shape with a member named "foo": -[input]-> structure > member [id|member='foo']
|
message | string |
Additional context to include in the message of each emitted validation event. |
severity | string |
The severity to use when an
incompatible shape is found. Defaults to ERROR if not set. |