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": "Add new transform compileBdd and compileBddForAws",
"pull_requests": [
"[#2953](https://github.com/smithy-lang/smithy/pull/2953)"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ on the scenario.
This trait is experimental and subject to change.

Summary
A Binary `Decision Diagram (BDD) <https://en.wikipedia.org/wiki/Binary_decision_diagram>`_ representation of
A `Binary Decision Diagram (BDD) <https://en.wikipedia.org/wiki/Binary_decision_diagram>`_ representation of
endpoint rules that is more compact and efficient at runtime than the decision-tree-based EndpointRuleSet trait.
Trait selector
``service``
Expand All @@ -108,8 +108,11 @@ runtime performance and reduced artifact sizes.

.. note::

The ``endpointBdd`` trait can be generated from an ``endpointRuleSet`` trait through compilation. Services may
provide either trait, with ``endpointBdd`` preferred for production use due to its performance characteristics.
The ``endpointBdd`` trait can be generated from an ``endpointRuleSet`` trait through compilation.
To generate the ``endpointBdd`` trait for a service, add the :ref:`compileBdd <compileBdd-transform>` transform
to the ``smithy-build.json`` file.
Services may provide either trait, with ``endpointBdd`` preferred for production use due to
its performance characteristics.

The ``endpointBdd`` structure has the following properties:

Expand Down
33 changes: 33 additions & 0 deletions docs/source-2.0/guides/smithy-build-json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,39 @@ Only the following shape type changes are supported:

.. seealso:: :ref:`changeStringEnumsToEnumShapes`

.. _compileBdd-transform:

compileBdd
-----------------------------

This transform compiles `Binary Decision Diagram (BDD) <https://en.wikipedia.org/wiki/Binary_decision_diagram>`_
from service shape's :ref:`@endpointRuleSet <smithy.rules#endpointRuleSet-trait>` trait and attaches
the compiled :ref:`@endpointBdd <smithy.rules#endpointBdd-trait>` trait to the service shape.

.. code-block:: json

{
"version": "1.0",
"projections": {
"exampleProjection": {
"transforms": [
{
"name": "compileBdd"
}
]
}
},
"maven": {
"dependencies": [
"software.amazon.smithy:smithy-rules-engine:__smithy_version__"
]
}
}

.. note::
AWS services like ``S3`` can have special tree transformations that dramatically improve the BDD compiled result
both in size and performance. To use them, please use the dedicated transform ``compileBddForAws`` and
include the dependency of ``software.amazon.smithy:smithy-aws-endpoints:__smithy_version__``.

.. _excludeShapesBySelector-transform:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@
*/
package software.amazon.smithy.rulesengine.aws.language.functions;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.build.TransformContext;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.ModelSerializer;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.rulesengine.analysis.BddCoverageChecker;
import software.amazon.smithy.rulesengine.aws.s3.S3TreeRewriter;
import software.amazon.smithy.rulesengine.aws.transforms.CompileBddForAws;
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
import software.amazon.smithy.rulesengine.language.evaluation.TestEvaluator;
import software.amazon.smithy.rulesengine.logic.bdd.CostOptimization;
Expand Down Expand Up @@ -154,4 +159,14 @@ private void writeModelWithBddTrait(EndpointBddTrait bddTrait) {
throw new RuntimeException("Failed to write S3 BDD model", e);
}
}

@Test
void compileBddForS3AddedTrait() {
TransformContext context = TransformContext.builder()
.model(model)
.build();
Model result = new CompileBddForAws().transform(context);
Shape serviceShape = result.expectShape(S3_SERVICE_ID);
assertTrue(serviceShape.hasTrait(EndpointBddTrait.ID));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rulesengine.aws.transforms;

import static software.amazon.smithy.rulesengine.transforms.CompileBdd.compileBdd;

import java.util.HashSet;
import java.util.Set;
import software.amazon.smithy.build.ProjectionTransformer;
import software.amazon.smithy.build.TransformContext;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.transform.ModelTransformer;
import software.amazon.smithy.rulesengine.aws.s3.S3TreeRewriter;
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
import software.amazon.smithy.rulesengine.language.evaluation.TestEvaluator;
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
import software.amazon.smithy.rulesengine.traits.EndpointTestCase;
import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait;

/**
* A dedicated transform to compile Binary Decision Diagram (BDD) from AWS services.
*/
public final class CompileBddForAws implements ProjectionTransformer {

private static final ShapeId S3_SERVICE_ID = ShapeId.from("com.amazonaws.s3#AmazonS3");

@Override
public String getName() {
return "compileBddForAws";
}

@Override
public Model transform(TransformContext transformContext) {
Model model = transformContext.getModel();
Set<Shape> shapes = new HashSet<>();
for (ServiceShape serviceShape : model.getServiceShapes()) {
if (serviceShape.hasTrait(EndpointRuleSetTrait.ID)
&& !serviceShape.hasTrait(EndpointBddTrait.ID)) {
EndpointRuleSet rules = getEndpointRuleSet(serviceShape);
EndpointBddTrait bdd = compileBdd(rules);
shapes.add(serviceShape.toBuilder().addTrait(bdd).build());
}
}
return ModelTransformer.create().replaceShapes(model, shapes);
}

private EndpointRuleSet getEndpointRuleSet(ServiceShape serviceShape) {
EndpointRuleSet rules = serviceShape.expectTrait(EndpointRuleSetTrait.class).getEndpointRuleSet();
if (serviceShape.getId().equals(S3_SERVICE_ID)) {
return applyS3Transform(rules, serviceShape);
}
return rules;
}

private EndpointRuleSet applyS3Transform(EndpointRuleSet rules, ServiceShape serviceShape) {
EndpointRuleSet transformedRules = S3TreeRewriter.transform(rules);
serviceShape.getTrait(EndpointTestsTrait.class).ifPresent(testsTrait -> {
for (EndpointTestCase testCase : testsTrait.getTestCases()) {
TestEvaluator.evaluate(transformedRules, testCase);
}
});
return transformedRules;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
software.amazon.smithy.rulesengine.aws.transforms.CompileBddForAws
1 change: 1 addition & 0 deletions smithy-rules-engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extra["moduleName"] = "software.amazon.smithy.rulesengine"

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

import java.util.HashSet;
import java.util.Set;
import software.amazon.smithy.build.ProjectionTransformer;
import software.amazon.smithy.build.TransformContext;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.transform.ModelTransformer;
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
import software.amazon.smithy.rulesengine.logic.bdd.CostOptimization;
import software.amazon.smithy.rulesengine.logic.bdd.SiftingOptimization;
import software.amazon.smithy.rulesengine.logic.cfg.Cfg;
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;

/**
* Compiles a Binary Decision Diagram (BDD) from a service's {@code @endpointRuleSet}
* trait and attaches the compiled {@code @endpointBdd} trait to the service shape.
*/
public final class CompileBdd implements ProjectionTransformer {

@Override
public String getName() {
return "compileBdd";
}

@Override
public Model transform(TransformContext transformContext) {
Model model = transformContext.getModel();
Set<Shape> shapes = new HashSet<>();
for (ServiceShape serviceShape : model.getServiceShapes()) {
if (serviceShape.hasTrait(EndpointRuleSetTrait.ID)
&& !serviceShape.hasTrait(EndpointBddTrait.ID)) {
EndpointRuleSetTrait endpointRuleSetTrait = serviceShape.expectTrait(EndpointRuleSetTrait.class);
EndpointBddTrait bdd = compileBdd(endpointRuleSetTrait.getEndpointRuleSet());
shapes.add(serviceShape.toBuilder().addTrait(bdd).build());
}
}
return ModelTransformer.create().replaceShapes(model, shapes);
}

/**
* Compile endpointBdd trait from endpoint ruleset and return the optimized version.
*
* @param rules Endpoint ruleset from service shape.
*/
public static EndpointBddTrait compileBdd(EndpointRuleSet rules) {
// Create the CFG to start BDD compilation process.
Cfg cfg = Cfg.from(rules);
EndpointBddTrait unoptimizedTrait = EndpointBddTrait.from(cfg);

// Sift the BDD to shorten paths and reduce the BDD size.
EndpointBddTrait siftedTrait = SiftingOptimization.builder().cfg(cfg).build().apply(unoptimizedTrait);

// "cost optimize" the BDD to ensure cheap conditions come first with up to 10% size impact.
EndpointBddTrait costOptimizedTrait = CostOptimization.builder().cfg(cfg).build().apply(siftedTrait);

// Remove unreferenced conditions. This is destructive and further optimizations cannot be applied after this.
return costOptimizedTrait.removeUnreferencedConditions();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
software.amazon.smithy.rulesengine.transforms.CompileBdd
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rulesengine.transforms;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.build.TransformContext;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;

public class CompileBddTest {

@Test
public void compilesAndAttachesBddTrait() throws Exception {
ShapeId serviceId = ShapeId.from("smithy.example#ExampleService");
Model model = Model.assembler()
.discoverModels()
.addImport(Paths.get(getClass().getResource("compile-bdd.smithy").toURI()))
.assemble()
.unwrap();
TransformContext context = TransformContext.builder()
.model(model)
.build();
Model result = new CompileBdd().transform(context);
Shape serviceShape = result.expectShape(serviceId);
assertTrue(serviceShape.hasTrait(EndpointBddTrait.ID));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
$version: "2.0"

namespace smithy.example

use smithy.rules#clientContextParams
use smithy.rules#endpointRuleSet
use smithy.rules#endpointTests

@clientContextParams(
Region: {type: "string", documentation: "docs"}
)
@endpointRuleSet({
"version": "1.1"
"parameters": {
"Region": {
"required": true
"type": "String"
"documentation": "docs"
}
}
"rules": [
{
"conditions": []
"documentation": "base rule"
"endpoint": {
"url": "https://{Region}.amazonaws.com"
"properties": {}
"headers": {}
}
"type": "endpoint"
}
]
})
@endpointTests({
"version": "1.0"
"testCases": [
{
"documentation": "example endpoint test"
"expect": {
"endpoint": {
"url": "https://example-region.amazonaws.com"
}
}
"params": {
Region: "example-region"
}
}
]
})
service ExampleService {}
Loading