16. Selectors#
A Smithy selector is a domain specific language (DSL) used to match shapes within a model. Selectors are used to build custom validators and to specify where it is valid to apply a trait.
For information about how to use selectors within a code generator, see Using the Semantic Model.
16.1. Introduction#
A selector is used to traverse a model as a graph and find matching shapes. A model can be thought of as a traversable graph, more specifically, as a labeled multidigraph: each shape in a model is a labeled graph vertex, and each shape ID and member is a labeled edge. Shapes that are one of the aggregate types or service types have named relationships to neighbors that they reference or are connected to. For example, a list shape has a member that targets a shape; thus, the list shape is connected to the member shape, and the member shape is connected to the targeted shape.
16.2. Matching shapes with selectors#
Every shape in a model is sent through a selector, one at a time. The individual shape that is sent through a selector is called the starting shape. When given a shape, a selector yields zero or more matching shapes. Any variables defined while evaluating a starting shape are cleared when evaluating the next starting shape.
Selectors can be composed of multiple selectors. Selectors are evaluated from left to right, yielding the results from one selector to the next. The following selector matches all string shapes marked as sensitive:
string [trait|sensitive]
The result of evaluating a selector is either the set of shapes that are yielded from each starting shape, or for more advanced use cases, the list of all shapes yielded by a selector along with the variables currently assigned when the shape was yielded.
See also
Refer to EmitEachSelector for more information on how to use selector results.
16.3. Matching shapes by type#
Shapes can be matched by type using the following tokens:
Token | Description |
---|---|
* |
Matches all shapes |
number |
Matches all byte , short , integer , long , float ,
double , bigDecimal , and bigInteger shapes |
simpleType |
Matches all simple types |
collection |
Deprecated: An alias of list . Also matches set shapes in Smithy IDL 1.0. |
blob |
Matches blob shapes |
boolean |
Matches boolean shapes |
document |
Matches document shapes |
string |
Matches string shapes |
integer |
Matches integer shapes |
byte |
Matches byte shapes |
short |
Matches short shapes |
long |
Matches long shapes |
float |
Matches float shapes |
double |
Matches double shapes |
bigDecimal |
Matches bigDecimal shapes |
bigInteger |
Matches bigInteger shapes |
timestamp |
Matches timestamp shapes |
list |
Matches list shapes. Note that set shapes also match list because
they are considered sub-types of list. |
set |
Deprecated: Matches set shapes. This is considered an alias for list . |
map |
Matches map shapes |
structure |
Matches structure shapes |
union |
Matches union shapes |
service |
Matches service shapes |
operation |
Matches operation shapes |
resource |
Matches resource shapes |
member |
Matches member shapes |
The following selector matches shapes in a model:
*
The following selector matches all numbers defined in a model:
number
16.4. Attribute selectors#
Attribute selectors are used to match shapes based on shape IDs, traits, and other attributes.
16.4.1. Attribute existence#
An attribute existence check tests for the existence of an attribute without any kind of comparison.
The following selector matches shapes that are marked with the deprecated trait:
[trait|deprecated]
Projection values are only considered to exist if they yield one or more results.
The following selector matches shapes that have an enum trait,
the trait contains at least one enum
entry, and one or more entries
contains a non-empty tags
list.
[trait|enum|(values)|tags|(values)]
16.4.2. Attribute comparison#
An attribute selector with a comparator
checks for the existence of an attribute and compares the resolved
attribute value to a comma separated list of possible values. The
resolved attribute value on the left hand side of the comparator MUST
match one or more of the comma separated values on the right hand
side of the comparator.
There are three kinds of comparators:
16.4.3. String comparators#
String comparators
are used to compare
the string representation of values. Attributes that do not have a string
representation are treated as an empty string when these comparisons are
performed.
Comparator | Description |
---|---|
= |
Matches if the attribute value is equal to the comparison value. This comparator never matches if either value does not exist. The following selector matches shapes in the "smithy.example" namespace. [id|namespace = 'smithy.example']
The following selector matches shapes that have the since trait
with a value of [trait|since = 2019, 2020]
|
!= |
Matches if the attribute value is not equal to the comparison value. This comparator never matches if either value does not exist. The following selector matches shapes that are not in the "smithy.example" namespace. [id|namespace != 'smithy.example']
|
^= |
Matches if the attribute value starts with the comparison value. This comparator never matches if either value does not exist. The following selector matches shapes where the name starts with "_". [id|name ^= '_']
|
$= |
Matches if the attribute value ends with the comparison value. This comparator never matches if either value does not exist. The following selector matches shapes where the name ends with "_". [id|name $= '_']
|
*= |
Matches if the attribute value contains the comparison value. This comparator never matches if either value does not exist. The following selector matches shapes where the name contains "_". [id|name *= '_']
|
?= |
Matches based on the existence of a value. This comparator uses the
same rules defined in Attribute existence. The comparator
matches if the value exists and the right hand side of the comparator
is The following selector matches shapes marked as [trait|required ?= true]
|
String comparisons can be made case-insensitive by preceding the closing
bracket with i
.
The following selector matches shapes that have a httpQuery trait that case-insensitively contains the word "token":
[trait|httpQuery *= token i]
16.4.4. Numeric comparators#
Relative comparators only match if both values being compared contain valid
smithy:Number
productions when converted to a string.
Comparator | Description |
---|---|
> |
Matches if the attribute value is greater than the comparison value. The following selector matches shapes with an httpError trait value that is greater than 500: [trait|httpError > 500]
|
>= |
Matches if the attribute value is greater than or equal to the comparison value. |
< |
Matches if the attribute value is less than the comparison value. |
<= |
Matches if the attribute value is less than or equal to the comparison value. |
If either value is not a valid number, then the selector does not match.
The following selector does not match any shapes because the comparison value is not a valid number:
[trait|httpError >= "not a number!"]
16.5. Attributes#
Attribute selectors return objects that MAY have nested properties. Objects returned from selectors MAY be available to cast to a string. Shapes support the following attributes:
Attribute | Description |
---|---|
id | Returns an object that contains the shape ID of a shape. |
trait | Returns an object that contains the traits applied to a shape. |
service | Returns an object that contains information about service shapes. |
var | Returns an object that contains the variables currently in scope. |
Nested properties of an attribute object can be selected using subsequent
pipe (|
) delimited property names.
The following selector matches shapes that have a range trait
with a min
property set to 1
:
[trait|range|min = 1]
Whitespace is insignificant. The following selector is equivalent to the above selector:
[trait | range
| min = 1 ]
16.5.1. id
attribute#
The id
attribute of a shape returns an object that contains information
about the shape ID of a shape. When used as a string, id
contains the
absolute shape ID of a shape.
The following selector matches only the foo.baz#Structure
shape:
[id = foo.baz#Structure]
Matching on a shape ID that contains a member requires that the shape ID is enclosed in single or double quotes:
[id = 'foo.baz#Structure$foo']
Properties
The id
attribute can be used as an object, and it supports the
following properties.
namespace
Gets the
smithy:Namespace
part of a shape ID.The following selector matches shapes in the
foo.baz
namespace:[id|namespace = 'foo.baz']
name
Gets the name part of a shape ID.
The following selector matches shapes named
MyShape
.[id|name = MyShape]
member
Gets the member part of a shape ID (if available). If the shape ID does not contain a member, an empty value is returned.
The following selector matches all members in the model that have a member name of
foo
.[id|member = foo]
(length)
The
(length)
property returns the length of the absolute shape ID.The following selector matches shapes where the absolute shape ID is longer than 80 characters:
[id|(length) > 80]
Note that the
(length)
property can also be applied to the result of thenamespace
,name
, andmember
properties.The following selector matches shapes where the member name is longer than 20 characters:
[id|member|(length) > 20]
16.5.2. service
attribute#
The service
attribute is an object that is available for service shapes.
The following selector matches all service shapes:
[service]
However, the intent of the above selector is more clearly stated using the following selector:
service
When compared to a string value, the service
attribute returns the
absolute shape ID of the service shape.
The following selector matches all service shapes with a shape ID of
smithy.example#MyService
:
[service = smithy.example#MyService]
Properties
The service
attribute supports the following properties:
id
- Returns the service shape ID as an id attribute.
version
Gets the version property of a service shape as a string.
The following selector matches all service shapes that have a version property that starts with
2018-
:[service|version ^= '2018-']
16.5.3. trait
attribute#
The trait
attribute returns an object that contains every trait applied
to a shape. The trait
attribute supports the following properties:
(keys)
The
(keys)
property returns a projection that contains the shape ID of every trait applied to a shape.The following selector matches shapes that apply a trait from the
smithy.example
namespace:[trait|(keys)|namespace = 'smithy.example']
(values)
The
(values)
property returns a projection that contains every trait attached to a shape as a node value.The following selector matches shapes that apply a trait that contains a top-level structure member named
tags
:[trait|(values)|tags]
(length)
The
(length)
property returns the number of traits applied to a shape.The following selector matches shapes with more than 10 traits applied to it:
[trait|(length) > 10]
*
Any other value is treated as a shape ID, where a relative shape ID is resolved to the
smithy.api
namespace. If a matching trait with the given shape ID is attached to the shape, it's node value is returned. An empty value is returned if the trait does not exist.The following selector matches shapes that have the deprecated trait:
[trait|smithy.api#deprecated]
Traits in the
smithy.api
namespace MAY be retrieved from thetrait
attribute without a namespace.[trait|deprecated]
Traits are converted to their serialized
node
form when matching against their values. Only string, boolean, and numeric values can be compared using a string comparator. Boolean values are converted to "true" or "false". Numeric values are converted to their string representation.The following selector matches shapes with the error trait set to
client
:[trait|error = client]
The following selector matches shapes that have the error trait where its value is not
client
:[trait|error != client]
The following selector matches shapes with the documentation trait with a value that contains "TODO" or "FIXME":
[trait|documentation *= TODO, FIXME]
Note
The trait
attribute returns an empty string when compared with
a string comparator.
16.5.4. Node attribute#
A node attribute is created by retrieving nested values from a trait
attribute. The node value created from a trait is defined in Trait node values.
A node that contains a string, number, or boolean value is converted to a
string value when used by string comparators
(where a boolean creates a string containing "true" or "false"). Other node
values return empty strings when used by string comparators.
Properties
(keys)
When applied to an object node, the
(keys)
property returns a projection that contains all of the keys of the object. When applied to any other kind of node, an empty value is returned.The following selector matches shapes that have an externalDocumentation trait with an entry named
Homepage
:[trait|externalDocumentation|(keys) = Homepage]
(values)
When applied to an array node, the
(values)
property returns a projection that contains every value in the array. When applied to an object node,(values)
returns a projection that contains every value in the object. When applied to any other kind of node, an empty value is returned.The following selector matches shapes that have an enum trait where one or more of the enum definitions has a
tags
property list in which one or more values in the list equalsinternal
:[trait|enum|(values)|tags|(values) = internal]
(length)
When applied to an array node, the
(length)
property returns the number of items in the array. When applied to an object node, the(length)
property returns the number of entries in the object. When applied to a string node, the(length)
property returns the number of characters in the string. When applied to any other kind of node, an empty value is returned.The following selector matches shapes that have a documentation trait value that is less than 3 characters:
[trait|documentation|(length) < 3]
*
Properties of an object node can be accessed by name.
The following selector matches shapes that have an
externalDocumentation
trait that defines an entry namedReference Docs
:[trait|externalDocumentation|'Reference Docs']
Attempting to access a nested property that does not exist or attempting to descend into nested values of a scalar type returns an empty value.
16.5.5. Empty attribute#
Attempting to access a trait that does not exist, a variable that does not exist, or attempting to descend into node attribute values that do not exist returns an empty value. An empty value does not satisfy existence checks, returns an empty string when used with string comparators, and returns an empty value when attempting to access any properties.
The following selector attempts to descend into non-existent properties of the documentation trait. This example MUST NOT cause an error and MUST NOT match any shapes:
[trait|documentation|invalid|child = Hi]
16.5.6. Projection attribute#
A projection is created using the (keys)
or (values)
property of
a trait attribute or
node attribute.
Properties
Projections support the following properties:
(first)
- Recursively flattens the values of a projection and returns the first value. Projections are unordered. This property SHOULD only be used when the contents of a projection are known to have a single value.
*
- All other property access is forwarded to each value stored in the projection, and the results are returned in a new projection.
16.5.6.1. Comparisons to non-projections#
When a projection is compared against a value that is not also a projection, the comparison matches if any value in the projection satisfies the comparator assertion against the other value.
The following selector matches shapes that have a tags trait that
contains a value that is the string literal value foo
:
[trait|tags|(values) = foo]
16.5.6.2. Comparisons to projections#
When a projection is compared against another projection using a string comparator or numeric comparator, the comparison matches if any value in the left projection satisfies the comparator when compared against any value in the right projection.
To illustrate an example, the following model defines a trait,
allowedTags
, that is meant to constrain the set of tags that can appear
in the closure of a service.
namespace smithy.example
@trait(selector: "service")
list allowedTags {
member: String
}
@allowedTags(["internal", "external"])
service MyService {
version: "2020-04-28"
operations: [OperationA, OperationB, OperationC, OperationD]
}
operation OperationA {
input: OperationAInput
}
@tags(["internal"])
operation OperationB {}
@tags(["internal", "external"])
operation OperationC {}
@tags(["invalid"])
operation OperationD {}
@input
structure OperationAInput {
badValue: BadEnum
goodValue: GoodEnum
}
@enum([
{value: "a", tags: ["internal"]}
{value: "b", tags: ["invalid"]}
])
string BadEnum
@enum([
{value: "a"}
{value: "b", tags: ["internal", "external"]}
{value: "c", tags: ["internal"]}
])
string GoodEnum
The following selector finds all shapes within the closure of a service
that applies the allowedTags
trait, where the shape applies a
tags
trait that is not part of the allowedTags
trait.
service
[trait|smithy.example#allowedTags]
$service(*)
~>
[trait|tags]
:not([@: @{trait|tags|(values)} = @{var|service|trait|smithy.example#allowedTags|(values)}])
When the above selector is applied to the example model, it matches the
smithy.example#OperationD
shape because it uses a tags
value of
invalid
.
It might be useful to also ensure that tags
added inside of enum
traits adhere to the allowedTags
trait. For example, the
smithy.example#BadEnum
shape has an enum
definition that contains
an invalid tag, invalid
. The following selector tries, and fails,
to find all shapes that apply the enum
trait where one of the enum
definitions uses a tag that is not allowed.
service
[trait|smithy.example#allowedTags]
$service(*)
~>
[trait|enum]
:not([@: @{trait|enum|(values)|tags|(values)}
= @{var|service|trait|smithy.example#allowedTags|(values)}])
The above selector fails to match any shapes in the model because of how
projections are compared. The @{trait|enum|(values)|tags|(values)}
value creates a projection that contains every tags
value found in
every enum
trait value of a shape. The
@{var|service|trait|smithy.example#allowedTags|(values)}
attribute
creates a projection that gets the set of allowedTags
from the previously
captured service
variable. Because BadEnum
defines both a valid and invalid enum
tags
value, it satisfies the
=
comparator, which is then negated with the :not function,
which means the shape does not match the selector. Projection comparators are
needed to solve this problem.
Building on the previous example, a projection comparator
can be used to correctly find shapes in which an enum
trait uses tags
that are not part of the set of allowedTags
.
service
[trait|smithy.example#allowedTags]
$service(*)
~>
[trait|enum]
:not([@: @{trait|enum|(values)|tags|(values)}
{<} @{var|service|trait|smithy.example#allowedTags|(values)}])
16.5.6.3. Projection comparators#
Projection comparators are used to compare projections to test if they are
equal, not equal, a subset, or a proper subset to another projection. With
the exception of the {!=}
comparator, projection comparators match if and
only if both the left hand side of the comparator and the right hand side of
the comparator are projections.
Comparator | Description |
---|---|
{=} |
Matches if every value in the left hand side can be found in
the right hand side using the = comparator for equality.
Projection comparisons are unordered, and the projections are not
required to have the same number of items. |
{!=} |
This comparator is the negation of the result of {=} . Comparing
a projection to a non-projection value will always return true . |
{<} |
Matches if the left projection is a subset of the right
projection. Every value in the left projection MUST be found
in the right projection using the = comparator for equality. |
{<<} |
Matches if the left projection is a proper subset of the right
projection. Every value in the left projection MUST be found in
the right projection using the = comparator for equality,
but the projections themselves are not equal, meaning that the left
projection is missing one or more values found in the right
projection. |
16.6. Scoped attribute selectors#
A scoped attribute selector
is similar to an
attribute selector, but it allows multiple complex comparisons to be made
against a scoped attribute.
16.6.1. Context values#
The first part of a scoped attribute selector is the attribute that is scoped
for the expression, followed by :
. The scoped attribute is accessed using
a context value
in the form of
@{
smithy:Identifier
}
.
In the following selector, the trait|range
attribute is used as the scoped
attribute of the expression, and the selector matches shapes marked with
the range trait where the min
value is greater than the max
value:
[@trait|range: @{min} > @{max}]
The scope can also be set to the current shape being evaluated by omitting
an expression before the :
character.
The following selector matches shapes that are traits that are applied
to themselves as traits (for example, this matches smithy.api#trait
,
smithy.api#documentation
, etc.):
[trait|trait][@: @{trait|(keys)} = @{id}]
A projection MAY be used as the scoped attribute context value. When the scoped attribute context value is a projection, each recursively flattened value of the projection is individually tested against each assertion. If any value from the projection matches the assertions, then the selector matches the shape.
The following selector matches shapes that have an enum trait where one
or more of the enum definitions is both marked as deprecated
and contains
an entry in its tags
property named deprecated
.
[@trait|enum|(values):
@{deprecated} = true &&
@{tags|(values)} = "deprecated"]
16.6.2. And-logic#
Scoped attribute selector assertions can be combined together using
and statements with &&
. All assertions MUST match in order for
the selector to match.
The following selector matches shapes with the idRef trait that
set failWhenMissing
to true
and omit an errorMessage
:
[@trait|idRef:
@{failWhenMissing} = true &&
@{errorMessage} ?= false]
16.6.3. Matching multiple values#
Like non-scoped selectors, multiple values can be provided using a comma separated list. One or more resolved attribute values MUST match one or more provided values.
The following selector matches shapes with the httpApiKeyAuth trait
where the in
property is header
and the name
property is neither
x-api-token
or authorization
:
[@trait|httpApiKeyAuth:
@{name} = header &&
@{in} != 'x-api-token', 'authorization']
16.6.4. Case insensitive comparisons#
The i
token used before &&
or the closing ]
makes a comparison
case-insensitive.
The following selector matches on the httpApiKeyAuth
trait using
case-insensitive string comparisons:
[@trait|httpApiKeyAuth:
@{name} = header i &&
@{in} != 'x-api-token', 'authorization' i]
The following selector matches on the httpApiKeyAuth
trait but only
uses a case-insensitive comparison on in
:
[@trait|httpApiKeyAuth:
@{name} = header &&
@{in} != 'x-api-token', 'authorization' i]
16.7. Neighbors#
Neighbor selectors yield shapes that are connected to the current shape. Most selectors are used to determine if a shape matches some criteria, meaning the selector yields zero or exactly one shape. However, neighbor selectors yield zero or more shapes by traversing the relationships of a shape.
16.7.1. Forward undirected neighbor#
A forward undirected neighbor
(>
) yields every shape referred to by the current shape. For example, the
following selector matches the key and value members of every map:
map > member
Neighbors can be chained to traverse further into a shape. The following selector yields strings that are targeted by list members:
list > member > string
16.7.2. Forward directed neighbors#
The forward undirected neighbor selector (>
) is an undirected edge
traversal. Sometimes, a directed edge traversal is necessary. For example,
the following selector matches the "input", "output", "error", and "mixin"
relationships of each operation:
operation > *
A forward directed edge traversal is applied using selectors:SelectorForwardDirectedNeighbor
(-[X, Y, Z]->
). The following selector matches all structure shapes
referenced as operation input
or output
.
operation -[input, output]-> structure
The :test function can be used to check if a shape has a named relationship. The following selector matches all resource shapes that define an identifier:
resource :test(-[identifier]->)
The following selector matches all shapes targeted by a resource property of MyResource:
resource [id|name=MyResource] -[property]->
Relationships from a shape to the traits applied to the shape can be traversed
using a forward directed relationship named trait
. It is atypical to
traverse trait
relationships, therefore they are only yielded by
selectors when explicitly requested using a trait
directed relationship.
The following selector finds all service shapes that have a protocol trait
applied to it (that is, a trait that is marked with the
protocolDefinition trait):
service :test(-[trait]-> [trait|protocolDefinition])
16.7.3. Forward recursive neighbors#
The forward recursive neighbor selector (~>
) yields all shapes that are
recursively connected in the closure of another shape. The shapes yielded
by this selector are equivalent to yielding every shape connected to the
current shape using a forward undirected neighbor, yielding every shape
connected to those shapes, and so on.
The following selector matches operations that are connected to a service:
service ~> operation
The following selector finds operations that do not have the http trait
that are in the closure of a service marked with the aws.protocols#restJson
trait:
service[trait|aws.protocols#restJson1]
~> operation:not([trait|http])
16.7.4. Reverse undirected neighbor#
A reverse undirected neighbor yields all of the shapes that have a relationship that points to the current shape.
The following selector matches strings that are targeted by members of lists:
string :test(< member < list)
The following selector yields all shapes that are not traits and are not referenced by other shapes:
:not([trait|trait]) :not(< *)
The following selectors are equivalent; however, a forward neighbor traversal is preferred over a reverse neighbor traversal when possible.
* Reverse: string < member < list
* Forward: list :test(> member > string)
16.7.5. Reverse directed neighbor#
A reverse directed neighbor yields all of the shapes that have a relationship of a specific type that points to the current shape.
For example, shapes marked with the streaming trait can only be targeted by top-level members of operation input or output structures. The following selector finds all shapes that target a streaming shape and violate this constraint:
[trait|streaming]
:test(<)
:not(< member < structure <-[input, output]- operation)
Like forward directed neighbors, trait
relationships are only included
when explicitly provided in the list of relationships to traverse. The
following selector yields all shapes that are traits that are not applied
to any shapes:
[trait|trait] :not(<-[trait]-)
16.7.6. Relationships#
The table below lists the labeled directed relationships from each shape.
Shape | Relationship | Description |
---|---|---|
service | operation | Each operation bound to a service. |
service | resource | Each resource bound to a service. |
service | error | Each error structure referenced by the service. |
resource | identifier | Each identifier shape of a resource. |
resource | property | Each property shape of a resource. |
resource | resource | Each resource bound to a resource. |
resource | operation | Each operation bound to a resource through the "operations" property. |
resource | collectionOperation | Each operation bound to a resource through the "collectionOperations" property. |
resource | create | The operation defined as the Create lifecycle of a resource. |
resource | read | The operation defined as the Read lifecycle of a resource. |
resource | update | The operation defined as the Update lifecycle of a resource. |
resource | delete | The operation defined as the Delete lifecycle of a resource. |
resource | list | The operation defined as the List lifecycle of a resource. |
resource | put | The operation defined as the Put lifecycle of a resource. |
operation | input | The input structure of an operation. Note smithy.api#Unit is considered "not present" for this relationship, and will not be yielded. |
operation | output | The output structure of an operation. Note smithy.api#Unit is considered "not present" for this relationship, and will not be yielded. |
operation | error | Each error structure of an operation. |
list | member | The member of a list. |
map | member | The key and value members of a map. |
structure | member | Each structure member. |
union | member | Each union member. |
enum | member | Each enum member. |
intEnum | member | Each intEnum member. |
member | The shape targeted by the member. Note that member targets have no relationship name. | |
* |
trait | Each trait applied to a shape. The neighbor shape is the shape that
defines the trait. This kind of relationship is only traversed if the
trait relationship is explicitly stated as a desired directed
neighbor relationship type (for example, -[trait]-> ). |
* |
mixin | Every mixin applied to the shape. Note A normal |
Note
Implementations MAY tolerate parsing unknown relationship types. When evaluated, the traversal of unknown relationship types SHOULD yield nothing.
16.8. Functions#
Functions are used to filter and yield shapes using a variadic argument
list of selectors separated by a comma (,
). Functions always start with
a colon (:
).
Important
Implementations MUST tolerate parsing unknown function names. When evaluated, an unknown function yields no shapes.
16.8.1. :test
#
The :test
function is used to test if a shape is matched by any of the
provided predicate selectors. The :test
function stops testing predicates
and yields the current shape as soon as the first predicate yields a shape.
The following selector is used to match all list shapes that target a string:
list:test(> member > string)
The above selector is very different from the following selector because the following selector returns only string shapes that are targeted by the members of list shapes:
list > member > string
The following selector matches shapes that are bound to a resource (for example, identifiers, operations, child resources) and have no documentation:
:test(< resource) :not([trait|documentation])
16.8.2. :is
#
The :is
function passes the current shape to each selector and
yields the shapes yielded by each selector.
The following selector yields string and number shapes:
:is(string, number)
The following selector yields string and number shapes that are targeted by a member:
member > :is(string, number)
The following selector yields shapes that are either targeted by a list member or targeted by a map member:
:is(list > member > *, map > member > *)
Note
This function was previously named :each
. Implementations that wish
to maintain backward compatibility with the old function name MAY
treat :each
as an alias for :is
, and models that use :each
SHOULD update to use :is
.
16.8.3. :not
#
The :not
function is used to filter out shapes. This function MUST be
provided a single predicate selector argument. If the predicate
selector yields any shapes when given the current shape as input, then
the current shape is not yielded by the function.
The following selector does not yield string shapes:
:not(string)
The following selector does not yield string or float shapes:
:not(string) :not(float)
The following selector yields list shapes that do not target strings:
list :not(> member > string)
The following selector yields structure members that do not have the
length
trait, and the member targets a string that does not have
the length
trait:
structure > member
:not([trait|length])
:test(> string :not([trait|length]))
The following selector yields service shapes that do not have a protocol trait applied to it:
service :not(-[trait]-> [trait|protocolDefinition])
16.8.4. :in
#
The :in
function is used to test if a shape is contained within the
result of an expression. This function is most useful when testing if a
variable or the result of a
root function contains a shape. The :in
function requires exactly one selector. If a shape is contained in the
result of evaluating the selector, the shape is yielded from the function.
The following example finds all numbers that are used in service operation inputs and not used in service operation outputs:
service
$outputs(~> operation -[output]-> ~> number)
~> operation -[input]-> ~> number
:not(:in(${outputs}))
Note
The above example returns the aggregate results of applying the selector to every shape: if a model contains multiple services, and one of the services uses a number 'X' in input and not output, but another service uses 'X' in both input and output, 'X' is part of the matched shapes. Use the :root function to match shapes globally.
16.8.5. :root
#
The :root
function evaluates a subexpression against all shapes in the
model and yields all matches. The :root
function is useful for breaking
a selector down into smaller operations, and it works best when used with
variables or the :in function.
The :root
function requires exactly one selector.
The following example finds all numbers that are used in any operation inputs and not used in any operation outputs:
number
:in(:root(service ~> operation -[input]-> ~> number))
:not(:in(:root(service ~> operation -[output]-> ~> number)))
Note
The above example is similar to ":in example using variables" but works independent of services. That is, if a model contains multiple services, and one of the services uses a number 'X' in input and not output, but another service uses 'X' in both input and output, 'X' is not part of the matched shapes.
16.8.5.1. :root functions are isolated subexpressions#
The expression evaluated by a :root
expression is evaluated in an isolated
context from the rest of the expression. The selector provided to a :root
function cannot access variables defined outside the function, and variables
defined in the selector do not persist outside the selector.
16.8.5.2. :root functions are evaluated at most once#
There is no need to store the result of a :root
function in a variable
because :root
selector functions are considered global common
subexpressions and are evaluated at most once during the selection process.
Implementations MAY choose to evaluate :root
expressions eagerly or
lazily, though they MUST evaluate :root
expressions no more than once.
16.8.6. :recursive
#
The :recursive
function applies a selector to the current shape, and for
every shape yielded that was not previously yielded, applies the selector to
that shape. This happens recursively until all matching shapes have been
traversed. Shapes that match the selector are yielded by the function up until
the point that a downstream selector tells the recursive selector to stop.
The following example finds all shapes that have a specific mixin:
:recursive(-[mixin]->) [id=smithy.example#Foo]
The following selector finds all shapes contained within the resource hierarchy of a specific resource.
resource :test(:recursive(<-[resource]-) [id=smithy.example#Baz])
The following selector finds all shapes that directly or transitively target
a specific shape, essentially the inverse of ~>
.
[id=smithy.example#MyShape] :recursive(<)
16.8.7. :topdown
#
The :topdown
function matches service, resource, and operation shapes
and resource and operation shapes within their containment hierarchy. The
:topdown
function starts at each given shape and forward-traverses
the containment hierarchy of the shape by following operation
and
resource
relationships from the shape
to its neighbors; this function does not traverse up the containment
hierarchy of a given shape to check if the shape is within the containment
hierarchy of a qualified service or resource shape. This function essentially
allows shapes to be matched by inheriting from the resource or service they
are bound to.
16.8.7.1. Selector arguments#
Exactly one or two selectors MUST be provided to the :topdown
selector:
- The first selector is the "qualifier". It is used to mark a shape as a match. If the selector yields any results, then it is considered a match.
- If provided, the second selector is called the "disqualifier". It is used to remove the match flag for the current shape before traversing any resource and operation bindings of the current shape. If this selector yields any results, then the shape is not considered a match, and bound resources and operations are not considered a match until the qualifier selector matches again. Resource and operation binding traversal continues regardless of if the second selector removes the match flag for the current shape because resource and operation shapes bound to the current shape could yield matching results.
16.8.7.2. Examples#
The following selector finds all service, resource, and operation shapes that
are marked with the aws.api#dataPlane
trait or that are bound within the
containment hierarchy of resource and service shapes that are marked as such:
:topdown([trait|aws.api#dataPlane])
The following selector finds all service, resource, and operation shapes that
are marked with the aws.api#dataPlane
trait, but does not match shapes
where the aws.api#controlPlane
trait is used to override the
aws.api#dataPlane
trait. For example, if a service is marked with the
aws.api#dataPlane
trait to provide a default setting for all resources and
operations within the service, the aws.api#controlPlane
trait can be used
to override the default.
:topdown([trait|aws.api#dataPlane], [trait|aws.api#controlPlane])
The above selector applied to the following model matches Example
,
OperationA
, and OperationB
. It does not match Foo
because Foo
matches the disqualifier selector.
namespace smithy.example
@aws.api#dataPlane
service Example {
version: "2020-09-08"
resources: [Foo]
operations: [OperationA]
}
operation OperationA {}
@aws.api#controlPlane
resource Foo {
operations: [OperationB]
}
@aws.api#dataPlane
operation OperationB {}
In the following example, the :topdown
function does not inherit any
matches from service shapes because the selector only sends resource shapes
to the function. When applied to the previous example model, the following
selector matches only OperationB
.
resource :topdown([trait|aws.api#dataPlane], [trait|aws.api#controlPlane])
16.9. Variables#
Variables are used to store eagerly computed, named intermediate results that can be accessed later in a selector. Variables are useful for caching results that are computed multiples times in a selector or for capturing information about the current shape that is referenced later in a selector after traversing neighbors.
A variable is set using a selectors:SelectorVariableSet
expression.
Variables can be reassigned without error.
The following selector defines a variable named foo
that sets the
variable to the result of applying the *
selector to the current shape.
$foo(*)
A variable is retrieved by name using a selectors:SelectorVariableGet
expression. Retrieving a variable yields the set of shapes stored in the
variable. Attempting to get a variable that does not exist yields no shapes.
${foo}
Variables can also be accessed inside of scoped attribute selectors
from shapes using the var
attribute.
16.9.1. var
attribute#
A var attribute is an object accessible from a shape that provides
access to the named variables currently in scope.
Variables are accessed by providing the variable name after var
. The
values returned from var
are projections
that contain the set of shapes that were bound to the variable, or an
empty value if the variable does not exist.
The following selector finds all operations in the closure of a service where the operation has an auth trait that is not a subset of the authDefinition traits applied to the service.
service
$authTraits(-[trait]-> [trait|authDefinition])
~>
operation
[trait|auth]
:not([@: @{trait|auth|(values)} {<} @{var|authTraits|id}]))
Given the following model, the selector matches the HasDigestAuth
operation:
namespace smithy.example
@httpBasicAuth
@httpBearerAuth
service MyService {
version: "2020-04-21"
operations: [HasDigestAuth, HasBasicAuth, NoAuth]
}
@auth([httpDigestAuth])
operation HasDigestAuth {}
@auth([httpBasicAuth])
operation HasBasicAuth {}
operation NoAuth {}
The HasDigestAuth
operation is matched because it is bound within the
closure of MyService
, it has an auth
trait set to httpDigestAuth
,
and MyService
does not apply the httpDigestAuth
trait.
The above selector is equivalent to the following pseudo-code:
matched_shapes = set()
for model.shapes as current_shape:
# service
if current_shape.type != "service":
continue
# $authTraits(-[trait]-> [trait|authDefinition])
auth_traits = []
for current_shape.traits as trait:
if "smithy.api#authDefinition" in trait.traits:
auth_traits.append(trait)
# ~>
for current_shape.get_recursive_neighbors() as current_shape:
# operation
if current_shape.type != "operation":
continue
# [trait|auth]
if "smithy.api#auth" not in current_shape.traits:
continue
# :not([@: @{trait|auth|(values)} {<} @{var|authTraits|id}]))
__trait_auth_values_projection = current_shape.traits.get("smithy.api#auth").values
__auth_traits_id_projection = auth_traits.get("id")
if not __trait_auth_values_projection.issubset(__auth_traits_id_projection):
matched_shapes.add(current_shape)
16.10. Grammar#
Selectors are defined by the following ABNF grammar.
Lexical note
Whitespace is insignificant and can occur between any token without changing the semantics of a selector.
Selector =SelectorExpression
*(SelectorExpression
) SelectorExpression =SelectorShapeTypes
/SelectorAttr
/SelectorScopedAttr
/SelectorFunction
/SelectorForwardUndirectedNeighbor
/SelectorReverseUndirectedNeighbor
/SelectorForwardDirectedNeighbor
/SelectorForwardRecursiveNeighbor
/SelectorReverseDirectedNeighbor
/SelectorVariableSet
/SelectorVariableGet
SelectorShapeTypes = "*" /smithy:Identifier
SelectorForwardUndirectedNeighbor = ">" SelectorReverseUndirectedNeighbor = "<" SelectorForwardDirectedNeighbor = "-["SelectorDirectedRelationships
"]->" SelectorReverseDirectedNeighbor = "<-["SelectorDirectedRelationships
"]-" SelectorDirectedRelationships =smithy:Identifier
*(","smithy:Identifier
) SelectorForwardRecursiveNeighbor = "~>" SelectorAttr = "["SelectorKey
[SelectorAttrComparison
] "]" SelectorAttrComparison =SelectorComparator
SelectorAttrValues
["i"] SelectorKey =smithy:Identifier
["|"SelectorPath
] SelectorPath =SelectorPathSegment
*("|"SelectorPathSegment
) SelectorPathSegment =SelectorValue
/SelectorFunctionProperty
SelectorValue =SelectorText
/smithy:Number
/smithy:RootShapeId
SelectorFunctionProperty = "("smithy:Identifier
")" SelectorAttrValues =SelectorValue
*(","SelectorValue
) SelectorComparator =SelectorStringComparator
/SelectorNumericComparator
/SelectorProjectionComparator
SelectorStringComparator = "^=" / "$=" / "*=" / "!=" / "=" / "?=" SelectorNumericComparator = ">=" / ">" / "<=" / "<" SelectorProjectionComparator = "{=}" / "{!=}" / "{<}" / "{<<}" SelectorAbsoluteRootShapeId =smithy:Namespace
"#"smithy:Identifier
SelectorScopedAttr = "[@" [SelectorKey
] ":"SelectorScopedAssertions
"]" SelectorScopedAssertions =SelectorScopedAssertion
*("&&"SelectorScopedAssertion
) SelectorScopedAssertion =SelectorScopedValue
SelectorComparator
SelectorScopedValues
["i"] SelectorScopedValue =SelectorValue
/SelectorContextValue
SelectorContextValue = "@{"SelectorPath
"}" SelectorScopedValues =SelectorScopedValue
*(","SelectorScopedValue
) SelectorFunction = ":"smithy:Identifier
"("SelectorFunctionArgs
")" SelectorFunctionArgs =Selector
*(","Selector
) SelectorText =SelectorSingleQuotedText
/SelectorDoubleQuotedText
SelectorSingleQuotedText = "'" 1*SelectorSingleQuotedChar
"'" SelectorDoubleQuotedText = DQUOTE 1*SelectorDoubleQuotedChar
DQUOTE SelectorSingleQuotedChar = %x20-26 / %x28-5B / %x5D-10FFFF ; Excludes (') SelectorDoubleQuotedChar = %x20-21 / %x23-5B / %x5D-10FFFF ; Excludes (") SelectorVariableSet = "$"smithy:Identifier
"("Selector
")" SelectorVariableGet = "${"smithy:Identifier
"}"
16.11. Compliance Tests#
Selector compliance tests are used to verify the behavior of selectors. Each compliance test is written as a Smithy file
and includes a metadata called selectorTests
. This metadata contains a list of test cases, each including a selector,
the expected matched shapes, and additional configuration options. The test case contains the following properties:
Property | Type | Description |
---|---|---|
selector | string |
REQUIRED The selector to match shapes within the smithy model |
matches | list<shape ID> |
REQUIRED The expected shapes ID of the matched shapes |
skipPreludeShapes | boolean |
Skip prelude shapes when comparing the expected shapes and the actual shapes returned from the selector. Default value is false |
Below is an example selector compliance test:
$version: "2.0"
metadata selectorTests = [
{
selector: "[trait|length|min > 1]"
matches: [
smithy.example#AtLeastTen
]
}
{
selector: "[trait|length|min >= 1]"
skipPreludeShapes: true
matches: [
smithy.example#AtLeastOne
smithy.example#AtLeastTen
]
}
{
selector: "[trait|length|min < 2]"
skipPreludeShapes: true
matches: [
smithy.example#AtLeastOne
]
}
]
namespace smithy.example
@length(min: 1)
string AtLeastOne
@length(max: 5)
string AtMostFive
@length(min: 10)
string AtLeastTen
The compliance tests can also be accessed in this directory of the Smithy Github repository.