Skip to content

Rule-Engine: Experimental Context Traits #1248

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

Merged
merged 1 commit into from
Aug 18, 2022
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
6 changes: 6 additions & 0 deletions config/spotbugs/filter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,10 @@
<Class name="software.amazon.smithy.model.knowledge.PropertyBindingIndex"/>
<Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"/>
</Match>

<!-- This is a false positive. The value is guarded behind an Objects.requireNonNull -->
<Match>
<Class name="software.amazon.smithy.rulesengine.traits.ContextIndex"/>
<Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"/>
</Match>
</FindBugsFilter>
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ include ":smithy-waiters"
include ":smithy-aws-cloudformation-traits"
include ":smithy-aws-cloudformation"
include ":smithy-validation-model"
include ":smithy-rules-engine"
26 changes: 26 additions & 0 deletions smithy-rules-engine/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2022 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.
*/

description = "Smithy rules engine Language and traits"

ext {
displayName = "Smithy :: Rules Engine"
moduleName = "software.amazon.smithy.rulesengine"
}

dependencies {
api project(":smithy-model")
api project(":smithy-utils")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2022 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.
*/

/**
* Smithy Rules Engine.
*/
@SmithyUnstableApi
package software.amazon.smithy.rulesengine;

import software.amazon.smithy.utils.SmithyUnstableApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2022 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.rulesengine.traits;

import java.util.Objects;
import java.util.Optional;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.SmithyUnstableApi;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* A service client context parameter definition.
*/
@SmithyUnstableApi
public final class ClientContextParamDefinition implements ToSmithyBuilder<ClientContextParamDefinition> {
private final ShapeType type;
private final String documentation;

private ClientContextParamDefinition(Builder builder) {
this.type = SmithyBuilder.requiredState("type", builder.type);
this.documentation = builder.documentation;
}

public static Builder builder() {
return new Builder();
}

public ShapeType getType() {
return type;
}

public Optional<String> getDocumentation() {
return Optional.ofNullable(documentation);
}

public Builder toBuilder() {
return builder()
.type(type)
.documentation(documentation);
}

@Override
public int hashCode() {
return Objects.hash(getType(), getDocumentation());
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClientContextParamDefinition that = (ClientContextParamDefinition) o;
return getType() == that.getType() && Objects.equals(getDocumentation(), that.getDocumentation());
}

public static final class Builder implements SmithyBuilder<ClientContextParamDefinition> {
private ShapeType type;
private String documentation;

private Builder() {
}

public Builder type(ShapeType type) {
this.type = type;
return this;
}

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

public ClientContextParamDefinition build() {
return new ClientContextParamDefinition(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2022 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.rulesengine.traits;

import java.util.LinkedHashMap;
import java.util.Map;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.AbstractTrait;
import software.amazon.smithy.model.traits.AbstractTraitBuilder;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.utils.BuilderRef;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.SmithyUnstableApi;
import software.amazon.smithy.utils.ToSmithyBuilder;

/**
* Indicates that the named rule-set parameters that should be configurable
* on the service client using the specified smithy types.
*/
@SmithyUnstableApi
public final class ClientContextParamsTrait extends AbstractTrait implements ToSmithyBuilder<ClientContextParamsTrait> {
public static final ShapeId ID = ShapeId.from("smithy.rules#clientContextParams");

private final Map<String, ClientContextParamDefinition> parameters;

public ClientContextParamsTrait(Builder builder) {
super(ID, builder.getSourceLocation());
this.parameters = builder.parameters.copy();
}

public static Builder builder() {
return new Builder();
}

public Map<String, ClientContextParamDefinition> getParameters() {
return parameters;
}

@Override
protected Node createNode() {
NodeMapper mapper = new NodeMapper();
return mapper.serialize(this.getParameters()).expectObjectNode();
}

@Override
public SmithyBuilder<ClientContextParamsTrait> toBuilder() {
return builder()
.sourceLocation(getSourceLocation())
.parameters(getParameters());
}

public static final class Provider extends AbstractTrait.Provider {
public Provider() {
super(ID);
}

@Override
public Trait createTrait(ShapeId target, Node value) {
NodeMapper mapper = new NodeMapper();

Map<String, ClientContextParamDefinition> parameters = new LinkedHashMap<>();
value.expectObjectNode().getMembers().forEach((stringNode, node) -> {
parameters.put(stringNode.getValue(), mapper.deserialize(node, ClientContextParamDefinition.class));
});

ClientContextParamsTrait trait = builder()
.parameters(parameters)
.sourceLocation(value)
.build();
trait.setNodeCache(value);
return trait;
}
}

public static final class Builder extends AbstractTraitBuilder<ClientContextParamsTrait, Builder> {
private final BuilderRef<Map<String, ClientContextParamDefinition>> parameters = BuilderRef.forOrderedMap();

private Builder() {
}

public Builder parameters(Map<String, ClientContextParamDefinition> parameters) {
this.parameters.clear();
this.parameters.get().putAll(parameters);
return this;
}

public Builder putParameter(String name, ClientContextParamDefinition definition) {
this.parameters.get().put(name, definition);
return this;
}

public Builder removeParameter(String name) {
this.parameters.get().remove(name);
return this;
}

public Builder clearParameters() {
this.parameters.clear();
return this;
}

@Override
public ClientContextParamsTrait build() {
return new ClientContextParamsTrait(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2022 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.rulesengine.traits;

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.KnowledgeIndex;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.Pair;
import software.amazon.smithy.utils.SmithyUnstableApi;

/**
* Resolves an indexes the context parameters in the model.
*/
@SmithyUnstableApi
public final class ContextIndex implements KnowledgeIndex {
private final WeakReference<Model> model;

public ContextIndex(Model model) {
this.model = new WeakReference<>(model);
}

public static ContextIndex of(Model model) {
return model.getKnowledge(ContextIndex.class, ContextIndex::new);
}

/**
* Gets the mapping of context parameter names and corresponding {@link ClientContextParamDefinition} to be
* generated on the service client's configuration.
*
* @param service The service shape.
* @return The mapping of context parameter names to {@link ClientContextParamDefinition}.
*/
public Optional<ClientContextParamsTrait> getClientContextParams(Shape service) {
return service.getTrait(ClientContextParamsTrait.class);
}

/**
* Gets the static context parameter names and their {@link StaticContextParamDefinition} to be set for the given
* operation.
*
* @param operation The operation shape.
* @return The mapping of context parameter names to the static {@link Node} value to be set.
*/
public Optional<StaticContextParamsTrait> getStaticContextParams(Shape operation) {
return operation.getTrait(StaticContextParamsTrait.class);
}

/**
* Gets the mapping of {@link MemberShape} to {@link ContextParamTrait} for the operation.
*
* @param operation The operation shape.
* @return The mapping of operation's {@link MemberShape} to {@link ContextParamTrait}.
*/
public Map<MemberShape, ContextParamTrait> getContextParams(Shape operation) {
OperationShape operationShape = operation.asOperationShape()
.orElseThrow(() -> new IllegalArgumentException(operation.toShapeId()
+ " is not an operation shape"));

return getModel().expectShape(operationShape.getInputShape())
.members().stream()
.map(memberShape -> Pair.of(memberShape, memberShape.getTrait(ContextParamTrait.class)))
.filter(pair -> pair.right.isPresent())
.collect(MapUtils.toUnmodifiableMap(pair -> pair.left, pair -> pair.right.get()));
}

private Model getModel() {
return Objects.requireNonNull(model.get(), "The dereferenced WeakReference<Model> is null");
}
}
Loading