Skip to content

Commit

Permalink
Add transforms to remove deprecated shapes (smithy-lang#2452)
Browse files Browse the repository at this point in the history
Adds two transforms to smithy-model and directed codegen that allow users to filter out deprecated shapes by version and/or date.
  • Loading branch information
hpmellema authored and MYoung25 committed Nov 29, 2024
1 parent a0200f8 commit 8e9b558
Show file tree
Hide file tree
Showing 23 changed files with 757 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
57 changes: 57 additions & 0 deletions docs/source-2.0/guides/smithy-build-json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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<RemoveDeprecatedShapes.Config> {

/**
* {@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<Config> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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])$"
);

private 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;
}

Model transform(ModelTransformer transformer, Model model) {
// If there is no filter. Exit without traversing shapes
if (relativeDate == null) {
return model;
}

Set<Shape> shapesToRemove = new HashSet<>();
for (Shape shape : model.getShapesWithTrait(DeprecatedTrait.class)) {
Optional<String> 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();
}
}
Loading

0 comments on commit 8e9b558

Please sign in to comment.