From ca869ccd897379087775e234a6f0a9d8525a8fb6 Mon Sep 17 00:00:00 2001 From: Hunter Mellema Date: Tue, 12 Nov 2024 10:56:58 -0700 Subject: [PATCH 1/3] Add transforms to remove deprecated shapes --- .../core/directed/CodegenDirector.java | 26 ++++++ .../FilterDeprecatedRelativeDate.java | 70 ++++++++++++++ .../FilterDeprecatedRelativeVersion.java | 91 +++++++++++++++++++ .../model/transform/ModelTransformer.java | 33 +++++++ .../FilterDeprecatedRelativeDateTest.java | 39 ++++++++ .../FilterDeprecatedRelativeVersionTest.java | 75 +++++++++++++++ .../deprecated-members-after.smithy | 11 +++ .../deprecated-members-before.smithy | 20 ++++ .../deprecated-shapes-after.smithy | 13 +++ .../deprecated-shapes-before.smithy | 28 ++++++ .../deprecated-traits-after.smithy | 15 +++ .../deprecated-traits-before.smithy | 30 ++++++ .../deprecated-members-after.smithy | 11 +++ .../deprecated-members-before.smithy | 14 +++ .../deprecated-shapes-after.smithy | 13 +++ .../deprecated-shapes-before.smithy | 18 ++++ .../deprecated-traits-after.smithy | 15 +++ .../deprecated-traits-before.smithy | 20 ++++ 18 files changed, 542 insertions(+) create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java create mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDateTest.java create mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersionTest.java create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-after.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-before.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-after.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-before.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-after.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-before.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-after.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-before.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-after.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-before.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-after.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-before.smithy diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java index 795fc54fb00..13aedaeb8f4 100644 --- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java +++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java @@ -289,6 +289,32 @@ public void createDedicatedInputsAndOutputs(String inputSuffix, String outputSuf }); } + /** + * Removes any shapes deprecated before the specified date. + * + * @param relativeDate Relative date, in YYYY-MM-DD format, to use to filter out deprecated shapes. + * @see ModelTransformer#filterDeprecatedRelativeDate(Model, String) + */ + public void removeShapesDeprecatedBeforeDate(String relativeDate) { + transforms.add((model, transformer) -> { + LOGGER.finest("Removing shapes deprecated before date: " + relativeDate); + return transformer.filterDeprecatedRelativeDate(model, relativeDate); + }); + } + + /** + * Removes any shapes deprecated before the specified version. + * + * @param relativeVersion Version, in SemVer format, to use to filter out deprecated shapes. + * @see ModelTransformer#filterDeprecatedRelativeVersion(Model, String) + */ + public void removeShapesDeprecatedBeforeVersion(String relativeVersion) { + transforms.add((model, transformer) -> { + LOGGER.finest("Removing shapes deprecated before version: " + relativeVersion); + return transformer.filterDeprecatedRelativeVersion(model, relativeVersion); + }); + } + /** * Changes each compatible string shape with the enum trait to an enum shape. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java new file mode 100644 index 00000000000..e3b640d8d7c --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.model.transform; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.DeprecatedTrait; + +final class FilterDeprecatedRelativeDate { + private static final Logger LOGGER = Logger.getLogger(FilterDeprecatedRelativeDate.class.getName()); + // YYYY-MM-DD calendar date with optional hyphens. + private static final Pattern ISO_8601_DATE_REGEX = Pattern.compile( + "^([0-9]{4})-?(1[0-2]|0[1-9])-?(3[01]|0[1-9]|[12][0-9])$" + ); + + public final String relativeDate; + + FilterDeprecatedRelativeDate(String relativeDate) { + if (relativeDate != null && !isIso8601Date(relativeDate)) { + throw new IllegalArgumentException("Provided relativeDate: `" + + relativeDate + + "` does not match ISO8601 calendar date format (YYYY-MM-DD)." + ); + } + this.relativeDate = relativeDate != null ? relativeDate.replace("-", "") : null; + } + + public Model transform(ModelTransformer transformer, Model model) { + // If there is no filter. Exit without traversing shapes + if (relativeDate == null) { + return model; + } + + Set shapesToRemove = new HashSet<>(); + for (Shape shape : model.getShapesWithTrait(DeprecatedTrait.class)) { + if (shape.hasTrait(DeprecatedTrait.class)) { + Optional sinceOptional = shape.expectTrait(DeprecatedTrait.class).getSince(); + if (!sinceOptional.isPresent()) { + continue; + } + String since = sinceOptional.get(); + + if (isIso8601Date(since)) { + // Compare lexicographical ordering without hyphens. + if (relativeDate.compareTo(since.replace("-", "")) > 0) { + LOGGER.fine("Filtering deprecated shape: `" + + shape + "`" + + ". Shape was deprecated as of: " + since + ); + shapesToRemove.add(shape); + } + } + } + } + + return transformer.removeShapes(model, shapesToRemove); + } + + private static boolean isIso8601Date(String string) { + return ISO_8601_DATE_REGEX.matcher(string).matches(); + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java new file mode 100644 index 00000000000..5eec64a6e45 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.model.transform; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.DeprecatedTrait; + +final class FilterDeprecatedRelativeVersion { + private static final Logger LOGGER = Logger.getLogger(FilterDeprecatedRelativeVersion.class.getName()); + + /** + * SemVer regex from semantic version spec. + * @see SemVer + */ + private static final Pattern SEMVER_REGEX = Pattern.compile( + "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" + + "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + ); + + public final String relativeVersion; + + FilterDeprecatedRelativeVersion(String relativeVersion) { + if (relativeVersion != null && !isSemVer(relativeVersion)) { + throw new IllegalArgumentException("Provided relativeDate: `" + + relativeVersion + + "` is not a valid ." + ); + } + this.relativeVersion = relativeVersion; + } + + public Model transform(ModelTransformer transformer, Model model) { + // If there are no filters. Exit without traversing shapes + if (relativeVersion == null) { + return model; + } + + Set shapesToRemove = new HashSet<>(); + for (Shape shape : model.getShapesWithTrait(DeprecatedTrait.class)) { + Optional sinceOptional = shape.expectTrait(DeprecatedTrait.class).getSince(); + + if (!sinceOptional.isPresent()) { + continue; + } + + String since = sinceOptional.get(); + // Remove any shapes that were deprecated before the specified version. + if (isSemVer(since)) { + if (compareSemVer(relativeVersion, since) > 0) { + LOGGER.fine("Filtering deprecated shape: `" + + shape + "`" + + ". Shape was deprecated as of version: " + since + ); + shapesToRemove.add(shape); + } + } + } + + return transformer.removeShapes(model, shapesToRemove); + } + + public static boolean isSemVer(String string) { + return SEMVER_REGEX.matcher(string).matches(); + } + + static int compareSemVer(String semVer1, String semVer2) { + String[] versionComponents1 = semVer1.split("\\."); + String[] versionComponents2 = semVer2.split("\\."); + + int maxLength = Math.max(versionComponents1.length, versionComponents2.length); + for (int i = 0; i < maxLength; i++) { + // Treat all implicit components as 0's + String component1 = i >= versionComponents1.length ? "0" : versionComponents1[i]; + String component2 = i >= versionComponents2.length ? "0" : versionComponents2[i]; + int comparison = component1.compareTo(component2); + if (comparison != 0) { + return comparison; + } + } + return 0; + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java index 4c6160db4a6..ed2123608d3 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java @@ -687,4 +687,37 @@ public Model deconflictErrorsWithSharedStatusCode(Model model, ServiceShape forS public Model flattenPaginationInfoIntoOperations(Model model, ServiceShape forService) { return new FlattenPaginationInfo(forService).transform(this, model); } + + /** + * Removes any shapes that were deprecated before the specified date. + * + *

The relative date used as a filter should match the ISO 8601 Calendar date format + * (i.e. YYYY-MM-DD with optional hyphens). + *

Note: Shapes with {@code @deprecated} trait but no {@code since} property are not filtered. + * + * @param model Model to transform. + * @param relativeDate ISO 8601 calendar date to use to filter out deprecated shapes. + * @return Returns the transformed model. + * + * @see ISO 8601 + */ + public Model filterDeprecatedRelativeDate(Model model, String relativeDate) { + return new FilterDeprecatedRelativeDate(relativeDate).transform(this, model); + } + + /** + * Removes any shapes that were deprecated before the specified version. + * + *

The version used should be a valid Semantic Version (SemVer). For example, {@code 1.2.3.1}. + *

Note: Shapes with {@code @deprecated} trait but no {@code since} property are not filtered. + * + * @param model Model to transform. + * @param relativeVersion Semantic Version to use to filter out deprecated shapes. + * @return Returns the transformed model. + * + * @see SemVer + */ + public Model filterDeprecatedRelativeVersion(Model model, String relativeVersion) { + return new FilterDeprecatedRelativeVersion(relativeVersion).transform(this, model); + } } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDateTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDateTest.java new file mode 100644 index 00000000000..6547a412795 --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDateTest.java @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.model.transform; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ModelSerializer; +import software.amazon.smithy.utils.ListUtils; + +public class FilterDeprecatedRelativeDateTest { + + public static List fileSource() { + return ListUtils.of("deprecated-shapes", "deprecated-members", "deprecated-traits"); + } + + @ParameterizedTest + @MethodSource("fileSource") + void compareTransform(String prefix) { + Model before = Model.assembler() + .addImport(FilterDeprecatedRelativeDate.class.getResource("deprecated-date/" + prefix + "-before.smithy")) + .assemble() + .unwrap(); + Model actualResult = ModelTransformer.create().filterDeprecatedRelativeDate(before, "2024-10-10"); + Model expectedResult = Model.assembler() + .addImport(FilterDeprecatedRelativeDate.class.getResource("deprecated-date/" + prefix + "-after.smithy")) + .assemble() + .unwrap(); + + Node actualNode = ModelSerializer.builder().build().serialize(actualResult); + Node expectedNode = ModelSerializer.builder().build().serialize(expectedResult); + Node.assertEquals(actualNode, expectedNode); + } +} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersionTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersionTest.java new file mode 100644 index 00000000000..e13b2d54477 --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersionTest.java @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.model.transform; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ModelSerializer; +import software.amazon.smithy.utils.ListUtils; + +public class FilterDeprecatedRelativeVersionTest { + static List semverSupplier() { + return ListUtils.of( + Arguments.of("1.0.0", "1.0.0", 0), + Arguments.of("1.0.0", "1.0.1", -1), + Arguments.of("1.1.0", "1.1.1", -1), + Arguments.of("1.1.0", "1.0.1", 1), + Arguments.of("1.1.1", "1.1.1.1", -1), + Arguments.of("1.0.0.1", "1.0.0", 1), + Arguments.of("1.0.0", "1.0", 0), + Arguments.of("20.20.0.1", "20.20.1.0", -1), + Arguments.of("20.20.1.0", "20.20.1.0-PATCH", -1) + ); + } + + @ParameterizedTest + @MethodSource("semverSupplier") + void testSemverComparison(String semver1, String semver2, int expected) { + int result = FilterDeprecatedRelativeVersion.compareSemVer(semver1, semver2); + switch (expected) { + case 0: + assertEquals(result, 0); + break; + case -1: + assertTrue(result < 0); + break; + case 1: + assertTrue(result > 0); + break; + default: + throw new IllegalStateException("Unexpected value: " + expected); + } + } + + public static List fileSource() { + return ListUtils.of("deprecated-shapes", "deprecated-members", "deprecated-traits"); + } + + @ParameterizedTest + @MethodSource("fileSource") + void compareTransform(String prefix) { + Model before = Model.assembler() + .addImport(FilterDeprecatedRelativeDate.class.getResource("deprecated-version/" + prefix + "-before.smithy")) + .assemble() + .unwrap(); + Model actualResult = ModelTransformer.create().filterDeprecatedRelativeVersion(before, "1.1.0"); + Model expectedResult = Model.assembler() + .addImport(FilterDeprecatedRelativeDate.class.getResource("deprecated-version/" + prefix + "-after.smithy")) + .assemble() + .unwrap(); + + Node actualNode = ModelSerializer.builder().build().serialize(actualResult); + Node expectedNode = ModelSerializer.builder().build().serialize(expectedResult); + Node.assertEquals(actualNode, expectedNode); + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-after.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-after.smithy new file mode 100644 index 00000000000..ce2ccc52bf7 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-after.smithy @@ -0,0 +1,11 @@ +$version: "2" + +namespace smithy.example + +structure MyStruct { + @deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") + notFilteredEquals: String + + @deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") + notFilteredAfter: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-before.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-before.smithy new file mode 100644 index 00000000000..b0a8d0b3d88 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-members-before.smithy @@ -0,0 +1,20 @@ +$version: "2" + +namespace smithy.example + +structure MyStruct { + @deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-10-09") + filteredBeforeHyphens: String + + @deprecated(message: "Should be filtered as it is deprecated before date", since: "20241009") + filteredBeforeNoHyphens: String + + @deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-1009") + filteredBeforeMixedHyphens: String + + @deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") + notFilteredEquals: String + + @deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") + notFilteredAfter: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-after.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-after.smithy new file mode 100644 index 00000000000..7b376f706d9 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-after.smithy @@ -0,0 +1,13 @@ +$version: "2" + +namespace smithy.example + +@deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") +structure NotFilteredEquals { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") +structure NotFilteredAfter { + field: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-before.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-before.smithy new file mode 100644 index 00000000000..08e34edd9ed --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-shapes-before.smithy @@ -0,0 +1,28 @@ +$version: "2" + +namespace smithy.example + +@deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-10-09") +structure FilteredBeforeHyphens { + field: String +} + +@deprecated(message: "Should be filtered as it is deprecated before date", since: "20241009") +structure FilteredBeforeNoHyphens { + field: String +} + +@deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-1009") +structure FilteredBeforeMixedHyphens { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") +structure NotFilteredEquals { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") +structure NotFilteredAfter { + field: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-after.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-after.smithy new file mode 100644 index 00000000000..69b7f18284c --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-after.smithy @@ -0,0 +1,15 @@ +$version: "2" + +namespace smithy.example + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") +structure NotFilteredEquals {} + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") +structure NotFilteredAfter {} + +@NotFilteredEquals +@NotFilteredAfter +structure MyStruct {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-before.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-before.smithy new file mode 100644 index 00000000000..dbae56ddbb7 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-date/deprecated-traits-before.smithy @@ -0,0 +1,30 @@ +$version: "2" + +namespace smithy.example + +@trait +@deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-10-09") +structure FilteredBeforeHyphens {} + +@trait +@deprecated(message: "Should be filtered as it is deprecated before date", since: "20241009") +structure FilteredBeforeNoHyphens {} + +@trait +@deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-1009") +structure FilteredBeforeMixedHyphens {} + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") +structure NotFilteredEquals {} + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") +structure NotFilteredAfter {} + +@FilteredBeforeHyphens +@FilteredBeforeNoHyphens +@FilteredBeforeMixedHyphens +@NotFilteredEquals +@NotFilteredAfter +structure MyStruct {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-after.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-after.smithy new file mode 100644 index 00000000000..1733842b00f --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-after.smithy @@ -0,0 +1,11 @@ +$version: "2" + +namespace smithy.example + +structure MyStruct { + @deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") + notFilteredEquals: String + + @deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") + notFilteredAfter: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-before.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-before.smithy new file mode 100644 index 00000000000..634ba688ca4 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-members-before.smithy @@ -0,0 +1,14 @@ +$version: "2" + +namespace smithy.example + +structure MyStruct { + @deprecated(message: "Should be filtered as it is deprecated before version", since: "1.0.0") + filteredBefore: String + + @deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") + notFilteredEquals: String + + @deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") + notFilteredAfter: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-after.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-after.smithy new file mode 100644 index 00000000000..378e9911a9e --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-after.smithy @@ -0,0 +1,13 @@ +$version: "2" + +namespace smithy.example + +@deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") +structure NotFilteredEquals { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") +structure NotFilteredAfter { + field: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-before.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-before.smithy new file mode 100644 index 00000000000..c763069ddb3 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-shapes-before.smithy @@ -0,0 +1,18 @@ +$version: "2" + +namespace smithy.example + +@deprecated(message: "Should be filtered as it is deprecated before version", since: "1.0.0") +structure FilteredBefore { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") +structure NotFilteredEquals { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") +structure NotFilteredAfter { + field: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-after.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-after.smithy new file mode 100644 index 00000000000..5289314349b --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-after.smithy @@ -0,0 +1,15 @@ +$version: "2" + +namespace smithy.example + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") +structure NotFilteredEquals {} + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") +structure NotFilteredAfter {} + +@NotFilteredEquals +@NotFilteredAfter +structure MyStruct {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-before.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-before.smithy new file mode 100644 index 00000000000..ecbad79cc6f --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/deprecated-version/deprecated-traits-before.smithy @@ -0,0 +1,20 @@ +$version: "2" + +namespace smithy.example + +@trait +@deprecated(message: "Should be filtered as it is deprecated before version", since: "1.0.0") +structure FilteredBefore {} + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") +structure NotFilteredEquals {} + +@trait +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") +structure NotFilteredAfter {} + +@FilteredBefore +@NotFilteredEquals +@NotFilteredAfter +structure MyStruct {} From 48ce249a307218005c6a8b032bd006a99723eb37 Mon Sep 17 00:00:00 2001 From: Hunter Mellema Date: Tue, 12 Nov 2024 12:16:16 -0700 Subject: [PATCH 2/3] Add smithy-build transform for deprecated shapes --- docs/requirements.txt | 3 + docs/source-2.0/guides/smithy-build-json.rst | 57 +++++++++++++ .../transforms/RemoveDeprecatedShapes.java | 79 +++++++++++++++++++ .../RemoveDeprecatedShapesTest.java | 45 +++++++++++ .../build/transforms/remove-deprecated.smithy | 33 ++++++++ 5 files changed, 217 insertions(+) create mode 100644 smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapes.java create mode 100644 smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapesTest.java create mode 100644 smithy-build/src/test/resources/software/amazon/smithy/build/transforms/remove-deprecated.smithy diff --git a/docs/requirements.txt b/docs/requirements.txt index e50794f6706..54c82aab9c5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -11,3 +11,6 @@ sphinx-tabs>=3.4.4 sphinx_copybutton==0.5.0 sphinx-autobuild>=2024.4.16 + +# needs to be added as it is removed in python3.13 +standard-imghdr==3.13.0 diff --git a/docs/source-2.0/guides/smithy-build-json.rst b/docs/source-2.0/guides/smithy-build-json.rst index ebdcf35a448..cba39798b14 100644 --- a/docs/source-2.0/guides/smithy-build-json.rst +++ b/docs/source-2.0/guides/smithy-build-json.rst @@ -1650,6 +1650,61 @@ but keeps the shape if it has any of the provided tags: } } +.. _removeDeprecatedShapes-transform: + +removeDeprecatedShapes +----------------------- + +Removes any shapes that were marked as ``@deprecated`` before the specified version or date. + +This transform can be used to filter out shapes with the :ref:`deprecated trait ` applied if the +``since`` property of the trait specifies a version or date. Versions are expected to follow the +`SemVer Specification`_ and dates are expected to be `ISO 8601`_ calendar dates (YYYY-MM-DD). +If the value of the ``since`` property is not a valid SemVer version or ISO 8601 calendar date then the +``@deprecated`` trait will be ignored by this transform. + +If the version or date in the ``since`` property of a ``@deprecated`` trait is before the configured version or date, then +the shape the ``@deprecated`` trait is applied to will be removed from the model. Shapes with the ``@deprecated`` trait, +but no ``since`` property will be ignored by this transform. + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - relativeDate + - ``string`` + - Date, in `ISO 8601`_ calendar date format (YYYY-MM-DD), to use to filter out deprecated shapes. + Any shapes deprecated before this date will be removed from the model. + * - relativeVersion + - ``string`` + - Version, following the `SemVer Specification`_, to use to filter out deprecated shapes. + Any shapes deprecated in an earlier version will be removed from the model. + +The following example removes any deprecated shapes that were deprecated before ``2024-10-10`` +or before version ``1.1.0``. + +.. code-block:: json + + { + "version": "1.0", + "projections": { + "exampleProjection": { + "transforms": [ + { + "name": "removeDeprecatedShapes", + "args": { + "relativeDate": "2024-10-10", + "relativeVersion": "1.1.0" + } + } + ] + } + } + } + .. _renameShapes-transform: renameShapes @@ -1964,3 +2019,5 @@ Assuming ``hello.sh`` is on the PATH and might look something like: .. _Apache Maven: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html .. _Maven Central: https://search.maven.org .. _official Maven documentation: https://maven.apache.org/pom.html#dependency-version-requirement-specification +.. _SemVer Specification: https://semver.org/ +.. _ISO 8601: https://www.iso.org/iso-8601-date-and-time-format.html diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapes.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapes.java new file mode 100644 index 00000000000..c0b08ab8713 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapes.java @@ -0,0 +1,79 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.build.transforms; + +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.transform.ModelTransformer; + +/** + * {@code removeDeprecatedShapes} removes shapes from a model if they have been deprecated before a version or date. + */ +public final class RemoveDeprecatedShapes extends ConfigurableProjectionTransformer { + + /** + * {@code RemoveDeprecatedShapes} configuration settings. + */ + public static final class Config { + private String relativeDate; + private String relativeVersion; + + /** + * Gets the date used to filter deprecated shapes. + * + * @return The date used to filter deprecated shapes. + */ + public String getRelativeDate() { + return relativeDate; + } + + /** + * Sets the date used to filter deprecated shapes. + * + * @param relativeDate The date used to filter deprecated shapes. + */ + public void setRelativeDate(String relativeDate) { + this.relativeDate = relativeDate; + } + + /** + * Gets the version used to filter deprecated shapes. + * + * @return The version used to filter deprecated shapes. + */ + public String getRelativeVersion() { + return relativeVersion; + } + + /** + * Sets the version used to filter deprecated shapes. + * + * @param relativeVersion The version used to filter deprecated shapes. + */ + public void setRelativeVersion(String relativeVersion) { + this.relativeVersion = relativeVersion; + } + } + + @Override + public String getName() { + return "removeDeprecatedShapes"; + } + + @Override + public Class getConfigType() { + return Config.class; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + model = transformer.filterDeprecatedRelativeDate(model, config.getRelativeDate()); + model = transformer.filterDeprecatedRelativeVersion(model, config.getRelativeVersion()); + return model; + } +} diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapesTest.java new file mode 100644 index 00000000000..4e9fa5d9f32 --- /dev/null +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveDeprecatedShapesTest.java @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.build.transforms; + +import static org.junit.jupiter.api.Assertions.assertFalse; +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.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; + +public class RemoveDeprecatedShapesTest { + @Test + public void removesAllDeprecatedShapes() throws Exception { + Model model = Model.assembler() + .addImport(Paths.get(getClass().getResource("remove-deprecated.smithy").toURI())) + .assemble() + .unwrap(); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode() + .withMember("relativeDate", Node.from("2024-10-10")) + .withMember("relativeVersion", Node.from("1.1.0"))) + .build(); + Model result = new RemoveDeprecatedShapes().transform(context); + + // Deprecated by date removed + assertFalse(result.getShape(ShapeId.from("smithy.example#FilteredBeforeHyphens")).isPresent()); + assertFalse(result.getShape(ShapeId.from("smithy.example#FilteredVersionBefore")).isPresent()); + + // Equal to the filter retained + assertTrue(result.getShape(ShapeId.from("smithy.example#NotFilteredDateEquals")).isPresent()); + assertTrue(result.getShape(ShapeId.from("smithy.example#NotFilteredVersionEquals")).isPresent()); + + // After filter retained + assertTrue(result.getShape(ShapeId.from("smithy.example#NotFilteredDateAfter")).isPresent()); + assertTrue(result.getShape(ShapeId.from("smithy.example#NotFilteredVersionAfter")).isPresent()); + } +} diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/remove-deprecated.smithy b/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/remove-deprecated.smithy new file mode 100644 index 00000000000..2e1d85bd251 --- /dev/null +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/remove-deprecated.smithy @@ -0,0 +1,33 @@ +$version: "2.0" + +namespace smithy.example + +@deprecated(message: "Should be filtered as it is deprecated before date", since: "2024-10-09") +structure FilteredDateBefore { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated at the same date as the filter", since: "2024-10-10") +structure NotFilteredDateEquals { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter date", since: "2024-10-11") +structure NotFilteredDateAfter { + field: String +} + +@deprecated(message: "Should be filtered as it is deprecated before version", since: "1.0.0") +structure FilteredVersionBefore { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated at the same version as the filter", since: "1.1.0") +structure NotFilteredVersionEquals { + field: String +} + +@deprecated(message: "Should NOT be filtered as it is deprecated after the filter version", since: "1.1.1") +structure NotFilteredVersionAfter { + field: String +} From 9697eec1f2c637b036eb4b320cedd0743cf2ed4d Mon Sep 17 00:00:00 2001 From: Hunter Mellema Date: Wed, 13 Nov 2024 09:54:27 -0700 Subject: [PATCH 3/3] Update based on PR comments --- .../FilterDeprecatedRelativeDate.java | 32 +++++++++---------- .../FilterDeprecatedRelativeVersion.java | 6 ++-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java index e3b640d8d7c..49a04818389 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeDate.java @@ -21,7 +21,7 @@ final class FilterDeprecatedRelativeDate { "^([0-9]{4})-?(1[0-2]|0[1-9])-?(3[01]|0[1-9]|[12][0-9])$" ); - public final String relativeDate; + private final String relativeDate; FilterDeprecatedRelativeDate(String relativeDate) { if (relativeDate != null && !isIso8601Date(relativeDate)) { @@ -33,7 +33,7 @@ final class FilterDeprecatedRelativeDate { this.relativeDate = relativeDate != null ? relativeDate.replace("-", "") : null; } - public Model transform(ModelTransformer transformer, Model model) { + Model transform(ModelTransformer transformer, Model model) { // If there is no filter. Exit without traversing shapes if (relativeDate == null) { return model; @@ -41,22 +41,20 @@ public Model transform(ModelTransformer transformer, Model model) { Set shapesToRemove = new HashSet<>(); for (Shape shape : model.getShapesWithTrait(DeprecatedTrait.class)) { - if (shape.hasTrait(DeprecatedTrait.class)) { - Optional sinceOptional = shape.expectTrait(DeprecatedTrait.class).getSince(); - if (!sinceOptional.isPresent()) { - continue; - } - String since = sinceOptional.get(); + Optional sinceOptional = shape.expectTrait(DeprecatedTrait.class).getSince(); + if (!sinceOptional.isPresent()) { + continue; + } + String since = sinceOptional.get(); - if (isIso8601Date(since)) { - // Compare lexicographical ordering without hyphens. - if (relativeDate.compareTo(since.replace("-", "")) > 0) { - LOGGER.fine("Filtering deprecated shape: `" - + shape + "`" - + ". Shape was deprecated as of: " + since - ); - shapesToRemove.add(shape); - } + if (isIso8601Date(since)) { + // Compare lexicographical ordering without hyphens. + if (relativeDate.compareTo(since.replace("-", "")) > 0) { + LOGGER.fine("Filtering deprecated shape: `" + + shape + "`" + + ". Shape was deprecated as of: " + since + ); + shapesToRemove.add(shape); } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java index 5eec64a6e45..ed00b28aa9d 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FilterDeprecatedRelativeVersion.java @@ -26,7 +26,7 @@ final class FilterDeprecatedRelativeVersion { + "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" ); - public final String relativeVersion; + private final String relativeVersion; FilterDeprecatedRelativeVersion(String relativeVersion) { if (relativeVersion != null && !isSemVer(relativeVersion)) { @@ -38,7 +38,7 @@ final class FilterDeprecatedRelativeVersion { this.relativeVersion = relativeVersion; } - public Model transform(ModelTransformer transformer, Model model) { + Model transform(ModelTransformer transformer, Model model) { // If there are no filters. Exit without traversing shapes if (relativeVersion == null) { return model; @@ -68,7 +68,7 @@ public Model transform(ModelTransformer transformer, Model model) { return transformer.removeShapes(model, shapesToRemove); } - public static boolean isSemVer(String string) { + private static boolean isSemVer(String string) { return SEMVER_REGEX.matcher(string).matches(); }