Skip to content

Commit

Permalink
feat: Add deprecated shape remover (#1511)
Browse files Browse the repository at this point in the history
* Add deprecated shape remover & codegen tests for it.

* ktlint

* Update codegen test.

* Update codegen test.

---------

Co-authored-by: Sichan Yoo <chanyoo@amazon.com>
  • Loading branch information
sichanyoo and Sichan Yoo authored May 20, 2024
1 parent a918bcf commit 685cad9
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package software.amazon.smithy.aws.swift.codegen.model

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.traits.DeprecatedTrait
import software.amazon.smithy.model.transform.ModelTransformer
import software.amazon.smithy.swift.codegen.SwiftSettings
import software.amazon.smithy.swift.codegen.integration.SwiftIntegration
import software.amazon.smithy.swift.codegen.model.getTrait
import java.time.DateTimeException
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.function.Predicate

private val DEPRECATED_SINCE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd")

private val REMOVE_BEFORE_DATE = "2024-09-17"

/**
* Parses a string of yyyy-MM-dd format to [LocalDate], returning `null` if parsing fails.
*/
internal fun String.toLocalDate(): LocalDate? = try { LocalDate.parse(this, DEPRECATED_SINCE_DATE_FORMATTER) } catch (e: DateTimeException) { null }

class AWSDeprecatedShapeRemover : SwiftIntegration {
override fun preprocessModel(model: Model, settings: SwiftSettings): Model {
return ModelTransformer.create().removeShapesIf(
model,
Predicate<Shape> {
val since = it.getTrait<DeprecatedTrait>()?.since?.orElse(null) ?: return@Predicate false
val deprecatedDate = since.toLocalDate() ?: return@Predicate false.also {
println("Failed to parse `since` field $since as a date, skipping removal of deprecated shape $it")
}
return@Predicate deprecatedDate < REMOVE_BEFORE_DATE.toLocalDate()
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ software.amazon.smithy.aws.swift.codegen.PresignerGenerator
software.amazon.smithy.aws.swift.codegen.model.AWSClientContextParamsTransformer
software.amazon.smithy.aws.swift.codegen.model.AWSHttpTraitTransformer
software.amazon.smithy.aws.swift.codegen.model.AWSEndpointTraitTransformer
software.amazon.smithy.aws.swift.codegen.model.AWSDeprecatedShapeRemover
software.amazon.smithy.aws.swift.codegen.AWSClientConfigurationIntegration
software.amazon.smithy.swift.codegen.swiftintegrations.InitialRequestIntegration
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package software.amazon.smithy.aws.swift.codegen.model

import io.kotest.matchers.string.shouldContainOnlyOnce
import io.kotest.matchers.string.shouldNotContain
import org.junit.jupiter.api.Test
import software.amazon.smithy.aws.swift.codegen.TestContext
import software.amazon.smithy.aws.swift.codegen.TestUtils
import software.amazon.smithy.aws.swift.codegen.shouldSyntacticSanityCheck
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait

class AWSDeprecatedShapeRemoverTests {
@Test
fun `Shape deprecated before the cutoff date based on deprecated trait's since field gets removed`() {
val context = setupTests("deprecated-shape-removal-test.smithy", "com.test#Example")
val contents = TestUtils.getModelFileContents("Example", "OperationWithDeprecatedInputMembersInput.swift", context.manifest)
contents.shouldSyntacticSanityCheck()
val removedContent = """
@available(*, deprecated, message: "API deprecated since 2024-09-01")
public var deprecatedMemberWithCorrectlyFormedSinceField: Swift.String?
"""
contents.shouldNotContain(removedContent)
}

@Test
fun `Shape deprecated after the cutoff date remains unremoved`() {
val context = setupTests("deprecated-shape-removal-test.smithy", "com.test#Example")
val contents = TestUtils.getModelFileContents("Example", "OperationWithDeprecatedInputMembersInput.swift", context.manifest)
contents.shouldSyntacticSanityCheck()
val expectedContents = """
@available(*, deprecated, message: "API deprecated since 2024-10-01")
public var deprecatedMemberWithCorrectlyFormedSinceFieldButDeprecatedAfterCutoff: Swift.String?
"""
contents.shouldContainOnlyOnce(expectedContents)
}

@Test
fun `Shape with deprecated trait that has malformed since field remains unremoved`() {
val context = setupTests("deprecated-shape-removal-test.smithy", "com.test#Example")
val contents = TestUtils.getModelFileContents("Example", "OperationWithDeprecatedInputMembersInput.swift", context.manifest)
contents.shouldSyntacticSanityCheck()
val expectedContents = """
@available(*, deprecated, message: "API deprecated since 4.2.0")
public var deprecatedMemberWithMalformedSinceField: Swift.String?
"""
contents.shouldContainOnlyOnce(expectedContents)
}

@Test
fun `Shape with deprecated trait missing since field remains unremoved`() {
val context = setupTests("deprecated-shape-removal-test.smithy", "com.test#Example")
val contents = TestUtils.getModelFileContents("Example", "OperationWithDeprecatedInputMembersInput.swift", context.manifest)
contents.shouldSyntacticSanityCheck()
val expectedContents = """
@available(*, deprecated)
public var deprecatedMemberWithoutSinceField: Swift.String?
"""
contents.shouldContainOnlyOnce(expectedContents)
}

private fun setupTests(smithyFile: String, serviceShapeId: String): TestContext {
val context = TestUtils.executeDirectedCodegen(smithyFile, serviceShapeId, RestJson1Trait.ID)
context.ctx.delegator.flushWriters()
return context
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
$version: "2.0"
namespace com.test

use aws.api#service
use aws.protocols#restJson1
use aws.auth#sigv4

@service(sdkId: "Example")
@restJson1
@sigv4(name: "Example")
service Example {
version: "1.0.0",
operations: [
OperationWithDeprecatedInputMembers
]
}

@http(method: "POST", uri: "/foo")
operation OperationWithDeprecatedInputMembers {
input: InputWithDeprecatedMembers
}

structure InputWithDeprecatedMembers {
@deprecated(since: "2024-09-01")
deprecatedMemberWithCorrectlyFormedSinceField: String

@deprecated(since: "2024-10-01")
deprecatedMemberWithCorrectlyFormedSinceFieldButDeprecatedAfterCutoff: String

@deprecated(since: "4.2.0")
deprecatedMemberWithMalformedSinceField: String

@deprecated
deprecatedMemberWithoutSinceField: String
}

0 comments on commit 685cad9

Please sign in to comment.