Skip to content

Commit

Permalink
Add support for stringArray rules engine Parameters (#2266)
Browse files Browse the repository at this point in the history
  • Loading branch information
alextwoods authored May 2, 2024
1 parent e52e625 commit a6f1d1c
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 20 deletions.
17 changes: 12 additions & 5 deletions docs/source-2.0/additional-specs/rules-engine/parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ This following is the :rfc:`ABNF <5234>` grammar for rule set parameter names:
.. productionlist:: smithy
identifier = ALPHA *(ALPHA / DIGIT)
Parameters declare their respective type using the ``type`` key. There are two
supported rule set parameter types: ``string`` and ``boolean``. The following
table provides the description of these types, and their Smithy compatible
Parameters declare their respective type using the ``type`` key. The following
parameter types are supported: ``string``, ``boolean``, and ``stringArray``.
The following table provides the description of these types, and their Smithy compatible
types whose values can be bound to these parameters. Rule set parameters are
always considered nullable and have no default value associated with them.

Expand All @@ -42,6 +42,9 @@ always considered nullable and have no default value associated with them.
* - ``boolean``
- ``boolean``
- Boolean value type.
* - ``stringArray``
- ``list``
- A list with ``string`` members.


.. _rules-engine-parameters-implementation:
Expand Down Expand Up @@ -219,13 +222,14 @@ The ``staticContextParam`` structure has the following properties:
* - value
- ``document``
- **Required**. The static value to be set for the parameter. The type
of the value MUST be either a ``string`` or ``boolean``.
of the value MUST be either a ``string``, ``boolean`` or an
array of ``string``.

Each parameter is identified using it’s name as specified in the rule set. The
type of a ``staticContextParam`` MUST be compatible with the parameter type
specified in the rule set.

The following example specifies two parameters to statically set for an
The following example specifies three parameters to statically set for an
operation:

.. code-block:: smithy
Expand All @@ -236,6 +240,9 @@ operation:
}
previewEndpoint: {
value: true
},
supportedPrefixes: {
value: ["host", "id", "resourceId"]
}
)
operation GetThing {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ A parameter object contains the following properties:
- Description
* - type
- ``string``
- **Required**. MUST be one of ``string`` or ``boolean``.
- **Required**. MUST be one of ``string``, ``boolean``, or ``stringArray``.
* - builtIn
- ``string``
- Specifies a named built-in value that is sourced and provided to the
endpoint provider by a caller.
* - default
- ``string`` or ``boolean``
- ``string``, ``boolean`` or an array of ``string``.
- Specifies the default value for the parameter if not set. Parameters
with defaults MUST also be marked as ``required``. The type of the
provided default MUST match ``type``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ static Type fromParameterType(ParameterType parameterType) {
if (parameterType == ParameterType.BOOLEAN) {
return booleanType();
}
if (parameterType == ParameterType.STRING_ARRAY) {
return arrayType(stringType());
}
throw new IllegalArgumentException("Unexpected parameter type: " + parameterType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public Type getType() {
} else {
Type first = values.get(0).getType();
for (Value value : values) {
if (value.getType() != first) {
if (!value.getType().isA(first)) {
throw new SourceException("An array cannot contain different types", this);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ private static List<Part> parse(String path, FromSourceLocation sourceLocation)
throw new InvalidRulesException("Invalid path component: slice index must be >= 0",
sourceLocation);
}
result.add(Part.Key.of(component.substring(0, slicePartIndex)));
if (slicePartIndex > 0) {
result.add(Part.Key.of(component.substring(0, slicePartIndex)));
}
result.add(new Part.Index(slice));
} catch (NumberFormatException ex) {
throw new InvalidRulesException(String.format("%s could not be parsed as a number", slicePart),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.rulesengine.language.error.RuleError;
import software.amazon.smithy.rulesengine.language.evaluation.type.ArrayType;
import software.amazon.smithy.rulesengine.language.evaluation.type.BooleanType;
import software.amazon.smithy.rulesengine.language.evaluation.type.StringType;
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
Expand All @@ -28,7 +29,12 @@ public enum ParameterType {
/**
* A "boolean" parameter type.
*/
BOOLEAN;
BOOLEAN,

/**
* An array (list) of strings parameter type.
*/
STRING_ARRAY;

/**
* Creates a {@link ParameterType} of a specific type from the given Node information.
Expand All @@ -39,14 +45,18 @@ public enum ParameterType {
*/
public static ParameterType fromNode(StringNode node) throws RuleError {
String value = node.getValue();
if (value.equalsIgnoreCase("String")) {
if (value.equalsIgnoreCase("string")) {
return STRING;
}
if (value.equalsIgnoreCase("Boolean")) {
if (value.equalsIgnoreCase("boolean")) {
return BOOLEAN;
}
if (value.equals("stringArray")) {
return STRING_ARRAY;
}
throw new RuleError(new SourceException(
String.format("Unexpected parameter type `%s`. Expected `String` or `Boolean`.", value), node));
String.format("Unexpected parameter type `%s`. Expected `string`, `boolean`, or `stringArray`.",
value), node));
}

/**
Expand All @@ -63,6 +73,17 @@ public static ParameterType fromNode(Node node) throws RuleError {
if (node.isBooleanNode()) {
return BOOLEAN;
}
if (node.isArrayNode()) {
// confirm all elements are Strings
node.expectArrayNode().getElements().forEach(memberNode -> {
if (!memberNode.isStringNode()) {
throw new RuleError(new SourceException(
String.format("Unexpected array member parameter type `%s`. Expected a string.",
memberNode.getType()), memberNode));
}
});
return STRING_ARRAY;
}
throw new RuleError(new SourceException(
String.format("Unexpected parameter type `%s`. Expected a string or boolean.", node.getType()), node));
}
Expand All @@ -81,8 +102,14 @@ public static ParameterType fromType(Type type) {
if (type instanceof BooleanType) {
return BOOLEAN;
}
if (type instanceof ArrayType) {
ArrayType arrayType = (ArrayType) type;
if (arrayType.getMember().isA(Type.stringType()) || arrayType.getMember().isA(Type.emptyType())) {
return STRING_ARRAY;
}
}
throw new RuntimeException(
String.format("Unexpected parameter type `%s`. Expected a string or boolean.", type));
String.format("Unexpected parameter type `%s`. Expected a string, boolean, or array<string>.", type));
}

/**
Expand All @@ -105,6 +132,16 @@ public static ParameterType fromShapeType(ShapeType type) {

@Override
public String toString() {
return this == STRING ? "String" : "Boolean";
// Inconsistent casing on string/boolean to preserve backwards compatibility in serialization
switch (this) {
case STRING:
return "String";
case BOOLEAN:
return "Boolean";
case STRING_ARRAY:
return "stringArray";
default:
return "Unknown Type";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public List<ValidationEvent> validate(Model model) {
.orElse(Collections.emptyMap());
for (Map.Entry<String, StaticContextParamDefinition> entry : definitionMap.entrySet()) {
Node node = entry.getValue().getValue();
if (node.isStringNode() || node.isBooleanNode()) {
if (supportedType(node)) {
continue;
}
events.add(error(operationShape,
Expand All @@ -45,4 +45,17 @@ public List<ValidationEvent> validate(Model model) {
}
return events;
}

private static boolean supportedType(Node node) {
if (node.isStringNode() || node.isBooleanNode()) {
return true;
}

if (node.isArrayNode()) {
// all elements must be strings
return node.expectArrayNode().getElements().stream().allMatch(e -> e.isStringNode());
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[WARNING] example#FizzBuzz: This shape applies a trait that is unstable: smithy.rules#clientContextParams | UnstableTrait
[WARNING] example#FizzBuzz: This shape applies a trait that is unstable: smithy.rules#endpointRuleSet | UnstableTrait
[WARNING] example#FizzBuzz: This shape applies a trait that is unstable: smithy.rules#endpointTests | UnstableTrait
[WARNING] example#GetThing: This shape applies a trait that is unstable: smithy.rules#staticContextParams | UnstableTrait.smithy.rules#staticContextParams
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace example
use smithy.rules#clientContextParams
use smithy.rules#endpointRuleSet
use smithy.rules#endpointTests
use smithy.rules#staticContextParams

@clientContextParams(
bar: {type: "string", documentation: "a client string parameter"}
Expand All @@ -30,10 +31,16 @@ use smithy.rules#endpointTests
default: "asdf"
documentation: "docs"
},
stringArrayParam: {
type: "stringArray",
required: true,
default: ["a", "b", "c"],
documentation: "docs"
}
},
rules: [
{
"documentation": "Template the region into the URI when FIPS is enabled",
"documentation": "Template baz into URI when bar is set",
"conditions": [
{
"fn": "isSet",
Expand All @@ -49,6 +56,25 @@ use smithy.rules#endpointTests
},
"type": "endpoint"
},
{
"documentation": "Template first array value into URI",
"conditions": [
{
"fn": "getAttr",
"argv": [
{
"ref": "stringArrayParam"
},
"[0]"
],
"assign": "arrayValue"
}
],
"endpoint": {
"url": "https://example.com/{arrayValue}"
},
"type": "endpoint"
},
{
"conditions": [],
"documentation": "error fallthrough",
Expand Down Expand Up @@ -94,6 +120,19 @@ use smithy.rules#endpointTests
}
},
{
"documentation": "Default array values used"
"params": {
}
"expect": {
"endpoint": {
"url": "https://example.com/a"
}
}
},
{
"params": {
"stringArrayParam": []
}
"documentation": "a documentation string",
"expect": {
"error": "endpoint error"
Expand All @@ -106,6 +145,9 @@ service FizzBuzz {
operations: [GetThing]
}

@staticContextParams(
"stringArrayParam": {value: []}
)
operation GetThing {
input := {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ use smithy.rules#staticContextParams
"ExtraParameter": {
"type": "string",
"documentation": "docs"
},
"StringArrayParameter": {
"type": "stringArray",
documentation: "docs"
}
},
"rules": []
Expand All @@ -38,7 +42,8 @@ service FizzBuzz {

@staticContextParams(
"ParameterFoo": {value: "foo"},
"ExtraParameter": {value: "someValue"}
"ExtraParameter": {value: "someValue"},
"StringArrayParameter": {value: ["a", "b", "c"]}
)
operation GetResource {
input: GetResourceInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// while parsing the parameter `RegionName`
// at invalid-rules/invalid-param-type.json5:10
// while parsing the parameter type
// Unexpected parameter type `notastring`. Expected `String` or `Boolean`.
// Unexpected parameter type `notastring`. Expected `string`, `boolean`, or `stringArray`.
// at invalid-rules/invalid-param-type.json5:11
{
"version": "1.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace smithy.example

use smithy.rules#staticContextParams

@staticContextParams(arrayParam: {value: ["foo", "bar"]})
@staticContextParams(arrayParam: {value: ["foo", 3]})
operation OperationArray {}

0 comments on commit a6f1d1c

Please sign in to comment.