Selectors¶
A Smithy selector is a domain specific language (DSL) used to match specific shapes within a model. Selectors are used to build custom validators and to specify where it is valid to apply a trait.
Table of contents
Introduction¶
A loaded Smithy 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.
Matching shapes with selectors¶
Every shape in a model, including members defined in shapes of aggregate types, is sent through a selector, and all of the shapes returned from the selector are the matching shapes.
Matching shapes by type¶
Shapes can be matched by type using the following tokens:
blob
, boolean
, document
, string
, integer
, byte
,
short
, long
, float
, double
, bigDecimal
, bigInteger
,
timestamp
, list
, map
, set
, structure
, union
,
service
, operation
, resource
, member
, number
,
simpleType
, collection
, *
.
number
matches allbyte
,short
,integer
,long
,float
,double
,bigDecimal
, andbigInteger
shapes.simpleType
matches all simple types.collection
matches both alist
andset
shape.*
matches all shapes.
The following selector matches all string shapes in a model:
string
Attribute selectors¶
Attribute selectors are used to match shapes based on the shape ID, traits, and member target. Attribute selectors take one of two forms: existence of an attribute and comparison of an attribute value to an expected value.
Attribute selectors support the following comparators:
Comparator | Description |
---|---|
= |
Matches if the attribute value is equal to the expected value. |
^= |
Matches if the attribute value starts with the expected value. |
$= |
Matches if the attribute value ends with the expected value. |
*= |
Matches if the attribute value contains with the expected value. |
Attribute comparisons can be made case-insensitive by preceding the closing
bracket with " i" (e.g., string[trait|time=DATE i]
).
Matching traits¶
We can match shapes based on traits using an attribute selector. The following selector finds all structure shapes with the error trait trait:
structure[trait|error]
The trait|
is called a namespace prefix. This particular prefix tells
the selector that we are interested in a trait applied to the current shape,
and that that specific trait is time
.
We can match string shapes that have a specific trait value:
structure[trait|error=client]
Matching on trait values only works for traits that have a scalar value (e.g., strings, numbers, and booleans). We can also match case-insensitvely on the value by appending " i" before the closing bracket:
structure[trait|error=CLIENT i]
Fully-qualified trait names are also supported:
string[trait|smithy.example#customTrait=foo]
Matching on shape ID¶
Attribute selectors can be used to match the shape ID. The
following example matches a single resource shape with an ID of
smithy.example#Foo
:
resource[id='smithy.example#Foo']
Notice that the value of an attribute selector can be quoted. The example above uses single quotes, but double quotes work too.
Smithy provides several attributes in the id
namespace to make matching
on a shape ID easier. The following example finds all shapes that are in the
"smithy.example" namespace:
resource[id|namespace=smithy.example]
Though not as clear, matching shapes in a specific namespace can also be
achieved using the ^=
comparator against id
:
resource[id^=smithy.example#]
The following example matches all member shapes that have a member name of "key":
resource[id|member=key]
Though not as clear, matching members with a member name of "key" can also be
achieved using the $=
comparator against id
:
resource[id$="$key"]
Available attributes¶
Attribute | Description | Example result |
---|---|---|
id |
The full shape ID of a shape | foo.baz#Structure$memberName |
id|namespace |
The namespace part of a shape ID | foo.baz |
id|name |
The name part of a shape ID | Structure |
id|member |
The member part of a shape ID (if available) | memberName |
service|version |
Gets the version property of a service shape if the shape is a service. | service[service|version^='2018-'] |
trait|* |
Gets the value of a trait applied to a shape, where "*" is the name
of a trait (e.g., trait|error ). Boolean trait values are
converted to "true" or "false". |
client |
Neighbors¶
The current shape evaluated by a selector is changed using a neighbor token,
>
. A neighbor token returns every shape that is connected to the current
shape. For example, the following selector returns the key and value members of
every map:
map > member
We can return just the key members or just the value members by adding an
attribute selector on the id|member
:
map > member[id|member=key]
Neighbors can be chained to traverse further into a shape. The following selector returns strings that are targeted by list members:
list > member > string
Directed neighbors¶
The >
neighbor selector is an undirected edge traversal. Sometimes a
directed edge traversal is necessary to match the appropriate shapes. For
example, the following selector returns the "bound", "input", "output",
and "errors" relationships of each operation:
operation > *
A directed edge traversal can be performed using the -[
token followed
by a comma separated list of relationships,
followed by ]->
. The following selector matches all structure
shapes referenced as operation input or output.
operation -[input, output]->
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]->)
Relationships¶
The table below lists the labeled directed relationships from each shape.
Shape | Relationship | Description |
---|---|---|
service | operation | Each operation that is bound to a service. |
service | resource | Each resource that is bound to a service. |
resource | identifier | The identifier referenced by the resource (if specified). |
resource | operation | Each operation that is bound to a resource through the "operations", "create", "put", "read", "update", "delete", and "list" properties. |
resource | instanceOperation | Each operation that is bound to a resource through the "operations", "put", "read", "update", and "delete" properties. |
resource | collectionOperation | Each operation that is bound to a resource through the "collectionOperations", "create", and "list" properties. |
resource | resource | Each resource that is bound to a resource. |
resource | create | The operation referenced by the Create lifecycle property of a resource (if present). |
resource | read | The operation referenced by the Read lifecycle property of a resource (if present). |
resource | update | The operation referenced by the Update lifecycle property of a resource (if present). |
resource | delete | The operation referenced by the Delete lifecycle property of a resource (if present). |
resource | list | The operation referenced by the List lifecycle property of a resource (if present). |
resource | bound | The service or resource to which the resource is bound. |
operation | bound | The service or resource to which the operation is bound. |
operation | input | The input structure of the operation (if present). |
operation | output | The output structure of the operation (if present). |
operation | error | Each error structure referenced by the operation (if present). |
list | member | The member of the list. Note that this is not the shape targeted by the member. |
map | member | The key and value members of the map. Note that these are not the shapes targeted by the member. |
structure | member | Each structure member. Note that these are not the shapes targeted by the members. |
union | member | Each union member. Note that these are not the shapes targeted by the members. |
member | The shape targeted by the member. Note that member targets have no relationship name. |
Functions¶
Functions are used to filter shapes. Functions always start with :
.
:each¶
The :each
function is used to map over the current shape with multiple
selectors and returns all of the shapes returned from each selector. The
:each
function accepts a variadic list of selectors each separated by a
comma (",").
The following selector matches all string and number shapes:
:each(string, number)
Each can be used inside of neighbors too. The following selector matches all members that target a string or number:
member > :each(string, number)
The following :each
selector matches all shapes that are either
targeted by a list member or targeted by a map member:
:each(list > member > *, map > member > *)
The following selector matches all list and map shapes that target strings:
:each(:test(list > member > string), :test(map > member > string))
Because none of the selectors in the :each
function are intended to
change the current node, this can be reduced to the following selector:
:test(:each(list > member > string, map > member > string))
:test¶
The :test
function is used to test if a shape is contained within any of
the provided predicate selector return values without changing the current
shape.
The following selector is used to match all string and number shapes:
:test(string, number)
The :test
function is much more interesting when used to test if a shape
contains a neighbor in addition to other filtering. The following example
matches all shapes that are bound to a resource and have no documentation:
:test(-[bound, resource]->) :not([trait|documentation])
:not¶
The :not function is used to filter out shapes. This function accepts a list of selector arguments, and the shapes returned from each predicate are filtered out from the result set.
The following selector matches every shape except strings:
:not(string)
The following selector matches every shape except strings and floats:
:not(string, float)
The following example matches all shapes except for strings that are targeted by a list member:
:not(list > member > string)
Important
The shapes returned from the predicate selectors are filtered out.
The :test
function can be used to test a shape, potentially traversing its
neighbors, without changing the return value of the test. The following
example does not match any list shape that has a string member:
:not(:test(list > member > string))
Successive :not
functions can be used to filter shapes using several
predicates. The following example does not match strings or shapes with the
sensitive trait trait:
:not(string):not([trait|sensitive])
Multiple selectors can be provided to :not
to find shapes that do not
match all of the provided predicates. The following selector finds all
string shapes that do not have both the length
and pattern
traits:
string:not([trait|length], [trait|pattern])
The following example matches all structure members that target strings in
which the member does not have the length
trait and the shape targeted by
the member does not have the length
trait:
structure > member
:test(> string:not([trait|length]))
:test(:not([trait|length]))
:of¶
The :of
function is used to match members based on their containers
(i.e., the shape that defines the member). The :of
function accepts one
or more selector arguments. Each selector receives the containing shape
of the member, and if any of the selectors return returns 1 or more shapes,
the member is matched.
The following example matches all structure members:
member:of(structure)
The following example matches all structure and list members:
member:of(structure, list)
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 =selector_expression
*(selector_expression
) selector_expression =shape_types
/attr
/function_expression
/neighbors
shape_types = "*" / "blob" / "boolean" / "document" / "string" / "byte" / "short" / "integer" / "long" / "float" / "double" / "bigDecimal" / "bigInteger" / "timestamp" / "list" / "map" / "set" / "structure" / "union" / "service" / "operation" / "resource" / "member" / "number" / "simpleType" / "collection" neighbors = ">" /directed_neighbor
directed_neighbor = "-["relationship_type
*(","relationship_type
) "]->" relationship_type = "identifier" / "create" / "read" / "update" / "delete" / "list" / "member" / "input" / "output" / "error" / "operation" / "collectionOperation" / "instanceOperation" / "resource" / "bound" attr = "["attr_key
*(comparator
attr_value
["i"]) "]" attr_key =id_attribute
/trait_attribute
/service_attribute
id_attribute = "id" ["|" ("namespace" / "name" / "member")] trait_attribute = "trait" "|"attr_value
*("|"attr_value
) attr_value =attr_identifier
/selector_text
attr_identifier = 1*(ALPHA / DIGIT / "_") *(ALPHA / DIGIT / "_" / "-" / "." / "#") service_attribute = "service|version" comparator = "^=" / "$=" / "*=" / "=" function_expression = ":"function
"("selector
*(","selector
) ")" function = "each" / "test" / "of" / "not" selector_text =selector_single_quoted_text
/selector_double_quoted_text
selector_single_quoted_text = "'" 1*selector_single_quoted_char
"'" selector_double_quoted_text = DQUOTE 1*selector_double_quoted_char
DQUOTE selector_single_quoted_char = %x20-26 / %x28-5B / %x5D-10FFFF ; Excludes (') selector_double_quoted_char = %x20-21 / %x23-5B / %x5D-10FFFF ; Excludes (")