Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e5a13dd
feat: Schema-based RestXML serialization/deserialization
dayaffe Mar 30, 2026
bfe3316
fix: Update MockHTTPRestXMLProtocolGenerator for schema-based
dayaffe Mar 30, 2026
2a34000
fix: Code review fixes
dayaffe Mar 30, 2026
d4413ee
cleanup: Remove dead code in Serializer.writeScalar
dayaffe Mar 30, 2026
c521feb
cleanup: Remove unnecessary comments, match CBOR comment patterns
dayaffe Mar 31, 2026
d31e482
cleanup: Remove MARK comments to match codebase pattern
dayaffe Mar 31, 2026
abae2ab
refactor: Rewrite Serializer to wrap SmithyXML.Writer
dayaffe Mar 31, 2026
60bc387
fix: SwiftLint - sort imports and fix line length
dayaffe Apr 2, 2026
d7390b4
fix: Delete obsolete XML serde tests and update test models
dayaffe Apr 6, 2026
6c3e7a3
fix: Resolve ktlint function-signature violations
dayaffe Apr 17, 2026
7cfeac4
Merge epic/sbs into day/schema-xml
dayaffe Apr 17, 2026
0e562ca
fix: Generate HTTP binding providers for schema-based RestXML
dayaffe Apr 18, 2026
20e21bf
chore: Retrigger CI to pick up companion fix (SmithyRestXML dep in pr…
dayaffe Apr 20, 2026
7bdc134
fix(SmithyRestXML): Avoid libxml2 parse on empty response body
dayaffe Apr 20, 2026
5ecbe05
feat(SmithyRestXML): Extract HTTP response bindings in schema-based d…
dayaffe Apr 20, 2026
8cc3ba5
Merge remote-tracking branch 'origin/epic/sbs' into day/schema-xml
dayaffe Apr 20, 2026
8caa4b7
fix: Resolve SwiftLint line_length violation in Deserializer
dayaffe Apr 20, 2026
0616fa4
fix(SmithyRestXML): Enumerate synthesized children for list-typed HTT…
dayaffe Apr 20, 2026
8db27d1
fix(SmithyRestXML): Timestamp-header default format + prefix-headers map
dayaffe Apr 21, 2026
7bd40a0
fix(SmithyRestXML): Add SmithyCodeGeneratorPlugin, HTTP traits, fix s…
dayaffe Apr 21, 2026
dbf35ef
fix(SmithyCodegenCore): Add XmlNameTrait to synthetic input/output sh…
dayaffe Apr 21, 2026
8620209
fix: XmlNameTrait inheritance, payload xmlName, service xmlNamespace,…
dayaffe Apr 22, 2026
6a8a705
fix(SmithyRestXML): Narrow unwrapped-output check to leaf nodes only
dayaffe Apr 22, 2026
2f5455f
fix(SmithyRestXML): Handle streaming @httpPayload without consuming b…
dayaffe Apr 22, 2026
8ddf59c
fix(SmithyRestXML): Detect streaming body by ByteStream case, not sch…
dayaffe Apr 22, 2026
88e00fa
fix: Resolve swiftlint line_length violations in Serializer.swift
dayaffe Apr 22, 2026
31ea1fe
fix: Resolve swiftlint sorted_imports violations
dayaffe Apr 22, 2026
882e210
fix: Resolve remaining swiftlint sorted_imports violation in Serializ…
dayaffe Apr 22, 2026
6d95570
fix(SmithyRestXML): Restore schema-based streaming payload detection
dayaffe Apr 22, 2026
7e21375
fix: Repair merged import line in HTTPClientProtocol.swift
dayaffe Apr 22, 2026
308b5d0
test: Update PackageManifestGeneratorTests for new plugin in schema-b…
dayaffe Apr 22, 2026
87bf42f
fix(SmithyRestXML): Handle streaming @httpPayload in Deserializer and…
dayaffe Apr 23, 2026
609c3e1
fix(SmithyRestXML): Handle streaming @httpPayload in serialization an…
dayaffe Apr 23, 2026
3d21fdd
fix(SmithyRestXML): Handle empty-body 404 as NotFound error for S3 He…
dayaffe Apr 24, 2026
4a5ccac
Merge branch 'epic/sbs' into day/schema-xml
dayaffe Apr 24, 2026
f4b1676
fix: Add @_spi(SchemaBasedSerde) to all trait and SmithyRestXML publi…
dayaffe Apr 24, 2026
d2ce4f8
fix: Add SchemaBasedSerde SPI to SmithyRestXMLTypes.kt
dayaffe Apr 24, 2026
dcb0aef
fix: Render empty struct XML elements and remove unused import
dayaffe Apr 24, 2026
a35d4fa
fix: Handle event stream responses in RestXML deserialization
dayaffe Apr 24, 2026
3c715a2
fix: Detect event streams via schema instead of content-type
dayaffe Apr 24, 2026
efaceec
fix: RestXML event stream target + error resolver hook
dayaffe Apr 27, 2026
3281048
fix: skip XML parsing for blob event payloads
dayaffe Apr 27, 2026
61e7e6d
Merge branch 'epic/sbs' into day/schema-xml
dayaffe Apr 27, 2026
b7bd655
fix: style consistency in RawBlobDeserializer + error message
dayaffe Apr 27, 2026
6f7ca6b
fixup: deslop + epic-pattern alignment for SBS RestXML
dayaffe May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ let package = Package(
.library(name: "SmithySerialization", targets: ["SmithySerialization"]),
.library(name: "SmithyAWSJSON", targets: ["SmithyAWSJSON"]),
.library(name: "SmithyRPCv2CBOR", targets: ["SmithyRPCv2CBOR"]),
.library(name: "SmithyRestXML", targets: ["SmithyRestXML"]),
.library(name: "ClientRuntime", targets: ["ClientRuntime"]),
.library(name: "SmithyRetriesAPI", targets: ["SmithyRetriesAPI"]),
.library(name: "SmithyRetries", targets: ["SmithyRetries"]),
Expand Down Expand Up @@ -315,6 +316,17 @@ let package = Package(
"SmithyCBOR",
]
),
.target(
name: "SmithyRestXML",
dependencies: [
"ClientRuntime",
"Smithy",
"SmithyEventStreams",
"SmithySerialization",
"SmithyXML",
"SmithyTimestamps",
]
),
.testTarget(
name: "ClientRuntimeTests",
dependencies: [
Expand Down
11 changes: 11 additions & 0 deletions Sources/Smithy/TraitLibrary/AllSupportedTraits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ private let allSupportedTraitTypes: [ShapeID: any Trait.Type] = [
ErrorTrait.id: ErrorTrait.self,
EventHeaderTrait.id: EventHeaderTrait.self,
EventPayloadTrait.id: EventPayloadTrait.self,
HttpHeaderTrait.id: HttpHeaderTrait.self,
HttpLabelTrait.id: HttpLabelTrait.self,
HttpPayloadTrait.id: HttpPayloadTrait.self,
HttpPrefixHeadersTrait.id: HttpPrefixHeadersTrait.self,
HttpQueryTrait.id: HttpQueryTrait.self,
HttpQueryParamsTrait.id: HttpQueryParamsTrait.self,
HttpResponseCodeTrait.id: HttpResponseCodeTrait.self,
InputTrait.id: InputTrait.self,
OutputTrait.id: OutputTrait.self,
RequiredTrait.id: RequiredTrait.self,
Expand All @@ -36,6 +43,10 @@ private let allSupportedTraitTypes: [ShapeID: any Trait.Type] = [
StreamingTrait.id: StreamingTrait.self,
TimestampFormatTrait.id: TimestampFormatTrait.self,
UnitTypeTrait.id: UnitTypeTrait.self, // UnitTypeTrait will only ever appear in Prelude.unitSchema
XmlAttributeTrait.id: XmlAttributeTrait.self,
XmlFlattenedTrait.id: XmlFlattenedTrait.self,
XmlNameTrait.id: XmlNameTrait.self,
XmlNamespaceTrait.id: XmlNamespaceTrait.self,

// Synthetic traits
TargetsUnitTrait.id: TargetsUnitTrait.self,
Expand Down
20 changes: 20 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpHeaderTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpHeaderTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpHeader") }
public let value: String
public var node: Node { .string(value) }

public init(node: Node) throws {
guard case .string(let value) = node else {
throw TraitError("httpHeader trait requires a string value")
}
self.value = value
}
}
13 changes: 13 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpLabelTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpLabelTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpLabel") }
public var node: Node { [:] }
public init(node: Node) throws {}
}
13 changes: 13 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpPayloadTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpPayloadTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpPayload") }
public var node: Node { [:] }
public init(node: Node) throws {}
}
20 changes: 20 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpPrefixHeadersTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpPrefixHeadersTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpPrefixHeaders") }
public let value: String
public var node: Node { .string(value) }

public init(node: Node) throws {
guard case .string(let value) = node else {
throw TraitError("httpPrefixHeaders trait requires a string value")
}
self.value = value
}
}
13 changes: 13 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpQueryParamsTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpQueryParamsTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpQueryParams") }
public var node: Node { [:] }
public init(node: Node) throws {}
}
20 changes: 20 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpQueryTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpQueryTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpQuery") }
public let value: String
public var node: Node { .string(value) }

public init(node: Node) throws {
guard case .string(let value) = node else {
throw TraitError("httpQuery trait requires a string value")
}
self.value = value
}
}
13 changes: 13 additions & 0 deletions Sources/Smithy/TraitLibrary/HttpResponseCodeTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct HttpResponseCodeTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "httpResponseCode") }
public var node: Node { [:] }
public init(node: Node) throws {}
}
13 changes: 13 additions & 0 deletions Sources/Smithy/TraitLibrary/XmlAttributeTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct XmlAttributeTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "xmlAttribute") }
public var node: Node { [:] }
public init(node: Node) throws {}
}
13 changes: 13 additions & 0 deletions Sources/Smithy/TraitLibrary/XmlFlattenedTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct XmlFlattenedTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "xmlFlattened") }
public var node: Node { [:] }
public init(node: Node) throws {}
}
20 changes: 20 additions & 0 deletions Sources/Smithy/TraitLibrary/XmlNameTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct XmlNameTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "xmlName") }
public let value: String
public var node: Node { .string(value) }

public init(node: Node) throws {
guard case .string(let value) = node else {
throw TraitError("xmlName trait requires a string value")
}
self.value = value
}
}
33 changes: 33 additions & 0 deletions Sources/Smithy/TraitLibrary/XmlNamespaceTrait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

@_spi(SchemaBasedSerde)
public struct XmlNamespaceTrait: Trait {
public static var id: ShapeID { .init("smithy.api", "xmlNamespace") }
public let uri: String
public let prefix: String?
public var node: Node {
var dict: [String: Node] = ["uri": .string(uri)]
if let prefix { dict["prefix"] = .string(prefix) }
return .object(dict)
}

public init(node: Node) throws {
guard case .object(let dict) = node else {
throw TraitError("xmlNamespace trait requires an object value")
}
guard case .string(let uri) = dict["uri"] else {
throw TraitError("xmlNamespace trait requires a 'uri' string")
}
self.uri = uri
if case .string(let prefix) = dict["prefix"] {
self.prefix = prefix
} else {
self.prefix = nil
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import struct Smithy.ShapeID
import struct Smithy.TargetsUnitTrait
@_spi(SchemaBasedSerde)
import struct Smithy.TraitCollection
import struct Smithy.XmlNamespaceTrait
import struct Smithy.XmlNameTrait

extension Model {

Expand All @@ -27,6 +29,11 @@ extension Model {
.filter { $0.type == .operation }
.compactMap { $0 as? OperationShape }

// Get service-level @xmlNamespace to propagate to synthetic input shapes (for RestXML root element)
let serviceXmlNamespace = shapes.values
.first { $0.type == .service }
.flatMap { try? $0.traits.getTrait(XmlNamespaceTrait.self) }

// Make a copy of this model's shapes to modify
var newShapes = shapes

Expand All @@ -52,9 +59,24 @@ extension Model {
// Add UsedAsInput and UsedAsOutput traits to the input/output structures
// These traits allow us to identify inputs/outputs by trait, but allow us to
// leave the Smithy input & output traits as set on the original model.
let newInput = newStruct(newID: newInputShapeID, newTraits: [UsedAsInputTrait()], original: inputShape)
// Also add XmlNameTrait with the original shape name so XML serialization uses
// the correct root element name (e.g. "SimpleScalarPropertiesRequest" not "SimpleScalarPropertiesInput").
var inputExtraTraits = TraitCollection()
inputExtraTraits.add(UsedAsInputTrait())
if !inputShape.hasTrait(XmlNameTrait.self) && inputShape.id != Prelude.unitSchema.id {
inputExtraTraits.add(try XmlNameTrait(node: .string(inputShape.id.name)))
}
if let ns = serviceXmlNamespace, !inputShape.hasTrait(XmlNamespaceTrait.self) {
inputExtraTraits.add(ns)
}
var outputExtraTraits = TraitCollection()
outputExtraTraits.add(UsedAsOutputTrait())
if !outputShape.hasTrait(XmlNameTrait.self) && outputShape.id != Prelude.unitSchema.id {
outputExtraTraits.add(try XmlNameTrait(node: .string(outputShape.id.name)))
}
let newInput = newStruct(newID: newInputShapeID, newTraits: inputExtraTraits, original: inputShape)
let newInputShapeMembers = try renamedMembers(newID: newInputShapeID, original: inputShape)
let newOutput = newStruct(newID: newOutputShapeID, newTraits: [UsedAsOutputTrait()], original: outputShape)
let newOutput = newStruct(newID: newOutputShapeID, newTraits: outputExtraTraits, original: outputShape)
let newOutputShapeMembers = try renamedMembers(newID: newOutputShapeID, original: outputShape)

// Add the new input & output and their members to the new shape dictionary.
Expand Down
5 changes: 4 additions & 1 deletion Sources/SmithyCodegenCore/Schemas/SchemasCodegen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import struct Smithy.ShapeID
import enum Smithy.ShapeType
@_spi(SchemaBasedSerde)
import func Smithy.traitType
import struct Smithy.XmlNameTrait

/// A generator for the `Schemas.swift`
package struct SchemasCodegen {
Expand Down Expand Up @@ -139,7 +140,9 @@ package struct SchemasCodegen {
// Get all the trait IDs that apply to this member & sort
let memberTraitIDs = Set(memberShape.traits.traitDict.keys)
let targetTraitIDs = Set(try memberShape.target.traits.traitDict.keys)
let allTraitIDs = Array(memberTraitIDs.union(targetTraitIDs)).smithySorted()
// @xmlName on the target renames the target shape, not the member; don't inherit.
let inheritableTargetTraitIDs = targetTraitIDs.filter { $0 != XmlNameTrait.id }
let allTraitIDs = Array(memberTraitIDs.union(inheritableTargetTraitIDs)).smithySorted()

var pairs = [(ShapeID, Node)]()
for traitID in allTraitIDs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ struct EventContentDeserializer: ShapeDeserializer {

// Deserialize the event payload, to the member marked with @eventPayload if it exists,
// to the structure's members otherwise.
// Use a deserializer for the protocol in use, by making it from the codec.
let payloadDeserializer = try codec.makeDeserializer(data: message.payload)
if let payloadMember = schema.members.first(where: { $0.hasTrait(EventPayloadTrait.self) }) {
try T.readConsumer(payloadMember, &value, payloadDeserializer)
// Skip codec for blob payloads — codec would parse as XML/JSON.
if (payloadMember.target ?? payloadMember).type == .blob {
let blobDeserializer = RawBlobDeserializer(data: message.payload)
try T.readConsumer(payloadMember, &value, blobDeserializer)
} else {
let payloadDeserializer = try codec.makeDeserializer(data: message.payload)
try T.readConsumer(payloadMember, &value, payloadDeserializer)
}
} else {
let payloadDeserializer = try codec.makeDeserializer(data: message.payload)
try payloadDeserializer.readStruct(schema, &value)
}

Expand Down
Loading
Loading