-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: s3 custom treatment getbucketlocation response #989
fix: s3 custom treatment getbucketlocation response #989
Conversation
… s3-custom-treatment-getbucketlocation-response
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should fix this by implementing support generically for @s3UnwrappedXmlOutput trait.
This could be done by:
- Add a new
FieldTrait
forXmlUnwrappedStruct
or similar and implement support in the XmlDeserializer for it. - Either override the descriptor generator for the S3 generator OR define our own generic trait for this and map the
s3UnwrappedXmlOutput
to our own generic trait. Thensmithy-kotlin
just operates on our own trait and a customization can preprocess the model to maps3UnwrappedXmlOutput
to whatever we are looking for/define.
… s3-custom-treatment-getbucketlocation-response
* Applies the [UnwrappedXMLOutput] custom-made [annotation trait](https://smithy.io/2.0/spec/model.html?highlight=annotation#annotation-traits) to a manually-curated list of structures | ||
* to mark when special unwrapped xml output deserialization is required. | ||
*/ | ||
class UnwrappedXMLOutputIntegration : KotlinIntegration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Generally our names should follow PascalCase, including treating abbreviations like "XML" as regular words for the purposes of capitalization (e.g., UnwrappedXmlOutputIntegration
).
when { | ||
shape.id.toString() in SHAPE_IDS -> { | ||
check(shape is StructureShape) { "Cannot apply UnwrappedXMLOutput to non-member shape" } | ||
shape.toBuilder().addTrait(UnwrappedXMLOutput()).build() | ||
} | ||
else -> shape | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Why do we need to check shape IDs from an allowlist? Why can't we just detect the aws.customizations#s3UnwrappedXmlOutput
trait and then apply the new UnwrappedXMLOutput
trait?
when {
shape is StructureShape && shape.hasTrait<S3UnwrappedXmlOutputTrait>() ->
shape.toBuilder().addTrait(UnwrappedXMLOutput()).build()
else -> shape
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that seems cleaner.
when { | ||
shape.id.toString() in SHAPE_IDS -> { | ||
check(shape is StructureShape) { "Cannot apply UnwrappedXMLOutput to non-member shape" } | ||
shape.toBuilder().addTrait(UnwrappedXMLOutput()).build() | ||
} | ||
else -> shape | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that seems cleaner.
import aws.smithy.kotlin.runtime.serde.xml.XmlNamespace | ||
import aws.smithy.kotlin.runtime.serde.xml.XmlSerialName | ||
|
||
internal fun deserializeWrapped(builder: GetBucketLocationResponse.Builder, payload: ByteArray) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: We shouldn't need this now right?
* Registers an integration that replaces the codegened GetBucketLocationDeserializer with | ||
* custom deserialization logic. | ||
* Registers an integration that tries deserializing the `GetBucketLocationOperation` | ||
* response as wrapped XML response when doing so as [unwrapped](https://smithy.io/2.0/aws/customizations/s3-customizations.html#aws-customizations-s3unwrappedxmloutput-trait) is unsuccessful. | ||
*/ | ||
class GetBucketLocationDeserializerIntegration : KotlinIntegration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: Shouldn't need this now either right?
override fun preprocessModel(model: Model, settings: KotlinSettings): Model { | ||
val unwrappedStructures = mutableSetOf<String>() | ||
ModelTransformer | ||
.create() | ||
.mapShapes(model) { shape -> | ||
when { | ||
shape is OperationShape && shape.hasTrait<S3UnwrappedXmlOutputTrait>() -> { | ||
if (shape.outputShape.toString() != "smithy.api#Unit") unwrappedStructures.add(shape.outputShape.toString()) | ||
shape | ||
} | ||
else -> shape | ||
} | ||
} | ||
|
||
return ModelTransformer | ||
.create() | ||
.mapShapes(model) { shape -> | ||
when { | ||
shape.id.toString() in unwrappedStructures -> { | ||
check(shape is StructureShape) { "Cannot apply UnwrappedXMLOutput to non-structure shape" } | ||
shape.toBuilder().addTrait(UnwrappedXmlOutput()).build() | ||
} | ||
else -> shape | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment: We don't need a full mapShapes
call to find the initial set of structs. We can just iterate through the model directly:
override fun preprocessModel(model: Model, settings: KotlinSettings) {
val unwrappedStructures = model
.operationShapes
.filter { it.hasTrait<S3UnwrappedXmlOutputTrait>() }
.map { it.outputShape }
.toSet()
return ModelTransformer
.create()
.mapShapes(model) { shape ->
when {
shape.id in unwrappedStructures ->
shape.toBuilder().addTrait(UnwrappedXmlOutput()).build()
else -> shape
}
}
}
if (shape.outputShape.toString() != "smithy.api#Unit") unwrappedStructures.add(shape.outputShape.toString()) | ||
shape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is checking for outputShape != smithy.api#Unit
important here? It seems like that would be a malformed model since there's no output to unwrap.
check(shape is StructureShape) { "Cannot apply UnwrappedXMLOutput to non-structure shape" } | ||
shape.toBuilder().addTrait(UnwrappedXmlOutput()).build() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is checking for shape is StructureShape
important here? It seems like the only things we add to the list are output shapes from operations marked with S3UnwrappedXmlOutputTrait
, which must be structures right?
fun deserializeUnwrappedResponse() { | ||
val responseXML = """ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">us-west-2</LocationConstraint> | ||
""".trimIndent() | ||
|
||
val response: HttpResponse = HttpResponse( | ||
HttpStatusCode(200, "Success"), | ||
Headers.invoke { }, | ||
HttpBody.fromBytes(responseXML.encodeToByteArray()), | ||
) | ||
|
||
val actual = runBlocking { | ||
GetBucketLocationOperationDeserializer().deserialize(ExecutionContext(), response) | ||
} | ||
|
||
assertEquals("UsWest2", actual.locationConstraint.toString()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: I'm confused by this test. Why is the expected output UsWest2
when response XML stated us-west-2
?
} | ||
|
||
@Test | ||
fun test2() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can these test
and test2
be given more precise names?
// Test s3 model where only the service name matters | ||
private fun model(serviceName: String): Model = | ||
""" | ||
@http(method: "PUT", uri: "/foo") | ||
operation Foo { } | ||
|
||
@http(method: "POST", uri: "/bar") | ||
operation Bar { } | ||
""" | ||
.prependNamespaceAndService(operations = listOf("Foo", "Bar"), serviceName = serviceName) | ||
.toSmithyModel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can grab this function from https://github.com/awslabs/aws-sdk-kotlin/blob/s3-custom-treatment-getbucketlocation-response/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/testutil/ModelUtil.kt#L7 instead of duplicating it
Note this wasn't available when you first created this PR, I merged it in sometime after :)
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
Issue #
194 - Custom treatment of
GetBucketLocation
responseDescription of changes
This was previously fixed using a customization but it left out error deserialization and deserialization of both wrapped and unwrapped non-error responses. Both possibilities are accounted for now
Also look at: smithy-lang/smithy-kotlin#897
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.