Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make selector attributes stricter #946

Merged
merged 1 commit into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions docs/source/1.0/spec/core/selectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -370,19 +370,6 @@ above selector:
[trait | range
| min = 1 ]

Accessing an attribute or nested attribute property that does 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 :ref:`documentation-trait`. This example MUST NOT cause an error and
MUST NOT match any shapes:

.. code-block:: none

[trait|documentation|invalid|child = Hi]


.. _id-attribute:

Expand Down Expand Up @@ -544,10 +531,11 @@ to a shape. The ``trait`` attribute supports the following properties:

[trait|(length) > 10]
``*``
Other values are treated as shape IDs, where a relative shape ID is
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 :ref:`node value <node-attribute>`
is returned. An empty value is returned if the trait does not exist.
is returned. An :ref:`empty value <empty-attributes>` is returned if the
trait does not exist.

The following selector matches shapes that have the
:ref:`deprecated-trait`:
Expand Down Expand Up @@ -661,6 +649,30 @@ values return empty strings when used by string comparators.

[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 :ref:`empty value <empty-attributes>`.


.. _empty-attributes:

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 :ref:`documentation-trait`. This example MUST NOT cause an error and
MUST NOT match any shapes:

.. code-block:: none

[trait|documentation|invalid|child = Hi]


.. _projection-attribute:

Expand Down Expand Up @@ -1491,8 +1503,8 @@ A *var attribute* is an object accessible from a shape that provides
access to the named :ref:`variables <selector-variables>` currently in scope.
Variables are accessed by providing the variable name after ``var``. The
values returned from ``var`` are :ref:`projections <projection-attribute>`
that contain the set of shapes that were bound to the variable, or an empty
value if the variable does not exist.
that contain the set of shapes that were bound to the variable, or an
:ref:`empty value <empty-attributes>` if the variable does not exist.

The following selector finds all operations in the closure of a service
where the operation has an :ref:`auth-trait` that is not a subset of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public AttributeValue getProperty(String key) {
} else if (key.equals("id")) {
return AttributeValue.id(service.getId());
} else {
return EMPTY;
throw new SelectorException("Invalid nested 'service' selector attribute property: " + key);
}
}
}
Expand Down Expand Up @@ -357,7 +357,7 @@ public AttributeValue getProperty(String property) {
// Length returns the length of the shape ID.
return AttributeValue.literal(id.toString().length());
default:
return EMPTY;
throw new SelectorException("Invalid nested 'id' selector attribute property: " + property);
}
}
}
Expand Down Expand Up @@ -445,8 +445,7 @@ public AttributeValue getProperty(String property) {
case "var":
return new VariableValue(vars);
default:
LOGGER.warning("Unsupported shape selector attribute: " + property);
return EMPTY;
throw new SelectorException("Invalid shape selector attribute: " + property);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.model.selector;

/**
* Exception thrown when a selector evaluation is invalid.
*/
public class SelectorException extends RuntimeException {
public SelectorException(String message) {
super(message);
}

public SelectorException(String message, Throwable previous) {
super(message, previous);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/**
* Exception thrown when a selector expression is invalid.
*/
public final class SelectorSyntaxException extends RuntimeException {
public final class SelectorSyntaxException extends SelectorException {
SelectorSyntaxException(String message, String expression, int pos, int line, int column) {
super(createMessage(message, expression, pos, line, column));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,8 @@ public void invalidNumbersFailsGracefully() {
}

@Test
public void toleratesGettingNullPropertyFromString() {
assertThat(ids(traitModel, "[id|no|no=100]"), empty());
public void doesNotToleratesInvalidIdAccess() {
Assertions.assertThrows(SelectorException.class, () -> ids(traitModel, "[id|no|no=100]"));
}

@Test
Expand Down Expand Up @@ -633,12 +633,8 @@ public void selectsServiceVersions() {
}

@Test
public void toleratesUnknownServicePaths() {
Set<String> services1 = ids(traitModel, "[service|foo|baz='bam']");
Set<String> services2 = ids(traitModel, "[service|foo|baz]");

assertThat(services1, empty());
assertThat(services2, empty());
public void doesNotTolerateUnknownServicePaths() {
Assertions.assertThrows(SelectorException.class, () -> ids(traitModel, "[service|foo|baz='bam']"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,14 +400,21 @@
"id": "ignored",
"name": "EmitEachSelector",
"configuration": {
"selector": "[foo]"
"selector": "[trait|notRealTrait|notFound]"
}
},
{
"id": "ignored",
"name": "EmitEachSelector",
"configuration": {
"selector": "[foo=baz]"
"selector": "[var|notRealVar|notFound]"
}
},
{
"id": "ignored",
"name": "EmitEachSelector",
"configuration": {
"selector": "[trait|error|nesting|into|things|is|tolerated]"
}
},
{
Expand Down