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 2019 or 2020:

[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 true, or if the value does not exist and the right hand side of the comparator is set to false. This selector is most useful in scoped attribute selectors.

The following selector matches shapes marked as required.

[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 the namespace, name, and member 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 the trait 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 equals internal:

[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 named Reference 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 member relationship exists from a given shape to all its mixed in members.

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:

:in example using variables#
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:

  1. 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.
  2. 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.