Skip to content

Commit ae5a8cf

Browse files
authored
fix: more code de-deuplication (#401)
1 parent 226c54c commit ae5a8cf

26 files changed

+736
-683
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
public struct HeaderMiddleware<OperationStackInput: Encodable & Reflection & HeaderProvider,
9+
OperationStackOutput: HttpResponseBinding,
10+
OperationStackError: HttpResponseBinding>: Middleware {
11+
public let id: String = "\(String(describing: OperationStackInput.self))HeadersMiddleware"
12+
13+
public init() {}
14+
15+
public func handle<H>(context: Context,
16+
input: MInput,
17+
next: H) -> Result<MOutput, MError>
18+
where H: Handler,
19+
Self.MInput == H.Input,
20+
Self.MOutput == H.Output,
21+
Self.Context == H.Context,
22+
Self.MError == H.MiddlewareError {
23+
input.builder.withHeaders(input.operationInput.headers)
24+
25+
return next.handle(context: context, input: input)
26+
}
27+
28+
public typealias MInput = SerializeStepInput<OperationStackInput>
29+
public typealias MOutput = OperationOutput<OperationStackOutput>
30+
public typealias Context = HttpContext
31+
public typealias MError = SdkError<OperationStackError>
32+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
public protocol HeaderProvider {
9+
var headers: Headers { get }
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
public protocol QueryItemProvider {
9+
var queryItems: [URLQueryItem] { get }
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
public protocol URLPathProvider {
9+
var urlPath: String? { get }
10+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
public struct QueryItemMiddleware<OperationStackInput: Encodable & Reflection & QueryItemProvider,
9+
OperationStackOutput: HttpResponseBinding,
10+
OperationStackError: HttpResponseBinding>: Middleware {
11+
public let id: String = "\(String(describing: OperationStackInput.self))QueryItemMiddleware"
12+
13+
public init() {}
14+
15+
public func handle<H>(context: Context,
16+
input: MInput,
17+
next: H) -> Result<MOutput, MError>
18+
where H: Handler,
19+
Self.MInput == H.Input,
20+
Self.MOutput == H.Output,
21+
Self.Context == H.Context,
22+
Self.MError == H.MiddlewareError {
23+
for queryItem in input.operationInput.queryItems {
24+
input.builder.withQueryItem(queryItem)
25+
}
26+
27+
return next.handle(context: context, input: input)
28+
}
29+
30+
public typealias MInput = SerializeStepInput<OperationStackInput>
31+
public typealias MOutput = OperationOutput<OperationStackOutput>
32+
public typealias Context = HttpContext
33+
public typealias MError = SdkError<OperationStackError>
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
public struct URLPathMiddleware<OperationStackInput: Encodable & Reflection & URLPathProvider,
9+
OperationStackOutput: HttpResponseBinding,
10+
OperationStackError: HttpResponseBinding>: ClientRuntime.Middleware {
11+
public let id: Swift.String = "\(String(describing: OperationStackInput.self))URLPathMiddleware"
12+
13+
let urlPrefix: Swift.String?
14+
15+
public init(urlPrefix: Swift.String? = nil) {
16+
self.urlPrefix = urlPrefix
17+
}
18+
19+
public func handle<H>(context: Context,
20+
input: MInput,
21+
next: H) -> Swift.Result<MOutput, MError>
22+
where H: Handler,
23+
Self.MInput == H.Input,
24+
Self.MOutput == H.Output,
25+
Self.Context == H.Context,
26+
Self.MError == H.MiddlewareError {
27+
guard var urlPath = input.urlPath else {
28+
return .failure(.client(ClientError.pathCreationFailed("Creating the url path failed, a required property in the path was nil")))
29+
}
30+
if let urlPrefix = urlPrefix, !urlPrefix.isEmpty {
31+
urlPath = "\(urlPrefix)\(urlPath)"
32+
}
33+
var copiedContext = context
34+
copiedContext.attributes.set(key: AttributeKey<String>(name: "Path"), value: urlPath)
35+
return next.handle(context: copiedContext, input: input)
36+
}
37+
38+
public typealias MInput = OperationStackInput
39+
public typealias MOutput = OperationOutput<OperationStackOutput>
40+
public typealias Context = HttpContext
41+
public typealias MError = SdkError<OperationStackError>
42+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 XCTest
9+
import ClientRuntime
10+
import SmithyTestUtil
11+
12+
class ProviderTests: HttpRequestTestBase {
13+
14+
func testURlPathProvider() {
15+
var mockInput = MockInput()
16+
mockInput.value = 3
17+
18+
XCTAssert(mockInput.urlPath == "/3")
19+
}
20+
21+
func testURLPathMiddleware() {
22+
var mockInput = MockInput()
23+
mockInput.value = 3
24+
25+
let context = HttpContextBuilder().withDecoder(value: JSONDecoder()).build()
26+
27+
var operationStack = OperationStack<MockInput, MockOutput, MockMiddlewareError>(id: "testURLPathOperation")
28+
operationStack.initializeStep.intercept(position: .after, middleware: URLPathMiddleware())
29+
operationStack.deserializeStep.intercept(position: .after, middleware: MockDeserializeMiddleware<MockOutput, MockMiddlewareError>(id: "TestDeserializeMiddleware"))
30+
_ = operationStack.handleMiddleware(context: context,
31+
input: mockInput,
32+
next: MockHandler { (context, request) in
33+
34+
XCTAssert(context.getPath() == "/3")
35+
let httpResponse = HttpResponse(body: HttpBody.none, statusCode: HttpStatusCode.ok)
36+
let output = OperationOutput<MockOutput>(httpResponse: httpResponse)
37+
return .success(output)
38+
})
39+
}
40+
41+
func testQueryItemProvider() {
42+
var mockInput = MockInput()
43+
mockInput.value = 3
44+
45+
XCTAssert(mockInput.queryItems.count == 1)
46+
}
47+
48+
func testQueryItemMiddleware() {
49+
var mockInput = MockInput()
50+
mockInput.value = 3
51+
52+
let context = HttpContextBuilder().withDecoder(value: JSONDecoder()).build()
53+
54+
var operationStack = OperationStack<MockInput, MockOutput, MockMiddlewareError>(id: "testURLPathOperation")
55+
operationStack.serializeStep.intercept(position: .after, middleware: QueryItemMiddleware())
56+
operationStack.deserializeStep.intercept(position: .after, middleware: MockDeserializeMiddleware<MockOutput, MockMiddlewareError>(id: "TestDeserializeMiddleware"))
57+
_ = operationStack.handleMiddleware(context: context,
58+
input: mockInput,
59+
next: MockHandler { (context, request) in
60+
61+
XCTAssert(request.queryItems?.count == 1)
62+
XCTAssert(request.queryItems?.first(where: { queryItem in
63+
queryItem.value == "3"
64+
}) != nil)
65+
let httpResponse = HttpResponse(body: HttpBody.none, statusCode: HttpStatusCode.ok)
66+
let output = OperationOutput<MockOutput>(httpResponse: httpResponse)
67+
return .success(output)
68+
})
69+
}
70+
71+
func testHeaderProvider() {
72+
var mockInput = MockInput()
73+
mockInput.value = 3
74+
75+
XCTAssert(mockInput.headers.headers.count == 1)
76+
}
77+
78+
func testHeaderMiddleware() {
79+
var mockInput = MockInput()
80+
mockInput.value = 3
81+
82+
let context = HttpContextBuilder().withDecoder(value: JSONDecoder()).build()
83+
84+
var operationStack = OperationStack<MockInput, MockOutput, MockMiddlewareError>(id: "testURLPathOperation")
85+
operationStack.serializeStep.intercept(position: .after, middleware: HeaderMiddleware())
86+
operationStack.deserializeStep.intercept(position: .after, middleware: MockDeserializeMiddleware<MockOutput, MockMiddlewareError>(id: "TestDeserializeMiddleware"))
87+
_ = operationStack.handleMiddleware(context: context,
88+
input: mockInput,
89+
next: MockHandler { (context, request) in
90+
91+
XCTAssert(request.headers.headers.count == 1)
92+
XCTAssert(request.headers.headers.first(where: { header in
93+
header.value == ["3"]
94+
}) != nil)
95+
let httpResponse = HttpResponse(body: HttpBody.none, statusCode: HttpStatusCode.ok)
96+
let output = OperationOutput<MockOutput>(httpResponse: httpResponse)
97+
return .success(output)
98+
})
99+
}
100+
}
101+
102+
extension MockInput: URLPathProvider, QueryItemProvider, HeaderProvider {
103+
public var urlPath: String? {
104+
guard let value = value else {
105+
return nil
106+
}
107+
return "/\(value)"
108+
}
109+
110+
public var queryItems: [ClientRuntime.URLQueryItem] {
111+
var items = [ClientRuntime.URLQueryItem]()
112+
113+
if let value = value {
114+
let valueQueryItem = URLQueryItem(name: "test", value: "\(value)")
115+
items.append(valueQueryItem)
116+
}
117+
return items
118+
}
119+
120+
public var headers: Headers {
121+
var items = Headers()
122+
123+
if let value = value {
124+
let headerItem = Header(name: "test", value: "\(value)")
125+
items.add(headerItem)
126+
}
127+
128+
return items
129+
}
130+
}

Packages/SmithyTestUtil/Sources/RequestTestUtil/MockInput.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
import ClientRuntime
99

1010
public struct MockInput: Encodable, Reflection {
11-
var value: Int?
11+
public var value: Int?
1212
public init() {}
1313
}

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ClientRuntimeTypes.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,17 @@ object ClientRuntimeTypes {
6060
val MutateHeadersMiddleware = runtimeSymbol("MutateHeadersMiddleware")
6161
val OperationStack = runtimeSymbol("OperationStack")
6262
val URLHostMiddleware = runtimeSymbol("URLHostMiddleware")
63+
val URLPathMiddleware = runtimeSymbol("URLPathMiddleware")
64+
val QueryItemMiddleware = runtimeSymbol("QueryItemMiddleware")
65+
val HeaderMiddleware = runtimeSymbol("HeaderMiddleware")
6366
val SerializableBodyMiddleware = runtimeSymbol("SerializableBodyMiddleware")
6467
val NoopHandler = runtimeSymbol("NoopHandler")
68+
69+
object Providers {
70+
val URLPathProvider = runtimeSymbol("URLPathProvider")
71+
val QueryItemProvider = runtimeSymbol("QueryItemProvider")
72+
val HeaderProvider = runtimeSymbol("HeaderProvider")
73+
}
6574
}
6675

6776
object Core {

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpBindingProtocolGenerator.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInp
4747
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputUrlHostMiddleware
4848
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputUrlPathMiddleware
4949
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpBodyMiddleware
50-
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpHeaderMiddleware
51-
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpQueryItemMiddleware
52-
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpUrlPathMiddleware
50+
import software.amazon.smithy.swift.codegen.integration.middlewares.providers.HttpHeaderProvider
51+
import software.amazon.smithy.swift.codegen.integration.middlewares.providers.HttpQueryItemProvider
52+
import software.amazon.smithy.swift.codegen.integration.middlewares.providers.HttpUrlPathProvider
5353
import software.amazon.smithy.swift.codegen.integration.serde.DynamicNodeDecodingGeneratorStrategy
5454
import software.amazon.smithy.swift.codegen.integration.serde.UnionDecodeGeneratorStrategy
5555
import software.amazon.smithy.swift.codegen.integration.serde.UnionEncodeGeneratorStrategy
@@ -129,9 +129,9 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
129129
continue
130130
}
131131
val httpBindingResolver = getProtocolHttpBindingResolver(ctx, defaultContentType)
132-
HttpUrlPathMiddleware.renderUrlPathMiddleware(ctx, operation, httpBindingResolver)
133-
HttpHeaderMiddleware.renderHeaderMiddleware(ctx, operation, httpBindingResolver, defaultTimestampFormat)
134-
HttpQueryItemMiddleware.renderQueryMiddleware(ctx, operation, httpBindingResolver, defaultTimestampFormat)
132+
HttpUrlPathProvider.renderUrlPathMiddleware(ctx, operation, httpBindingResolver)
133+
HttpHeaderProvider.renderHeaderMiddleware(ctx, operation, httpBindingResolver, defaultTimestampFormat)
134+
HttpQueryItemProvider.renderQueryMiddleware(ctx, operation, httpBindingResolver, defaultTimestampFormat)
135135
HttpBodyMiddleware.renderBodyMiddleware(ctx, operation, httpBindingResolver)
136136
inputShapesWithHttpBindings.add(inputShapeId)
137137
}
@@ -146,21 +146,21 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
146146
.definitionFile("./$rootNamespace/models/$symbolName+Encodable.swift")
147147
.name(symbolName)
148148
.build()
149-
149+
val httpBodyMembers = shape.members()
150+
.filter { it.isInHttpBody() }
151+
.toList()
150152
ctx.delegator.useShapeWriter(encodeSymbol) { writer ->
151153
writer.openBlock("extension $symbolName: \$N, \$N {", "}", SwiftTypes.Protocols.Encodable, ClientRuntimeTypes.Core.Reflection) {
152154
writer.addImport(SwiftDependency.CLIENT_RUNTIME.target)
153-
val httpBodyMembers = shape.members()
154-
.filter { it.isInHttpBody() }
155-
.toList()
155+
156156
if (shouldRenderCodingKeysForEncodable) {
157157
generateCodingKeysForMembers(ctx, writer, httpBodyMembers)
158158
writer.write("")
159159
}
160160
renderStructEncode(ctx, shape, shapeMetadata, httpBodyMembers, writer, defaultTimestampFormat)
161161
}
162162
}
163-
if (shouldRenderDecodableBodyStructForInputShapes) {
163+
if (shouldRenderDecodableBodyStructForInputShapes && httpBodyMembers.isNotEmpty()) {
164164
renderBodyStructAndDecodableExtension(ctx, shape, mapOf())
165165
DynamicNodeDecodingGeneratorStrategy(ctx, shape, isForBodyStruct = true).renderIfNeeded()
166166
}

0 commit comments

Comments
 (0)