Skip to content

Commit 149f88a

Browse files
authored
feat!: XML response deserialization (#644)
1 parent 3f2a084 commit 149f88a

File tree

134 files changed

+4273
-4055
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+4273
-4055
lines changed

Package.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ let package = Package(
3636
dependencies: [
3737
.package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.26.0"),
3838
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
39-
.package(url: "https://github.com/MaxDesiatov/XMLCoder.git", exact: "0.17.0")
4039
],
4140
targets: [
4241
.target(
@@ -45,7 +44,6 @@ let package = Package(
4544
"SmithyXML",
4645
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"),
4746
.product(name: "Logging", package: "swift-log"),
48-
.product(name: "XMLCoder", package: "XMLCoder")
4947
]
5048
),
5149
.target(name: "SmithyReadWrite"),
@@ -71,7 +69,7 @@ let package = Package(
7169
),
7270
.testTarget(
7371
name: "SmithyXMLTests",
74-
dependencies: ["SmithyXML"]
72+
dependencies: ["SmithyXML", "ClientRuntime"]
7573
),
7674
.testTarget(
7775
name: "SmithyTimestampsTests",

Sources/ClientRuntime/EventStream/DefaultMessageDecoderStream.swift

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,36 @@
88
extension EventStream {
99

1010
/// Stream adapter that decodes input data into `EventStream.Message` objects.
11-
public struct DefaultMessageDecoderStream<Event: MessageUnmarshallable>: MessageDecoderStream {
11+
public struct DefaultMessageDecoderStream<Event>: MessageDecoderStream {
1212
public typealias Element = Event
1313

1414
let stream: ReadableStream
1515
let messageDecoder: MessageDecoder
16-
let responseDecoder: ResponseDecoder
16+
let unmarshalClosure: UnmarshalClosure<Event>
1717

18-
public init(stream: ReadableStream, messageDecoder: MessageDecoder, responseDecoder: ResponseDecoder) {
18+
public init(
19+
stream: ReadableStream,
20+
messageDecoder: MessageDecoder,
21+
unmarshalClosure: @escaping UnmarshalClosure<Event>
22+
) {
1923
self.stream = stream
2024
self.messageDecoder = messageDecoder
21-
self.responseDecoder = responseDecoder
25+
self.unmarshalClosure = unmarshalClosure
2226
}
2327

2428
public struct AsyncIterator: AsyncIteratorProtocol {
2529
let stream: ReadableStream
2630
let messageDecoder: MessageDecoder
27-
let responseDecoder: ResponseDecoder
31+
let unmarshalClosure: UnmarshalClosure<Event>
2832

29-
init(stream: ReadableStream, messageDecoder: MessageDecoder, responseDecoder: ResponseDecoder) {
33+
init(
34+
stream: ReadableStream,
35+
messageDecoder: MessageDecoder,
36+
unmarshalClosure: @escaping UnmarshalClosure<Event>
37+
) {
3038
self.stream = stream
3139
self.messageDecoder = messageDecoder
32-
self.responseDecoder = responseDecoder
40+
self.unmarshalClosure = unmarshalClosure
3341
}
3442

3543
mutating public func next() async throws -> Event? {
@@ -42,8 +50,7 @@ extension EventStream {
4250

4351
// if we have a message in the decoder buffer, return it
4452
if let message = try messageDecoder.message() {
45-
let event = try Element(message: message, decoder: responseDecoder)
46-
return event
53+
return try unmarshalClosure(message)
4754
}
4855

4956
data = try await stream.readAsync(upToCount: Int.max)
@@ -61,7 +68,7 @@ extension EventStream {
6168
AsyncIterator(
6269
stream: stream,
6370
messageDecoder: messageDecoder,
64-
responseDecoder: responseDecoder
71+
unmarshalClosure: unmarshalClosure
6572
)
6673
}
6774
}

Sources/ClientRuntime/EventStream/MessageUnmarshallable.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@ public protocol MessageUnmarshallable {
1616
/// using the same decoder.
1717
init(message: EventStream.Message, decoder: ResponseDecoder) throws
1818
}
19+
20+
public typealias UnmarshalClosure<T> = (EventStream.Message) throws -> T
21+
22+
/// Provides an `UnmarshalClosure` for event payloads that are Swift `Decodable`.
23+
/// - Parameter responseDecoder: The Swift `Decoder` to be used for decoding this event payload.
24+
/// - Returns: An `UnmarshalClosure` that uses the provided decoder to decode event payloads.
25+
public func jsonUnmarshalClosure<T: MessageUnmarshallable>(responseDecoder: ResponseDecoder) -> UnmarshalClosure<T> {
26+
return { message in
27+
try T(message: message, decoder: responseDecoder)
28+
}
29+
}

Sources/ClientRuntime/Networking/Http/HTTPResponseClosure.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,34 @@
55
// SPDX-License-Identifier: Apache-2.0
66
//
77

8-
public typealias HTTPResponseClosure<OperationStackOutput> = (HttpResponse) async throws -> OperationStackOutput
8+
public typealias HTTPResponseOutputClosure<OperationStackOutput> = (HttpResponse) async throws -> OperationStackOutput
99

10+
public typealias HTTPResponseOutputBinding<OperationStackOutput, Reader> =
11+
(HttpResponse, HTTPResponseDocumentBinding<Reader>) async throws -> OperationStackOutput
12+
13+
/// Provides a response closure for a type that conforms to the `HttpResponseBinding` decoding protocol.
14+
///
15+
/// This allows for use of JSON and FormURL serialized types with closure-based deserialization.
16+
/// - Parameter decoder: The decoder to be used for decoding the value.
17+
/// - Returns: A `HTTPResponseOutputClosure` that can be used to decode a value of the specified type.
1018
public func responseClosure<OperationStackOutput: HttpResponseBinding, Decoder: ResponseDecoder>(
1119
decoder: Decoder
12-
) -> HTTPResponseClosure<OperationStackOutput> {
20+
) -> HTTPResponseOutputClosure<OperationStackOutput> {
1321
return { response in
1422
try await OperationStackOutput(httpResponse: response, decoder: decoder)
1523
}
1624
}
25+
26+
/// Provides a response closure for a type that provides closures for its output bindings.
27+
/// - Parameters:
28+
/// - responseOutputBinding: The `HTTPResponseOutputBinding` for the model to be deserialized.
29+
/// - responseDocumentBinding: A `HTTPResponseDocumentBinding` to convert the HTTP response to a `Reader`.
30+
/// - Returns: a `HTTPResponseOutputClosure` that can be used to deserialize a value of the specified type.
31+
public func responseClosure<OperationStackOutput, Reader>(
32+
_ responseOutputBinding: @escaping HTTPResponseOutputBinding<OperationStackOutput, Reader>,
33+
_ responseDocumentBinding: @escaping HTTPResponseDocumentBinding<Reader>
34+
) -> HTTPResponseOutputClosure<OperationStackOutput> {
35+
return { response in
36+
try await responseOutputBinding(response, responseDocumentBinding)
37+
}
38+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import class SmithyXML.Reader
9+
10+
public typealias HTTPResponseDocumentBinding<Reader> = (HttpResponse) async throws -> Reader
11+
12+
/// Creates a `HTTPResponseDocumentBinding` for converting a HTTP response into a `Reader`.
13+
public var responseDocumentBinding: HTTPResponseDocumentBinding<Reader> {
14+
return { response in
15+
let data = try await response.body.readData()
16+
response.body = .data(data)
17+
return try Reader.from(data: data ?? Data())
18+
}
19+
}

Sources/ClientRuntime/Networking/Http/HTTPResponseErrorClosure.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,20 @@
55
// SPDX-License-Identifier: Apache-2.0
66
//
77

8+
import class SmithyXML.Reader
9+
10+
/// Defines a closure that can be used to convert a HTTP response to a Swift `Error`.
811
public typealias HTTPResponseErrorClosure = (HttpResponse) async throws -> Error
912

13+
/// Defines a closure that can be used to convert a HTTP response and `Reader` for that response to a Swift `Error`.
14+
public typealias HTTPResponseErrorBinding<Reader> =
15+
(HttpResponse, HTTPResponseDocumentBinding<Reader>) async throws -> Error
16+
17+
/// Provides a `HTTPResponseErrorClosure` for types that have Swift Decodable-based deserialization.
18+
/// - Parameters:
19+
/// - errorBinding: The `HttpResponseErrorBinding`-conforming type for this operation.
20+
/// - decoder: The Swift `Decoder` to be used with the response.
21+
/// - Returns: The `HTTPResponseErrorClosure` for deserializing this error.
1022
public func responseErrorClosure<OperationErrorBinding: HttpResponseErrorBinding, Decoder: ResponseDecoder>(
1123
_ errorBinding: OperationErrorBinding.Type,
1224
decoder: Decoder
@@ -15,3 +27,17 @@ public func responseErrorClosure<OperationErrorBinding: HttpResponseErrorBinding
1527
try await OperationErrorBinding.makeError(httpResponse: response, decoder: decoder)
1628
}
1729
}
30+
31+
/// Provides a `HTTPResponseErrorClosure` for types that have closure-based deserialization.
32+
/// - Parameters:
33+
/// - responseErrorBinding: The `HTTPResponseErrorBinding` closure for this operation.
34+
/// - responseDocumentBinding: The `HTTPResponseDocumentBinding` closure for converting the HTTP response into a `Reader`.
35+
/// - Returns: The `HTTPResponseErrorClosure` for deserializing this error.
36+
public func responseErrorClosure<Reader>(
37+
_ responseErrorBinding: @escaping HTTPResponseErrorBinding<Reader>,
38+
_ responseDocumentBinding: @escaping HTTPResponseDocumentBinding<Reader>
39+
) -> HTTPResponseErrorClosure {
40+
return { response in
41+
try await responseErrorBinding(response, responseDocumentBinding)
42+
}
43+
}

Sources/ClientRuntime/Networking/Http/Middlewares/DeserializeMiddleware.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
public struct DeserializeMiddleware<OperationStackOutput>: Middleware {
99
public var id: String = "Deserialize"
10-
let httpResponseClosure: HTTPResponseClosure<OperationStackOutput>
10+
let httpResponseClosure: HTTPResponseOutputClosure<OperationStackOutput>
1111
let httpResponseErrorClosure: HTTPResponseErrorClosure
1212

1313
public init(
14-
_ httpResponseClosure: @escaping HTTPResponseClosure<OperationStackOutput>,
14+
_ httpResponseClosure: @escaping HTTPResponseOutputClosure<OperationStackOutput>,
1515
_ httpResponseErrorClosure: @escaping HTTPResponseErrorClosure
1616
) {
1717
self.httpResponseClosure = httpResponseClosure

Sources/ClientRuntime/Networking/Http/SdkHttpRequest.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import struct Foundation.URLRequest
1616
// we need to maintain a reference to this same request while we add headers
1717
// in the CRT engine so that is why it's a class
1818
public class SdkHttpRequest {
19-
public let body: ByteStream
19+
public var body: ByteStream
2020
public let endpoint: Endpoint
2121
public let method: HttpMethodType
2222
private var additionalHeaders: Headers = Headers()
@@ -102,7 +102,9 @@ public extension URLRequest {
102102
self.httpMethod = sdkRequest.method.rawValue
103103
// Set body, handling any serialization errors
104104
do {
105-
self.httpBody = try await sdkRequest.body.readData()
105+
let data = try await sdkRequest.body.readData()
106+
sdkRequest.body = .data(data)
107+
self.httpBody = data
106108
} catch {
107109
throw ClientError.serializationFailed("Failed to construct URLRequest due to HTTP body conversion failure.")
108110
}

Sources/ClientRuntime/Networking/Http/URLSession/URLSessionConfiguration+HTTPClientConfiguration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import class Foundation.URLSessionConfiguration
1212
extension URLSessionConfiguration {
1313

1414
public static func from(httpClientConfiguration: HttpClientConfiguration) -> URLSessionConfiguration {
15-
var config = URLSessionConfiguration.default
15+
let config = URLSessionConfiguration.default
1616
if let connectTimeout = httpClientConfiguration.connectTimeout {
1717
config.timeoutIntervalForRequest = connectTimeout
1818
}

Sources/ClientRuntime/PrimitiveTypeExtensions/Indirect.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
@propertyWrapper
99
public class Indirect<T> {
10-
public var wrappedValue: T?
10+
public var wrappedValue: Optional<T>
1111

12-
public init(wrappedValue: T? = nil) {
12+
public init(wrappedValue: Optional<T>) {
1313
self.wrappedValue = wrappedValue
1414
}
1515
}

0 commit comments

Comments
 (0)