Skip to content

Commit 811e491

Browse files
authored
chore: unify host and host prefix middleware (#375)
1 parent 55aa8c3 commit 811e491

16 files changed

Lines changed: 243 additions & 12 deletions

Packages/SmithyTestUtil/Sources/RequestTestUtil/HttpRequestTestBase.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ open class HttpRequestTestBase: XCTestCase {
9999
let parsedHost = urlFromHost.host {
100100
deconflictedHost = parsedHost
101101
}
102-
if let resolvedHost = resolvedHost {
102+
if let resolvedHost = resolvedHost, !resolvedHost.isEmpty {
103103
deconflictedHost = resolvedHost
104104
}
105105
return deconflictedHost
@@ -111,6 +111,15 @@ open class HttpRequestTestBase: XCTestCase {
111111
}
112112
return hostCustomPath
113113
}
114+
115+
// Per spec, host can contain a path prefix, so this function is used to get only the host
116+
// https://awslabs.github.io/smithy/1.0/spec/http-protocol-compliance-tests.html#smithy-test-httprequesttests-trait
117+
public func hostOnlyFromHost(host: String) -> String? {
118+
guard !host.isEmpty, let hostOnly = URL(string: "http://\(host)")?.host else {
119+
return nil
120+
}
121+
return hostOnly
122+
}
114123

115124
func addQueryItems(queryParams: [String], builder: ExpectedSdkHttpRequestBuilder) {
116125
for queryParam in queryParams {
@@ -207,7 +216,8 @@ open class HttpRequestTestBase: XCTestCase {
207216
assertQueryItems(expected.queryItems, actual.queryItems)
208217

209218
XCTAssertEqual(expected.endpoint.path, actual.endpoint.path)
210-
219+
XCTAssertEqual(expected.endpoint.host, actual.endpoint.host)
220+
211221
assertForbiddenQueryItems(expected.forbiddenQueryItems, actual.queryItems)
212222

213223
assertRequiredQueryItems(expected.requiredQueryItems, actual.queryItems)

Packages/SmithyTestUtil/Tests/RequestTestUtilTests/HttpRequestTestBaseTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,37 @@ import XCTest
99

1010
class HttpRequestTestBaseTests: HttpRequestTestBase {
1111
static let host = "myapi.host.com"
12+
13+
public struct SayHelloInputURLHostMiddleware: ClientRuntime.Middleware {
14+
public let id: Swift.String = "SayHelloInputURLHostMiddleware"
15+
16+
let host: Swift.String?
17+
18+
public init(host: Swift.String? = nil) {
19+
self.host = host
20+
}
21+
22+
public func handle<H>(context: Context,
23+
input: SayHelloInput,
24+
next: H) -> Swift.Result<ClientRuntime.OperationOutput<MockOutput>, MError>
25+
where H: Handler,
26+
Self.MInput == H.Input,
27+
Self.MOutput == H.Output,
28+
Self.Context == H.Context,
29+
Self.MError == H.MiddlewareError
30+
{
31+
var copiedContext = context
32+
if let host = host {
33+
copiedContext.attributes.set(key: AttributeKey<String>(name: "Host"), value: host)
34+
}
35+
return next.handle(context: copiedContext, input: input)
36+
}
37+
38+
public typealias MInput = SayHelloInput
39+
public typealias MOutput = ClientRuntime.OperationOutput<MockOutput>
40+
public typealias Context = ClientRuntime.HttpContext
41+
public typealias MError = ClientRuntime.SdkError<MockMiddlewareError>
42+
}
1243

1344
struct SayHelloInputQueryItemMiddleware<StackOutput: HttpResponseBinding,
1445
StackError: HttpResponseBinding>: Middleware {
@@ -158,6 +189,12 @@ class HttpRequestTestBaseTests: HttpRequestTestBase {
158189
requiredHeader: "required header")
159190

160191
var operationStack = OperationStack<SayHelloInput, MockOutput, MockMiddlewareError>(id: "SayHelloInputRequest")
192+
operationStack.initializeStep.intercept(position: .before, middleware: SayHelloInputURLHostMiddleware(host: HttpRequestTestBaseTests.host))
193+
operationStack.buildStep.intercept(position: .after, id: "RequestTestEndpointResolver") { (context, input, next) -> Swift.Result<ClientRuntime.OperationOutput<MockOutput>, ClientRuntime.SdkError<MockMiddlewareError>> in
194+
let host = "\(context.getHostPrefix() ?? "")\(context.getHost() ?? "")"
195+
input.withHost(host)
196+
return next.handle(context: context, input: input)
197+
}
161198
operationStack.serializeStep.intercept(position: .before, middleware: SayHelloInputQueryItemMiddleware())
162199
operationStack.serializeStep.intercept(position: .before, middleware: SayHelloInputHeaderMiddleware())
163200
operationStack.serializeStep.intercept(position: .before, middleware: SayHelloInputBodyMiddleware())

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class EndpointTraitConstructor(private val endpointTrait: EndpointTrait, private
1616
// hostLabel can only target string shapes
1717
// see: https://awslabs.github.io/smithy/1.0/spec/core/endpoint-traits.html#hostlabel-trait
1818
val member = inputShape.members().first { it.memberName == segment.content }
19-
"\\(input.${member.memberName.toCamelCase()})"
19+
"\\(input.${member.memberName.toCamelCase()}!)"
2020
} else {
2121
segment.content
2222
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ import software.amazon.smithy.swift.codegen.integration.middlewares.LoggingMiddl
4444
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputBodyMiddleware
4545
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputHeadersMiddleware
4646
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputQueryItemMiddleware
47+
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputUrlHostMiddleware
4748
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputUrlPathMiddleware
4849
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpBodyMiddleware
4950
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpHeaderMiddleware
5051
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpQueryItemMiddleware
52+
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpUrlHostMiddleware
5153
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.HttpUrlPathMiddleware
5254
import software.amazon.smithy.swift.codegen.integration.serde.DynamicNodeDecodingGeneratorStrategy
5355
import software.amazon.smithy.swift.codegen.integration.serde.UnionDecodeGeneratorStrategy
@@ -128,6 +130,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
128130
continue
129131
}
130132
val httpBindingResolver = getProtocolHttpBindingResolver(ctx, defaultContentType)
133+
HttpUrlHostMiddleware.renderMiddleware(ctx, operation, httpBindingResolver)
131134
HttpUrlPathMiddleware.renderUrlPathMiddleware(ctx, operation, httpBindingResolver)
132135
HttpHeaderMiddleware.renderHeaderMiddleware(ctx, operation, httpBindingResolver, defaultTimestampFormat)
133136
HttpQueryItemMiddleware.renderQueryMiddleware(ctx, operation, httpBindingResolver, defaultTimestampFormat)
@@ -383,6 +386,7 @@ abstract class HttpBindingProtocolGenerator : ProtocolGenerator {
383386

384387
operationMiddleware.appendMiddleware(operation, ContentMD5Middleware(ctx.model, ctx.symbolProvider))
385388
operationMiddleware.appendMiddleware(operation, OperationInputUrlPathMiddleware(ctx.model, ctx.symbolProvider, ""))
389+
operationMiddleware.appendMiddleware(operation, OperationInputUrlHostMiddleware(ctx.model, ctx.symbolProvider, ""))
386390
operationMiddleware.appendMiddleware(operation, OperationInputHeadersMiddleware(ctx.model, ctx.symbolProvider))
387391
operationMiddleware.appendMiddleware(operation, OperationInputQueryItemMiddleware(ctx.model, ctx.symbolProvider))
388392
operationMiddleware.appendMiddleware(operation, ContentTypeMiddleware(ctx.model, ctx.symbolProvider, resolver.determineRequestContentType(operation)))

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import software.amazon.smithy.protocoltests.traits.HttpRequestTestsTrait
1515
import software.amazon.smithy.protocoltests.traits.HttpResponseTestsTrait
1616
import software.amazon.smithy.swift.codegen.SwiftDependency
1717
import software.amazon.smithy.swift.codegen.getOrNull
18+
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputUrlHostMiddleware
1819
import software.amazon.smithy.swift.codegen.integration.middlewares.OperationInputUrlPathMiddleware
1920
import software.amazon.smithy.swift.codegen.integration.middlewares.RequestTestEndpointResolverMiddleware
2021
import software.amazon.smithy.swift.codegen.middleware.MiddlewareStep
@@ -68,6 +69,7 @@ class HttpProtocolTestGenerator(
6869

6970
for (operation in requestTestOperations) {
7071
cloned.removeMiddleware(operation, MiddlewareStep.INITIALIZESTEP, "OperationInputUrlPathMiddleware")
72+
cloned.removeMiddleware(operation, MiddlewareStep.INITIALIZESTEP, "OperationInputUrlHostMiddleware")
7173
cloned.removeMiddleware(operation, MiddlewareStep.BUILDSTEP, "EndpointResolverMiddleware")
7274
cloned.removeMiddleware(operation, MiddlewareStep.BUILDSTEP, "UserAgentMiddleware")
7375
cloned.removeMiddleware(operation, MiddlewareStep.FINALIZESTEP, "RetryMiddleware")
@@ -77,6 +79,7 @@ class HttpProtocolTestGenerator(
7779

7880
cloned.appendMiddleware(operation, RequestTestEndpointResolverMiddleware(ctx.model, ctx.symbolProvider))
7981
cloned.appendMiddleware(operation, OperationInputUrlPathMiddleware(ctx.model, ctx.symbolProvider, "urlPrefix: urlPrefix"))
82+
cloned.appendMiddleware(operation, OperationInputUrlHostMiddleware(ctx.model, ctx.symbolProvider, "host: hostOnly"))
8083
}
8184
return cloned
8285
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B
3232
private fun renderExpectedBlock(test: HttpRequestTestCase) {
3333
var resolvedHostValue = test.resolvedHost?.let { it } ?: run { "nil" }
3434
writer.write("let urlPrefix = urlPrefixFromHost(host: \$S)", test.host)
35+
writer.write("let hostOnly = hostOnlyFromHost(host: \$S)", test.host)
3536
writer.openBlock("let expected = buildExpectedHttpRequest(")
3637
.write("method: .${test.method.toLowerCase()},")
3738
.write("path: \$S,", test.uri)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package software.amazon.smithy.swift.codegen.integration.middlewares
2+
3+
import software.amazon.smithy.codegen.core.SymbolProvider
4+
import software.amazon.smithy.model.Model
5+
import software.amazon.smithy.model.shapes.OperationShape
6+
import software.amazon.smithy.swift.codegen.SwiftWriter
7+
import software.amazon.smithy.swift.codegen.integration.middlewares.handlers.MiddlewareShapeUtils
8+
import software.amazon.smithy.swift.codegen.middleware.MiddlewarePosition
9+
import software.amazon.smithy.swift.codegen.middleware.MiddlewareRenderable
10+
import software.amazon.smithy.swift.codegen.middleware.MiddlewareStep
11+
12+
class OperationInputUrlHostMiddleware(
13+
val model: Model,
14+
val symbolProvider: SymbolProvider,
15+
val inputParameters: String
16+
) : MiddlewareRenderable {
17+
18+
override val name = "OperationInputUrlHostMiddleware"
19+
20+
override val middlewareStep = MiddlewareStep.INITIALIZESTEP
21+
22+
override val position = MiddlewarePosition.AFTER
23+
24+
override fun render(
25+
writer: SwiftWriter,
26+
op: OperationShape,
27+
operationStackName: String
28+
) {
29+
val inputShapeName = MiddlewareShapeUtils.inputSymbol(symbolProvider, model, op).name
30+
writer.write("$operationStackName.${middlewareStep.stringValue()}.intercept(position: ${position.stringValue()}, middleware: ${inputShapeName}URLHostMiddleware($inputParameters))")
31+
}
32+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class RequestTestEndpointResolverMiddleware(private val model: Model, private va
2626
ClientRuntimeTypes.Core.SdkError
2727
) {
2828
writer.write("input.withPath(context.getPath())")
29+
writer.write("let host = \"\\(context.getHostPrefix() ?? \"\")\\(context.getHost() ?? \"\")\"")
30+
writer.write("input.withHost(host)")
2931
writer.write("return next.handle(context: context, input: input)")
3032
}
3133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package software.amazon.smithy.swift.codegen.integration.middlewares.handlers
2+
3+
import software.amazon.smithy.codegen.core.Symbol
4+
import software.amazon.smithy.model.shapes.OperationShape
5+
import software.amazon.smithy.model.traits.EndpointTrait
6+
import software.amazon.smithy.swift.codegen.Middleware
7+
import software.amazon.smithy.swift.codegen.MiddlewareGenerator
8+
import software.amazon.smithy.swift.codegen.SwiftDependency
9+
import software.amazon.smithy.swift.codegen.SwiftTypes
10+
import software.amazon.smithy.swift.codegen.SwiftWriter
11+
import software.amazon.smithy.swift.codegen.integration.EndpointTraitConstructor
12+
import software.amazon.smithy.swift.codegen.integration.HttpBindingResolver
13+
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
14+
import software.amazon.smithy.swift.codegen.integration.steps.OperationInitializeStep
15+
16+
class HttpUrlHostMiddleware(
17+
private val ctx: ProtocolGenerator.GenerationContext,
18+
private val op: OperationShape,
19+
inputSymbol: Symbol,
20+
outputSymbol: Symbol,
21+
outputErrorSymbol: Symbol,
22+
private val writer: SwiftWriter
23+
) : Middleware(writer, inputSymbol, OperationInitializeStep(inputSymbol, outputSymbol, outputErrorSymbol)) {
24+
companion object {
25+
fun renderMiddleware(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, httpBindingResolver: HttpBindingResolver) {
26+
val inputSymbol = MiddlewareShapeUtils.inputSymbol(ctx.symbolProvider, ctx.model, op)
27+
val outputSymbol = MiddlewareShapeUtils.outputSymbol(ctx.symbolProvider, ctx.model, op)
28+
val outputErrorSymbol = MiddlewareShapeUtils.outputErrorSymbol(op)
29+
val rootNamespace = MiddlewareShapeUtils.rootNamespace(ctx.settings)
30+
31+
val headerMiddlewareSymbol = Symbol.builder()
32+
.definitionFile("./$rootNamespace/models/${inputSymbol.name}+UrlPathMiddleware.swift")
33+
.name(inputSymbol.name)
34+
.build()
35+
ctx.delegator.useShapeWriter(headerMiddlewareSymbol) { writer ->
36+
writer.addImport(SwiftDependency.CLIENT_RUNTIME.target)
37+
val queryItemMiddleware = HttpUrlHostMiddleware(ctx, op, inputSymbol, outputSymbol, outputErrorSymbol, writer)
38+
MiddlewareGenerator(writer, queryItemMiddleware).generate()
39+
}
40+
}
41+
}
42+
override val typeName = "${inputSymbol.name}URLHostMiddleware"
43+
44+
override fun generateMiddlewareClosure() {
45+
writer.write("var copiedContext = context")
46+
writer.openBlock("if let host = host {", "}") {
47+
writer.write("copiedContext.attributes.set(key: AttributeKey<String>(name: \"Host\"), value: host)")
48+
}
49+
50+
op.getTrait(EndpointTrait::class.java).ifPresent {
51+
val inputShape = ctx.model.expectShape(op.input.get())
52+
val hostPrefix = EndpointTraitConstructor(it, inputShape).construct()
53+
writer.write("copiedContext.attributes.set(key: AttributeKey<String>(name: \"HostPrefix\"), value: \"\$L\")", hostPrefix)
54+
}
55+
}
56+
57+
override fun generateInit() {
58+
writer.write("let host: \$T", SwiftTypes.String)
59+
writer.write("")
60+
writer.openBlock("public init(host: \$D) {", "}", SwiftTypes.String) {
61+
writer.write("self.host = host")
62+
}
63+
}
64+
override fun renderReturn() {
65+
writer.write("return next.handle(context: copiedContext, input: input)")
66+
}
67+
}

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package software.amazon.smithy.swift.codegen.middleware
22

33
import software.amazon.smithy.model.Model
44
import software.amazon.smithy.model.shapes.OperationShape
5-
import software.amazon.smithy.model.traits.EndpointTrait
65
import software.amazon.smithy.swift.codegen.ClientRuntimeTypes
76
import software.amazon.smithy.swift.codegen.ClientRuntimeTypes.Middleware.OperationStack
87
import software.amazon.smithy.swift.codegen.SwiftWriter
9-
import software.amazon.smithy.swift.codegen.integration.EndpointTraitConstructor
108
import software.amazon.smithy.swift.codegen.integration.HttpBindingResolver
119
import software.amazon.smithy.swift.codegen.integration.HttpProtocolCustomizable
1210
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
@@ -52,11 +50,6 @@ class MiddlewareExecutionGenerator(
5250
writer.write(" .withIdempotencyTokenGenerator(value: config.idempotencyTokenGenerator)")
5351
writer.write(" .withLogger(value: config.logger)")
5452

55-
op.getTrait(EndpointTrait::class.java).ifPresent {
56-
val inputShape = model.expectShape(op.input.get())
57-
val hostPrefix = EndpointTraitConstructor(it, inputShape).construct()
58-
writer.write(" .withHostPrefix(value: \"\$L\")", hostPrefix)
59-
}
6053
val serviceShape = ctx.service
6154
httpProtocolCustomizable.renderContextAttributes(ctx, writer, serviceShape, op)
6255
}

0 commit comments

Comments
 (0)