Skip to content

Commit

Permalink
feat!: Replace the XML encoder with a custom Smithy implementation (#619
Browse files Browse the repository at this point in the history
)
  • Loading branch information
jbelkins authored Nov 29, 2023
1 parent b1a0cf4 commit 34aecef
Show file tree
Hide file tree
Showing 109 changed files with 2,220 additions and 2,527 deletions.
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ excluded:
- Sources/SmithyTestUtil/*
- Tests/ClientRuntimeTests/*
- Tests/SmithyTestUtilTests/*
- Tests/SmithyXMLTests/*
- Tests/SmithyTimestampsTests/*

analyzer_rules:
- unused_import
Expand Down
41 changes: 36 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ let package = Package(
],
products: [
.library(name: "ClientRuntime", targets: ["ClientRuntime"]),
.library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"])
.library(name: "SmithyReadWrite", targets: ["SmithyReadWrite"]),
.library(name: "SmithyXML", targets: ["SmithyXML"]),
.library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"]),
],
dependencies: [
.package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.17.0"),
Expand All @@ -21,22 +23,51 @@ let package = Package(
.target(
name: "ClientRuntime",
dependencies: [
"SmithyXML",
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"),
.product(name: "Logging", package: "swift-log"),
.product(name: "XMLCoder", package: "XMLCoder")
]
),
.testTarget(
name: "ClientRuntimeTests",
dependencies: ["ClientRuntime", "SmithyTestUtil"]
.target(name: "SmithyReadWrite"),
.target(
name: "SmithyXML",
dependencies: [
"SmithyReadWrite",
"SmithyTimestamps",
.target(name: "libxml2", condition: .when(platforms: [.linux]))
]
),
.systemLibrary(
name: "libxml2",
pkgConfig: "libxml-2.0",
providers: [
.apt(["libxml2 libxml2-dev"]),
.yum(["libxml2 libxml2-devel"])
]
),
.target(
name: "SmithyTimestamps"
),
.target(
name: "SmithyTestUtil",
dependencies: ["ClientRuntime"]
),
.testTarget(
name: "ClientRuntimeTests",
dependencies: ["ClientRuntime", "SmithyTestUtil"]
),
.testTarget(
name: "SmithyXMLTests",
dependencies: ["SmithyXML"]
),
.testTarget(
name: "SmithyTimestampsTests",
dependencies: ["SmithyTimestamps"]
),
.testTarget(
name: "SmithyTestUtilTests",
dependencies: ["SmithyTestUtil"]
)
),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ extension EventStream {
public class DefaultMessageEncoderStream<Event: MessageMarshallable>: MessageEncoderStream, Stream {
let stream: AsyncThrowingStream<Event, Error>
let messageEncoder: MessageEncoder
let messageSinger: MessageSigner
let messageSigner: MessageSigner
let requestEncoder: RequestEncoder
var readAsyncIterator: AsyncIterator?

public init(
stream: AsyncThrowingStream<Event, Error>,
messageEncoder: MessageEncoder,
requestEncoder: RequestEncoder,
messageSinger: MessageSigner
messageSigner: MessageSigner
) {
self.stream = stream
self.messageEncoder = messageEncoder
self.messageSinger = messageSinger
self.messageSigner = messageSigner
self.requestEncoder = requestEncoder
self.readAsyncIterator = makeAsyncIterator()
}

public struct AsyncIterator: AsyncIteratorProtocol {
let stream: AsyncThrowingStream<Event, Error>
let messageEncoder: MessageEncoder
var messageSinger: MessageSigner
var messageSigner: MessageSigner
let requestEncoder: RequestEncoder

private var lastMessageSent: Bool = false
Expand All @@ -42,12 +42,12 @@ extension EventStream {
stream: AsyncThrowingStream<Event, Error>,
messageEncoder: MessageEncoder,
requestEncoder: RequestEncoder,
messageSinger: MessageSigner
messageSigner: MessageSigner
) {
self.stream = stream
self.streamIterator = stream.makeAsyncIterator()
self.messageEncoder = messageEncoder
self.messageSinger = messageSinger
self.messageSigner = messageSigner
self.requestEncoder = requestEncoder
}

Expand All @@ -56,7 +56,7 @@ extension EventStream {
// There are no more messages in the base stream
// if we have not sent the last message, send it now
guard lastMessageSent else {
let emptySignedMessage = try await messageSinger.signEmpty()
let emptySignedMessage = try await messageSigner.signEmpty()
let data = try messageEncoder.encode(message: emptySignedMessage)
lastMessageSent = true
return data
Expand All @@ -70,7 +70,7 @@ extension EventStream {
let message = try event.marshall(encoder: requestEncoder)

// sign the message
let signedMessage = try await messageSinger.sign(message: message)
let signedMessage = try await messageSigner.sign(message: message)

// encode again the signed message
let data = try messageEncoder.encode(message: signedMessage)
Expand All @@ -83,7 +83,7 @@ extension EventStream {
stream: stream,
messageEncoder: messageEncoder,
requestEncoder: requestEncoder,
messageSinger: messageSinger
messageSigner: messageSigner
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import struct Foundation.Data

public struct BlobBodyMiddleware<OperationStackInput,
OperationStackOutput: HttpResponseBinding>: Middleware {
public let id: Swift.String = "BlobBodyMiddleware"

let keyPath: KeyPath<OperationStackInput, Data?>

public init(keyPath: KeyPath<OperationStackInput, Data?>) {
self.keyPath = keyPath
}

public func handle<H>(context: Context,
input: SerializeStepInput<OperationStackInput>,
next: H) async throws -> OperationOutput<OperationStackOutput>
where H: Handler,
Self.MInput == H.Input,
Self.MOutput == H.Output,
Self.Context == H.Context {
let body = HttpBody.data(input.operationInput[keyPath: keyPath])
input.builder.withBody(body)
return try await next.handle(context: context, input: input)
}

public typealias MInput = SerializeStepInput<OperationStackInput>
public typealias MOutput = OperationOutput<OperationStackOutput>
public typealias Context = HttpContext
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import struct Foundation.Data

public struct BlobStreamBodyMiddleware<OperationStackInput,
OperationStackOutput: HttpResponseBinding>: Middleware {
public let id: Swift.String = "BlobStreamBodyMiddleware"

let keyPath: KeyPath<OperationStackInput, ByteStream?>

public init(keyPath: KeyPath<OperationStackInput, ByteStream?>) {
self.keyPath = keyPath
}

public func handle<H>(context: Context,
input: SerializeStepInput<OperationStackInput>,
next: H) async throws -> OperationOutput<OperationStackOutput>
where H: Handler,
Self.MInput == H.Input,
Self.MOutput == H.Output,
Self.Context == H.Context {
if let byteStream = input.operationInput[keyPath: keyPath] {
let body = HttpBody(byteStream: byteStream)
input.builder.withBody(body)
}
return try await next.handle(context: context, input: input)
}

public typealias MInput = SerializeStepInput<OperationStackInput>
public typealias MOutput = OperationOutput<OperationStackOutput>
public typealias Context = HttpContext
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,24 @@
// SPDX-License-Identifier: Apache-2.0
//

public struct SerializableBodyMiddleware<OperationStackInput: Encodable,
OperationStackOutput: HttpResponseBinding>: Middleware {
public let id: Swift.String = "\(String(describing: OperationStackInput.self))BodyMiddleware"
import struct Foundation.Data
import typealias SmithyReadWrite.DocumentWritingClosure
import typealias SmithyReadWrite.WritingClosure

let xmlName: String?
public struct BodyMiddleware<OperationStackInput,
OperationStackOutput: HttpResponseBinding,
Writer>: Middleware {
public let id: Swift.String = "BodyMiddleware"

public init(xmlName: String? = nil) {
self.xmlName = xmlName
let documentWritingClosure: DocumentWritingClosure<OperationStackInput, Writer>
let inputWritingClosure: WritingClosure<OperationStackInput, Writer>

public init(
documentWritingClosure: @escaping DocumentWritingClosure<OperationStackInput, Writer>,
inputWritingClosure: @escaping WritingClosure<OperationStackInput, Writer>
) {
self.documentWritingClosure = documentWritingClosure
self.inputWritingClosure = inputWritingClosure
}

public func handle<H>(context: Context,
Expand All @@ -23,13 +33,7 @@ public struct SerializableBodyMiddleware<OperationStackInput: Encodable,
Self.MOutput == H.Output,
Self.Context == H.Context {
do {
let encoder = context.getEncoder()
let data: Data
if let xmlName = xmlName, let xmlEncoder = encoder as? XMLEncoder {
data = try xmlEncoder.encode(input.operationInput, withRootKey: xmlName)
} else {
data = try encoder.encode(input.operationInput)
}
let data = try documentWritingClosure(input.operationInput, inputWritingClosure)
let body = HttpBody.data(data)
input.builder.withBody(body)
} catch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import struct Foundation.Data

public struct EnumBodyMiddleware<OperationStackInput,
OperationStackOutput: HttpResponseBinding,
Primitive: RawRepresentable>: Middleware where Primitive.RawValue == String {
public let id: Swift.String = "EnumBodyMiddleware"

let keyPath: KeyPath<OperationStackInput, Primitive?>

public init(keyPath: KeyPath<OperationStackInput, Primitive?>) {
self.keyPath = keyPath
}

public func handle<H>(context: Context,
input: SerializeStepInput<OperationStackInput>,
next: H) async throws -> OperationOutput<OperationStackOutput>
where H: Handler,
Self.MInput == H.Input,
Self.MOutput == H.Output,
Self.Context == H.Context {
let bodyString = input.operationInput[keyPath: keyPath]?.rawValue ?? ""
let body = HttpBody.data(Data(bodyString.utf8))
input.builder.withBody(body)
return try await next.handle(context: context, input: input)
}

public typealias MInput = SerializeStepInput<OperationStackInput>
public typealias MOutput = OperationOutput<OperationStackOutput>
public typealias Context = HttpContext
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import struct Foundation.Data
import typealias SmithyReadWrite.DocumentWritingClosure
import typealias SmithyReadWrite.WritingClosure

public struct EventStreamBodyMiddleware<OperationStackInput,
OperationStackOutput: HttpResponseBinding,
OperationStackInputPayload: MessageMarshallable>:
Middleware {
public let id: Swift.String = "EventStreamBodyMiddleware"

let keyPath: KeyPath<OperationStackInput, AsyncThrowingStream<OperationStackInputPayload, Swift.Error>?>
let defaultBody: String?

public init(
keyPath: KeyPath<OperationStackInput, AsyncThrowingStream<OperationStackInputPayload, Swift.Error>?>,
defaultBody: String? = nil
) {
self.keyPath = keyPath
self.defaultBody = defaultBody
}

public func handle<H>(context: Context,
input: SerializeStepInput<OperationStackInput>,
next: H) async throws -> OperationOutput<OperationStackOutput>
where H: Handler,
Self.MInput == H.Input,
Self.MOutput == H.Output,
Self.Context == H.Context {
let encoder = context.getEncoder()
if let eventStream = input.operationInput[keyPath: keyPath] {
guard let messageEncoder = context.getMessageEncoder() else {
fatalError("Message encoder is required for streaming payload")
}
guard let messageSigner = context.getMessageSigner() else {
fatalError("Message signer is required for streaming payload")
}
let encoderStream = EventStream.DefaultMessageEncoderStream(
stream: eventStream,
messageEncoder: messageEncoder,
requestEncoder: encoder,
messageSigner: messageSigner
)
input.builder.withBody(.stream(encoderStream))
} else if let defaultBody {
input.builder.withBody(HttpBody.data(Data(defaultBody.utf8)))
}
return try await next.handle(context: context, input: input)
}

public typealias MInput = SerializeStepInput<OperationStackInput>
public typealias MOutput = OperationOutput<OperationStackOutput>
public typealias Context = HttpContext
}
Loading

0 comments on commit 34aecef

Please sign in to comment.