Skip to content

Commit

Permalink
Add helper to detect HTTP-based timestampFormats
Browse files Browse the repository at this point in the history
  • Loading branch information
mtdowling committed Oct 28, 2019
1 parent b914ce5 commit 3a56046
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import software.amazon.smithy.model.traits.HttpQueryTrait;
import software.amazon.smithy.model.traits.HttpTrait;
import software.amazon.smithy.model.traits.MediaTypeTrait;
import software.amazon.smithy.model.traits.TimestampFormatTrait;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.utils.ListUtils;

Expand Down Expand Up @@ -220,6 +221,39 @@ public List<HttpBinding> getResponseBindings(ToShapeId shapeOrId, HttpBinding.Lo
.collect(Collectors.toList());
}

/**
* Determines the appropriate timestamp format for a member shape bound to
* a specific location.
*
* @param member Member to derive the timestamp format.
* @param location Location the member is bound to.
* @param defaultFormat The format to use for the body or a default.
* @return Returns the determined timestamp format.
*/
public TimestampFormatTrait.Format determineTimestampFormat(
ToShapeId member,
HttpBinding.Location location,
TimestampFormatTrait.Format defaultFormat
) {
return index.getShape(member.toShapeId())
.flatMap(Shape::asMemberShape)
// Use the timestampFormat trait on the member or target if present.
.flatMap(shape -> shape.getMemberTrait(index, TimestampFormatTrait.class))
.map(TimestampFormatTrait::getFormat)
.orElseGet(() -> {
// Determine the format based on the location.
switch (location) {
case HEADER:
return TimestampFormatTrait.Format.HTTP_DATE;
case QUERY:
case LABEL:
return TimestampFormatTrait.Format.DATE_TIME;
default:
return defaultFormat;
}
});
}

/**
* Returns the expected request Content-Type of the given operation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,57 @@ public TimestampFormatTrait(String value) {
this(value, SourceLocation.NONE);
}

/**
* Gets the {@code timestampFormat} value as a {@code Format} enum.
*
* @return Returns the {@code Format} enum.
*/
public Format getFormat() {
return Format.fromString(getValue());
}

public static final class Provider extends StringTrait.Provider<TimestampFormatTrait> {
public Provider() {
super(ID, TimestampFormatTrait::new);
}
}

/**
* The known {@code timestampFormat} values.
*/
public enum Format {
EPOCH_SECONDS(TimestampFormatTrait.EPOCH_SECONDS),
DATE_TIME(TimestampFormatTrait.DATE_TIME),
HTTP_DATE(TimestampFormatTrait.HTTP_DATE),
UNKNOWN("unknown");

private String value;

Format(String value) {
this.value = value;
}

/**
* Create a {@code Format} from a string that would appear in a model.
*
* <p>Any unknown value is returned as {@code Unknown}.
*
* @param value Value from a trait or model.
* @return Returns the Format enum value.
*/
public static Format fromString(String value) {
for (Format format : values()) {
if (format.value.equals(value)) {
return format;
}
}

return UNKNOWN;
}

@Override
public String toString() {
return value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.pattern.UriPattern;
import software.amazon.smithy.model.shapes.ListShape;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
Expand All @@ -42,6 +43,7 @@
import software.amazon.smithy.model.traits.HttpPayloadTrait;
import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait;
import software.amazon.smithy.model.traits.HttpTrait;
import software.amazon.smithy.model.traits.TimestampFormatTrait;

public class HttpBindingIndexTest {

Expand Down Expand Up @@ -209,7 +211,7 @@ public void findsUnboundMembers() {
@Test
public void checksForHttpRequestAndResponseBindings() {
Shape shape = MemberShape.builder()
.target("smithy.api#String")
.target("smithy.api#Timestamp")
.id("smithy.example#Baz$bar")
.addTrait(new HttpLabelTrait(SourceLocation.NONE))
.build();
Expand All @@ -221,7 +223,7 @@ public void checksForHttpRequestAndResponseBindings() {
@Test
public void checksForHttpResponseBindings() {
Shape shape = MemberShape.builder()
.target("smithy.api#String")
.target("smithy.api#Timestamp")
.id("smithy.example#Baz$bar")
.addTrait(new HttpHeaderTrait("hello", SourceLocation.NONE))
.build();
Expand Down Expand Up @@ -270,4 +272,80 @@ private static MemberShape expectMember(Model model, String id) {
ShapeId shapeId = ShapeId.from(id);
return model.getShapeIndex().getShape(shapeId).get().asMemberShape().get();
}

@Test
public void usesTimestampFormatMemberTraitToDetermineFormat() {
MemberShape member = MemberShape.builder()
.id("foo.bar#Baz$member")
.target("smithy.api#Timestamp")
.addTrait(new TimestampFormatTrait(TimestampFormatTrait.EPOCH_SECONDS))
.build();
Model model = Model.assembler()
.addShape(member)
.addShape(ListShape.builder().member(member).id("foo.bar#Baz").build())
.assemble()
.unwrap();
HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);
TimestampFormatTrait.Format format = index.determineTimestampFormat(
member, HttpBinding.Location.HEADER, TimestampFormatTrait.Format.DATE_TIME);

assertThat(format, equalTo(TimestampFormatTrait.Format.EPOCH_SECONDS));
}

@Test
public void headerLocationUsesHttpDateTimestampFormat() {
MemberShape member = MemberShape.builder()
.id("foo.bar#Baz$member")
.target("smithy.api#Timestamp")
.build();
Model model = Model.assembler()
.addShape(member)
.addShape(ListShape.builder().member(member).id("foo.bar#Baz").build())
.assemble()
.unwrap();
HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);

assertThat(index.determineTimestampFormat(
member, HttpBinding.Location.HEADER, TimestampFormatTrait.Format.EPOCH_SECONDS),
equalTo(TimestampFormatTrait.Format.HTTP_DATE));
}

@Test
public void queryAndLabelLocationUsesDateTimeTimestampFormat() {
MemberShape member = MemberShape.builder()
.id("foo.bar#Baz$member")
.target("smithy.api#Timestamp")
.build();
Model model = Model.assembler()
.addShape(member)
.addShape(ListShape.builder().member(member).id("foo.bar#Baz").build())
.assemble()
.unwrap();
HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);

assertThat(index.determineTimestampFormat(
member, HttpBinding.Location.QUERY, TimestampFormatTrait.Format.EPOCH_SECONDS),
equalTo(TimestampFormatTrait.Format.DATE_TIME));
assertThat(index.determineTimestampFormat(
member, HttpBinding.Location.LABEL, TimestampFormatTrait.Format.EPOCH_SECONDS),
equalTo(TimestampFormatTrait.Format.DATE_TIME));
}

@Test
public void otherLocationsUseDefaultTimestampFormat() {
MemberShape member = MemberShape.builder()
.id("foo.bar#Baz$member")
.target("smithy.api#Timestamp")
.build();
Model model = Model.assembler()
.addShape(member)
.addShape(ListShape.builder().member(member).id("foo.bar#Baz").build())
.assemble()
.unwrap();
HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);

assertThat(index.determineTimestampFormat(
member, HttpBinding.Location.DOCUMENT, TimestampFormatTrait.Format.EPOCH_SECONDS),
equalTo(TimestampFormatTrait.Format.EPOCH_SECONDS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package software.amazon.smithy.model.traits;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;

public class TimestampFormatTraitTest {
@Test
public void createsFromString() {
assertThat(TimestampFormatTrait.Format.fromString("date-time"),
equalTo(TimestampFormatTrait.Format.DATE_TIME));
assertThat(TimestampFormatTrait.Format.fromString("http-date"),
equalTo(TimestampFormatTrait.Format.HTTP_DATE));
assertThat(TimestampFormatTrait.Format.fromString("epoch-seconds"),
equalTo(TimestampFormatTrait.Format.EPOCH_SECONDS));
assertThat(TimestampFormatTrait.Format.fromString("foo-baz"),
equalTo(TimestampFormatTrait.Format.UNKNOWN));
}

@Test
public void convertsFormatToString() {
assertThat(TimestampFormatTrait.Format.fromString("date-time").toString(),
equalTo("date-time"));
}

@Test
public void createsFormatFromTrait() {
TimestampFormatTrait trait = new TimestampFormatTrait("date-time");

assertThat(trait.getFormat(), equalTo(TimestampFormatTrait.Format.DATE_TIME));
}
}

0 comments on commit 3a56046

Please sign in to comment.