diff --git a/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/CreatesTraitTest.java b/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/CreatesTraitTest.java index 10b9a140f21..25809f891c2 100644 --- a/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/CreatesTraitTest.java +++ b/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/CreatesTraitTest.java @@ -41,6 +41,7 @@ import com.example.traits.structures.BasicAnnotationTrait; import com.example.traits.structures.NestedA; import com.example.traits.structures.NestedB; +import com.example.traits.structures.StructMemberWithTimestampFormatTrait; import com.example.traits.structures.StructWithIdrefMemberTrait; import com.example.traits.structures.StructWithListOfMapTrait; import com.example.traits.structures.StructWithUniqueItemsListTrait; @@ -53,6 +54,8 @@ import com.example.traits.uniqueitems.SetMember; import com.example.traits.uniqueitems.StringSetTrait; import com.example.traits.uniqueitems.StructureSetTrait; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -210,6 +213,14 @@ static Stream createTraitTests() { .idRefMemberB(ShapeId.from("test.smithy.traitcodegen#b")) .build() .toNode()), + Arguments.of(StructMemberWithTimestampFormatTrait.ID, + StructMemberWithTimestampFormatTrait.builder() + .memberDateTime(Instant.parse("1985-04-12T23:20:50.52Z")) + .memberHttpDate(Instant.from( + DateTimeFormatter.RFC_1123_DATE_TIME.parse("Tue, 29 Apr 2014 18:30:38 GMT"))) + .memberEpochSeconds(Instant.ofEpochSecond((long) 1515531081.123)) + .build() + .toNode()), // Timestamps Arguments.of(TimestampTrait.ID, Node.from("1985-04-12T23:20:50.52Z")), Arguments.of(DateTimeTimestampTrait.ID, Node.from("1985-04-12T23:20:50.52Z")), diff --git a/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/LoadsFromModelTest.java b/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/LoadsFromModelTest.java index fc87eefd16d..4b9b398cf22 100644 --- a/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/LoadsFromModelTest.java +++ b/smithy-trait-codegen/src/it/java/software/amazon/smithy/traitcodegen/test/LoadsFromModelTest.java @@ -48,6 +48,7 @@ import com.example.traits.structures.BasicAnnotationTrait; import com.example.traits.structures.NestedA; import com.example.traits.structures.NestedB; +import com.example.traits.structures.StructMemberWithTimestampFormatTrait; import com.example.traits.structures.StructWithIdrefMemberTrait; import com.example.traits.structures.StructWithListOfMapTrait; import com.example.traits.structures.StructWithUniqueItemsListTrait; @@ -332,6 +333,16 @@ static Stream loadsModelTests() { Optional.of(ShapeId.from("test.smithy.traitcodegen#a")), "getIdRefMemberB", Optional.of(ShapeId.from("test.smithy.traitcodegen#b")))), + Arguments.of("structures/struct-member-with-timestamp-format-trait.smithy", + StructMemberWithTimestampFormatTrait.class, + MapUtils.of( + "getMemberDateTime", + Optional.of(Instant.parse("1985-04-12T23:20:50.52Z")), + "getMemberHttpDate", + Optional.of(Instant.from( + DateTimeFormatter.RFC_1123_DATE_TIME.parse("Tue, 29 Apr 2014 18:30:38 GMT"))), + "getMemberEpochSeconds", + Optional.of(Instant.ofEpochSecond((long) 1515531081.123)))), // Timestamps Arguments.of("timestamps/struct-with-nested-timestamps.smithy", StructWithNestedTimestampsTrait.class, diff --git a/smithy-trait-codegen/src/it/resources/software/amazon/smithy/traitcodegen/test/structures/struct-member-with-timestamp-format-trait.smithy b/smithy-trait-codegen/src/it/resources/software/amazon/smithy/traitcodegen/test/structures/struct-member-with-timestamp-format-trait.smithy new file mode 100644 index 00000000000..5cb64bd4637 --- /dev/null +++ b/smithy-trait-codegen/src/it/resources/software/amazon/smithy/traitcodegen/test/structures/struct-member-with-timestamp-format-trait.smithy @@ -0,0 +1,12 @@ +$version: "2.0" + +namespace test.smithy.traitcodegen + +use test.smithy.traitcodegen.structures#StructMemberWithTimestampFormat + +@StructMemberWithTimestampFormat( + memberDateTime: "1985-04-12T23:20:50.52Z" + memberHttpDate: "Tue, 29 Apr 2014 18:30:38 GMT" + memberEpochSeconds: 1515531081.123 +) +structure myStruct {} diff --git a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeGenerator.java b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeGenerator.java index a032b3a1df8..f89d44e3790 100644 --- a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeGenerator.java +++ b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeGenerator.java @@ -247,12 +247,8 @@ private MemberGenerator(MemberShape member) { @Override public Void memberShape(MemberShape shape) { - if (shape.hasTrait(IdRefTrait.ID)) { - writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)", - fieldName, - memberName, - (Runnable) () -> shape - .accept(new FromNodeMapperVisitor(writer, model, "n", 1, symbolProvider))); + if (shape.hasTrait(IdRefTrait.ID) || shape.hasTrait(TimestampFormatTrait.ID)) { + writeObjectNodeMember(shape); return null; } return model.expectShape(shape.getTarget()).accept(this); @@ -413,19 +409,13 @@ public Void enumShape(EnumShape shape) { @Override public Void structureShape(StructureShape shape) { - writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)", - fieldName, - memberName, - (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "n", symbolProvider))); + writeObjectNodeMember(shape); return null; } @Override public Void timestampShape(TimestampShape shape) { - writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)", - fieldName, - memberName, - (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "n", symbolProvider))); + writeObjectNodeMember(shape); return null; } @@ -438,5 +428,12 @@ public Void unionShape(UnionShape shape) { public Void blobShape(BlobShape shape) { throw new UnsupportedOperationException("Shape not supported " + shape); } + + private void writeObjectNodeMember(Shape shape) { + writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)", + fieldName, + memberName, + (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "n", symbolProvider))); + } } } diff --git a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java index 169af40c7b0..7dad4174e49 100644 --- a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java +++ b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/FromNodeMapperVisitor.java @@ -269,24 +269,10 @@ public Void structureShape(StructureShape shape) { @Override public Void timestampShape(TimestampShape shape) { if (shape.hasTrait(TimestampFormatTrait.ID)) { - switch (shape.expectTrait(TimestampFormatTrait.class).getFormat()) { - case EPOCH_SECONDS: - writer.writeInline("$2T.ofEpochSecond($1L.expectNumberNode().getValue().longValue())", - varName, - Instant.class); - return null; - case HTTP_DATE: - writer.writeInline("$2T.from($3T.RFC_1123_DATE_TIME.parse($1L.expectStringNode().getValue()))", - varName, - Instant.class, - DateTimeFormatter.class); - return null; - default: - // Fall through on default - break; - } + writeForTimestampFormat(shape); + } else { + writer.writeInline("$2T.parse($1L.expectStringNode().getValue())", varName, Instant.class); } - writer.writeInline("$2T.parse($1L.expectStringNode().getValue())", varName, Instant.class); return null; } @@ -304,6 +290,8 @@ public Void blobShape(BlobShape shape) { public Void memberShape(MemberShape shape) { if (shape.hasTrait(IdRefTrait.ID)) { writer.write("$T.fromNode($L)", ShapeId.class, varName); + } else if (shape.hasTrait(TimestampFormatTrait.ID)) { + writeForTimestampFormat(shape); } else { model.expectShape(shape.getTarget()).accept(this); } @@ -321,4 +309,23 @@ public Void intEnumShape(IntEnumShape shape) { writer.write("$L.expectNumberNode().getValue().intValue()", varName); return null; } + + private void writeForTimestampFormat(Shape shape) { + switch (shape.expectTrait(TimestampFormatTrait.class).getFormat()) { + case EPOCH_SECONDS: + writer.writeInline("$2T.ofEpochSecond($1L.expectNumberNode().getValue().longValue())", + varName, + Instant.class); + break; + case HTTP_DATE: + writer.writeInline("$2T.from($3T.RFC_1123_DATE_TIME.parse($1L.expectStringNode().getValue()))", + varName, + Instant.class, + DateTimeFormatter.class); + break; + default: + // Fall through on default + writer.writeInline("$2T.parse($1L.expectStringNode().getValue())", varName, Instant.class); + } + } } diff --git a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java index e117ce45bfb..7e23d612353 100644 --- a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java +++ b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/generators/ToNodeGenerator.java @@ -352,6 +352,8 @@ public Void mapShape(MapShape shape) { public Void memberShape(MemberShape shape) { if (shape.hasTrait(IdRefTrait.ID)) { toStringMapper(); + } else if (shape.hasTrait(TimestampFormatTrait.ID)) { + writeForTimestampFormat(shape); } else { model.expectShape(shape.getTarget()).accept(this); } @@ -391,22 +393,10 @@ public Void structureShape(StructureShape shape) { @Override public Void timestampShape(TimestampShape shape) { if (shape.hasTrait(TimestampFormatTrait.ID)) { - switch (shape.expectTrait(TimestampFormatTrait.class).getFormat()) { - case EPOCH_SECONDS: - writer.write("$T.from($L.getEpochSecond())", Node.class, varName); - return null; - case HTTP_DATE: - writer.write("$T.from($T.RFC_1123_DATE_TIME.format($L))", - Node.class, - DateTimeFormatter.class, - varName); - return null; - default: - // Fall through on default - break; - } + writeForTimestampFormat(shape); + } else { + toStringMapper(); } - toStringMapper(); return null; } @@ -417,5 +407,24 @@ private void fromNodeMapper() { private void toStringMapper() { writer.write("$T.from($L.toString())", Node.class, varName); } + + private void writeForTimestampFormat(Shape shape) { + switch (shape.expectTrait(TimestampFormatTrait.class).getFormat()) { + case EPOCH_SECONDS: + writer.write("$T.from($L.getEpochSecond())", Node.class, varName); + break; + case HTTP_DATE: + writer.write("$T.from($T.RFC_1123_DATE_TIME.format($T.ofInstant($L, $T.UTC)))", + Node.class, + DateTimeFormatter.class, + ZonedDateTime.class, + varName, + ZoneOffset.class); + break; + default: + // Fall through on default + toStringMapper(); + } + } } } diff --git a/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java b/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java index bce61dd23e9..59cb15b1a16 100644 --- a/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java +++ b/smithy-trait-codegen/src/test/java/software/amazon/smithy/traitcodegen/TraitCodegenPluginTest.java @@ -26,7 +26,7 @@ import software.amazon.smithy.model.node.ObjectNode; public class TraitCodegenPluginTest { - private static final int EXPECTED_NUMBER_OF_FILES = 68; + private static final int EXPECTED_NUMBER_OF_FILES = 69; private MockManifest manifest; private Model model; diff --git a/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest b/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest index 6152a776710..d43071bb086 100644 --- a/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest +++ b/smithy-trait-codegen/src/test/resources/META-INF/smithy/manifest @@ -43,6 +43,7 @@ structures/structure-trait.smithy structures/struct-with-listofmap-trait.smithy structures/struct-with-uniqueitems-list-trait.smithy structures/struct-with-idref-member-trait.smithy +structures/struct-member-with-timestamp-format-trait.smithy timestamps/date-time-format-timestamp-trait.smithy timestamps/epoch-seconds-format-timestamp-trait.smithy timestamps/http-date-format-timestamp-trait.smithy diff --git a/smithy-trait-codegen/src/test/resources/META-INF/smithy/structures/struct-member-with-timestamp-format-trait.smithy b/smithy-trait-codegen/src/test/resources/META-INF/smithy/structures/struct-member-with-timestamp-format-trait.smithy new file mode 100644 index 00000000000..1a400f56777 --- /dev/null +++ b/smithy-trait-codegen/src/test/resources/META-INF/smithy/structures/struct-member-with-timestamp-format-trait.smithy @@ -0,0 +1,32 @@ +$version: "2.0" + +namespace test.smithy.traitcodegen.structures + +@trait +structure StructMemberWithTimestampFormat { + @timestampFormat("date-time") + memberDateTime: Timestamp + + @timestampFormat("http-date") + memberHttpDate: Timestamp + + memberEpochSeconds: MemberEpochSeconds + + memberList: TimeStampList + + memberMap: StringTimeStampMap +} + +@timestampFormat("epoch-seconds") +timestamp MemberEpochSeconds + +list TimeStampList { + @timestampFormat("http-date") + member: Timestamp +} + +map StringTimeStampMap { + key: String + @timestampFormat("http-date") + value:Timestamp +}