Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "feature",
"description": "Added the `@smithy.contracts#conditions` trait, available in the new `smithy-contract-traits` package. This trait defines restrictions on shape values using JMESPath expressions." ,
"pull_requests": [
"[#2935](https://github.com/smithy-lang/smithy/pull/2935)"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "feature",
"description": "Added a service provider interface for `NodeValidationVisitor` plugins, and optimized to index plugins by the `ShapeType` they apply to.",
"pull_requests": [
"[#2935](https://github.com/smithy-lang/smithy/pull/2935)"
]
}
83 changes: 83 additions & 0 deletions docs/source-2.0/additional-specs/contract-traits.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. _contract-traits:

===============
Contract traits
===============

Contract traits are used to further constrain the valid values and behaviors of a model.
Like constraint traits, contract traits are for validation only and SHOULD NOT
impact the types signatures of generated code.

--------------------------
Contract trait enforcement
--------------------------

Contract traits provide structured documentation of implicit API constraints,
and are useful for generating tests or applying static analysis to client or service code.

Contract traits SHOULD NOT be directly enforced by default when serializing or deserializing.
These traits often express contracts using higher-level constructs and simpler but less efficient expressions.
Services will usually check these contracts outside of service frameworks in more efficient ways.

.. smithy-trait:: smithy.contracts#conditions
.. _conditions-trait:

--------------------
``conditions`` trait
--------------------

Summary
Restricts shape values to those which satisfy the given JMESPath expressions.
Trait selector
``:not(:test(service, operation, resource))``

*Any shape other than services, operations, and resources*
Value type
``map``

The ``conditions`` trait is a map from condition names to ``Condition`` structures that contain
the following members:

.. list-table::
:header-rows: 1
:widths: 10 23 67

* - Property
- Type
- Description
* - expression
- ``string``
- **Required**. JMESPath_ expression that must evaluate to true.
* - documentation
- ``string``
- **Required**. Documentation about the condition defined using CommonMark_.

See the :ref:`JMESPath data model <waiter-jmespath-data-model>` for details on how Smithy types are mapped to JMESPath types.

.. code-block:: smithy

@conditions({
StartBeforeEnd: {
description: "The start time must be strictly less than the end time",
expression: "start < end"
}
})
structure FetchLogsInput {
@required
start: Timestamp

@required
end: Timestamp
}

@conditions({
NoKeywords: {
description: "The name cannot contain either 'id' or 'name', as these are reserved keywords"
expression: "!contains(@, 'id') && !contains(@, 'name')"
}
})
string Name


.. _CommonMark: https://spec.commonmark.org/
.. _JMESPath: https://jmespath.org/
2 changes: 2 additions & 0 deletions docs/source-2.0/additional-specs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ start with ``smithy.*`` where "*" is anything other than ``api``.
:caption: smithy.* specifications

ai-traits
contract-traits
http-protocol-compliance-tests
smoke-tests
waiters
mqtt
rules-engine/index
protocols/index


.. seealso::

* :doc:`../spec/index`
Expand Down
1 change: 1 addition & 0 deletions docs/source-2.0/additional-specs/waiters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ support the following members:
``expression`` with the ``expected`` value. The string value MUST
be a valid :ref:`PathComparator-enum`.

.. _waiter-JMESPath-data-model:

JMESPath data model
-------------------
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ include(":smithy-utils")
include(":smithy-protocol-test-traits")
include(":smithy-jmespath")
include(":smithy-jmespath-tests")
include(":smithy-model-jmespath")
include(":smithy-waiters")
include(":smithy-aws-cloudformation-traits")
include(":smithy-aws-cloudformation")
Expand All @@ -41,3 +42,4 @@ include(":smithy-protocol-traits")
include(":smithy-protocol-tests")
include(":smithy-trait-codegen")
include(":smithy-docgen")
include(":smithy-contract-traits")
16 changes: 16 additions & 0 deletions smithy-contract-traits/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
plugins {
id("smithy.module-conventions")
}

description = "This module provides Smithy traits for declaring contracts on models."

extra["displayName"] = "Smithy :: Contract Traits"
extra["moduleName"] = "software.amazon.smithy.contract.traits"

dependencies {
api(project(":smithy-model-jmespath"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.contracts;

import software.amazon.smithy.jmespath.JmespathException;
import software.amazon.smithy.jmespath.JmespathExpression;
import software.amazon.smithy.model.FromSourceLocation;
import software.amazon.smithy.model.SourceException;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.node.ExpectationNotMetException;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.ToNode;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* Defines an individual condition.
*/
public final class Condition implements ToNode, ToSmithyBuilder<Condition>, FromSourceLocation {
private final SourceLocation sourceLocation;
private final String expressionText;
private final JmespathExpression expression;
private final String documentation;

private Condition(Builder builder) {
this.sourceLocation = SmithyBuilder.requiredState("sourceLocation", builder.sourceLocation);
this.expressionText = SmithyBuilder.requiredState("expression", builder.expression);
try {
this.expression = JmespathExpression.parse(expressionText);
} catch (JmespathException e) {
throw new SourceException(
"Invalid condition JMESPath expression: `" + expressionText + "`. " + e.getMessage(),
builder.sourceLocation);
}
this.documentation = SmithyBuilder.requiredState("documentation", builder.documentation);
}

@Override
public Node toNode() {
return Node.objectNodeBuilder()
.withMember("expression", Node.from(expressionText))
.withMember("documentation", Node.from(documentation))
.build();
}

/**
* Creates a {@link Condition} from a {@link Node}.
*
* @param node Node to create the Condition from.
* @return Returns the created Condition.
* @throws ExpectationNotMetException if the given Node is invalid.
*/
public static Condition fromNode(Node node) {
Builder builder = builder().sourceLocation(node.getSourceLocation());
node.expectObjectNode()
.expectStringMember("expression", builder::expression)
.expectStringMember("documentation", builder::documentation);
return builder.build();
}

@Override
public SourceLocation getSourceLocation() {
return sourceLocation;
}

/**
* JMESPath expression that must evaluate to true.
*
* @return Return the JMESPath expression.
*/
public JmespathExpression getExpression() {
return expression;
}

/**
* Documentation about the condition.
*
* @return Return the documentation.
*/
public String getDocumentation() {
return documentation;
}

@Override
public Builder toBuilder() {
return builder()
.expression(expressionText)
.documentation(documentation);
}

/**
* Creates a builder used to build an equivalent {@link Condition}.
* @return the builder.
*/
public static Builder builder() {
return new Builder();
}

/**
* Builder for {@link Condition}.
*/
public static final class Builder implements SmithyBuilder<Condition> {
private SourceLocation sourceLocation;
private String expression;
private String documentation;

private Builder() {}

public Builder sourceLocation(SourceLocation sourceLocation) {
this.sourceLocation = sourceLocation;
return this;
}

public Builder expression(String expression) {
this.expression = expression;
return this;
}

public Builder documentation(String documentation) {
this.documentation = documentation;
return this;
}

@Override
public Condition build() {
return new Condition(this);
}
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (!(other instanceof Condition)) {
return false;
} else {
Condition b = (Condition) other;
return toNode().equals(b.toNode());
}
}

@Override
public int hashCode() {
return toNode().hashCode();
}
}
Loading
Loading