Skip to content

Commit

Permalink
Move to CBOR trait from configured formats
Browse files Browse the repository at this point in the history
This commit refactors the trait implementation to be specific to the
CBOR wire format instead of taking a collection of possible wire
formats, simplifying the process of resolving the protocol to use
for generated code.

It also moves the package to `smithy-protocol-traits`, removing the
pluralization on `protocols`.
  • Loading branch information
kstich committed Jan 3, 2024
1 parent 65c81b2 commit b0c5e38
Show file tree
Hide file tree
Showing 20 changed files with 155 additions and 260 deletions.
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ include ":smithy-rules-engine"
include ":smithy-smoke-test-traits"
include ":smithy-syntax"
include ":smithy-aws-endpoints"
include ":smithy-protocols-traits"
include ":smithy-protocol-traits"
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
* permissions and limitations under the License.
*/

description = "This module provides the implementation of protocols traits for Smithy."
description = "This module provides the implementation of protocol traits for Smithy."

ext {
displayName = "Smithy :: Protocols Traits"
moduleName = "software.amazon.smithy.protocols.traits"
displayName = "Smithy :: Protocol Traits"
moduleName = "software.amazon.smithy.protocol.traits"
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,57 @@
* specific language governing permissions and limitations under the License.
*/

package software.amazon.smithy.protocols.traits;
package software.amazon.smithy.protocol.traits;

import java.util.ArrayList;
import java.util.List;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.node.ObjectNode;
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.ListUtils;
import software.amazon.smithy.utils.ToSmithyBuilder;

public final class Rpcv2Trait extends AbstractTrait {
public final class Rpcv2CborTrait extends AbstractTrait implements ToSmithyBuilder<Rpcv2CborTrait> {

public static final ShapeId ID = ShapeId.from("smithy.protocols#rpcv2");
public static final ShapeId ID = ShapeId.from("smithy.protocols#rpcv2Cbor");

private static final String HTTP = "http";
private static final String EVENT_STREAM_HTTP = "eventStreamHttp";
private static final String FORMAT = "format";

private final List<String> http;
private final List<String> eventStreamHttp;
private final List<String> format;

// Package-private constructor (at least for now)
Rpcv2Trait(Builder builder) {
private Rpcv2CborTrait(Builder builder) {
super(ID, builder.getSourceLocation());
http = ListUtils.copyOf(builder.http);
eventStreamHttp = ListUtils.copyOf(builder.eventStreamHttp);
format = ListUtils.copyOf(builder.format);
}

/**
* Creates a new {@code Builder}.
*/
public static Builder builder() {
return new Builder();
}

/**
* Updates the builder from a Node.
*
* @param node Node object that must be a valid {@code ObjectNode}.
* @return Returns the updated builder.
*/
public static Rpcv2CborTrait fromNode(Node node) {
Builder builder = builder();
ObjectNode objectNode = node.expectObjectNode();
objectNode.getArrayMember(HTTP).map(values -> Node.loadArrayOfString(HTTP, values))
.ifPresent(builder::http);
objectNode.getArrayMember(EVENT_STREAM_HTTP).map(values -> Node.loadArrayOfString(EVENT_STREAM_HTTP, values))
.ifPresent(builder::eventStreamHttp);
return builder.build();
}

/**
Expand All @@ -61,50 +83,34 @@ public List<String> getEventStreamHttp() {
return eventStreamHttp;
}

/**
* Gets the priority ordered list of supported wire formats.
*
* @return Returns the supported wire formats.
*/
public List<String> getFormat() {
return format;
}

/**
* Turns the trait into a {@code Node}.
*/
@Override
protected Node createNode() {
ObjectNode.Builder builder = Node.objectNodeBuilder();

if (!getHttp().isEmpty()) {
builder.withMember(HTTP, Node.fromStrings(getHttp()));
}

if (!getEventStreamHttp().isEmpty()) {
builder.withMember(EVENT_STREAM_HTTP, Node.fromStrings(getEventStreamHttp()));
}

// Build the format property even if it's empty as the {@code Rpcv2TraitValidator}
// will take care of validating it.
builder.withMember(FORMAT, Node.fromStrings(getFormat()));

return builder.build();
}

@Override
public Builder toBuilder() {
return builder().http(http).eventStreamHttp(eventStreamHttp);
}

/**
* Builder for creating a {@code Rpcv2Trait}.
* Builder for creating a {@code Rpcv2CborTrait}.
*/
public static final class Builder extends AbstractTraitBuilder<Rpcv2Trait, Builder> {
public static final class Builder extends AbstractTraitBuilder<Rpcv2CborTrait, Builder> {

private final List<String> http = new ArrayList<>();
private final List<String> eventStreamHttp = new ArrayList<>();
private final List<String> format = new ArrayList<>();


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

/**
Expand All @@ -130,44 +136,6 @@ public Builder eventStreamHttp(List<String> eventStreamHttp) {
this.eventStreamHttp.addAll(eventStreamHttp);
return this;
}

/**
* Sets the list of supported wire formats.
*
* @param format Wire format to set and replace.
* @return Returns the builder.
*/
public Builder format(List<String> format) {
this.format.clear();
this.format.addAll(format);
return this;
}

/**
* Updates the builder from a Node.
*
* @param node Node object that must be a valid {@code ObjectNode}.
* @return Returns the updated builder.
*/
public Builder fromNode(Node node) {
ObjectNode objectNode = node.expectObjectNode();
objectNode.getArrayMember(HTTP).map(values -> Node.loadArrayOfString(HTTP, values))
.ifPresent(this::http);
objectNode.getArrayMember(EVENT_STREAM_HTTP)
.map(values -> Node.loadArrayOfString(EVENT_STREAM_HTTP, values))
.ifPresent(this::eventStreamHttp);
objectNode.getArrayMember(FORMAT).map(values -> Node.loadArrayOfString(FORMAT, values))
.ifPresent(this::format);

return this;
}
}

/**
* Creates a new {@code Builder}.
*/
public static Builder builder() {
return new Builder();
}

/**
Expand All @@ -179,16 +147,11 @@ public Provider() {
super(ID);
}

/**
* Creates a trait from a {@code Node} value.
*
* @param shapeId The shape ID.
* @param value The Node value.
* @return Returns the Rpcv2Trait.
*/
@Override
public Rpcv2Trait createTrait(ShapeId shapeId, Node value) {
return builder().sourceLocation(value).fromNode(value).build();
public Trait createTrait(ShapeId target, Node value) {
Rpcv2CborTrait result = new NodeMapper().deserialize(value, Rpcv2CborTrait.class);
result.setNodeCache(value);
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2023 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.protocol.traits;

import java.util.ArrayList;
import java.util.List;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Validates models implementing the {@code Rpcv2CborTrait} against its constraints by:
*
* - Ensuring that every entry in {@code eventStreamHttp} also appears in the {@code http} property
* of a protocol trait.
*/
@SmithyInternalApi
public final class Rpcv2CborTraitValidator extends AbstractValidator {

@Override
public List<ValidationEvent> validate(Model model) {
List<ValidationEvent> events = new ArrayList<>();
for (ServiceShape serviceShape : model.getServiceShapesWithTrait(Rpcv2CborTrait.class)) {
Rpcv2CborTrait protocolTrait = serviceShape.expectTrait(Rpcv2CborTrait.class);

List<String> invalid = new ArrayList<>(protocolTrait.getEventStreamHttp());
invalid.removeAll(protocolTrait.getHttp());
if (!invalid.isEmpty()) {
events.add(error(serviceShape, protocolTrait,
String.format("The following values of the `eventStreamHttp` property do "
+ "not also appear in the `http` property of the %s protocol "
+ "trait: %s", protocolTrait.toShapeId(), invalid)));
}
}
return events;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
* Defines protocols for Smithy.
*/
@SmithyUnstableApi
package software.amazon.smithy.protocols.traits;
package software.amazon.smithy.protocol.traits;

import software.amazon.smithy.utils.SmithyUnstableApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
software.amazon.smithy.protocol.traits.Rpcv2CborTrait$Provider
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
software.amazon.smithy.protocol.traits.Rpcv2CborTraitValidator
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@ $version: "2.0"

namespace smithy.protocols

use smithy.api#httpError
use smithy.api#cors
use smithy.api#endpoint
use smithy.api#hostLabel
use smithy.api#httpError

/// An RPC protocol with support for multiple wire formats.
/// An RPC-based protocol that serializes CBOR payloads.
@trait(selector: "service")
@protocolDefinition(traits: [
httpError
cors
endpoint
hostLabel
httpError
])
structure rpcv2 {
structure rpcv2Cbor {
/// Priority ordered list of supported HTTP protocol versions.
http: StringList

/// Priority ordered list of supported HTTP protocol versions
/// that are required when using event streams.
eventStreamHttp: StringList

/// Priority ordered list of supported wire formats.
@required
format: StringList
}

/// A list of String shapes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* specific language governing permissions and limitations under the License.
*/

package software.amazon.smithy.protocols.traits;
package software.amazon.smithy.protocol.traits;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand All @@ -21,18 +21,17 @@
import software.amazon.smithy.model.traits.TraitFactory;
import java.util.Optional;

public class Rpcv2TraitTest {
public class Rpcv2CborTraitTest {

@Test
public void loadsTraitWithDefaults() {
Node node = Node.objectNode().withMember("format", Node.fromStrings("cbor"));
Node node = Node.objectNode();
TraitFactory provider = TraitFactory.createServiceFactory();
Optional<Trait> trait =
provider.createTrait(Rpcv2Trait.ID, ShapeId.from("ns.foo#foo"), node);
Optional<Trait> trait = provider.createTrait(Rpcv2CborTrait.ID, ShapeId.from("ns.foo#foo"), node);

Assertions.assertTrue(trait.isPresent());
Assertions.assertTrue(trait.get() instanceof Rpcv2Trait);
Rpcv2Trait smithyRpcV2Trait = (Rpcv2Trait) trait.get();
Assertions.assertTrue(trait.get() instanceof Rpcv2CborTrait);
Rpcv2CborTrait smithyRpcV2Trait = (Rpcv2CborTrait) trait.get();
Assertions.assertEquals(smithyRpcV2Trait.toNode(), node);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* specific language governing permissions and limitations under the License.
*/

package software.amazon.smithy.protocols.traits;
package software.amazon.smithy.protocol.traits;

import java.util.concurrent.Callable;
import java.util.stream.Stream;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[ERROR] smithy.example#InvalidService1: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2 protocol trait: [http/1.1] | Rpcv2Trait
[ERROR] smithy.example#InvalidService2: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2 protocol trait: [http/1.1] | Rpcv2Trait
[ERROR] smithy.example#InvalidService3: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2 protocol trait: [http/1.1, h2c] | Rpcv2Trait
[ERROR] smithy.example#InvalidService1: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait
[ERROR] smithy.example#InvalidService2: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait
[ERROR] smithy.example#InvalidService3: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1, h2c] | Rpcv2CborTrait
Loading

0 comments on commit b0c5e38

Please sign in to comment.