Skip to content

Commit c654e3f

Browse files
authored
chore: Adding mechanism to provide plugin extensions (#2852)
1 parent f47d335 commit c654e3f

File tree

7 files changed

+204
-0
lines changed

7 files changed

+204
-0
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ extension AWSCognitoAuthPlugin {
2929
AuthPluginErrorConstants.decodeConfigurationError.recoverySuggestion)
3030
}
3131

32+
jsonConfiguration = jsonValueConfiguration
33+
3234
let authConfiguration = try ConfigurationHelper.authConfiguration(jsonValueConfiguration)
3335

3436
let credentialStoreResolver = CredentialStoreState.Resolver().eraseToAnyResolver()
@@ -89,6 +91,13 @@ extension AWSCognitoAuthPlugin {
8991
frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(),
9092
region: userPoolConfig.region
9193
)
94+
95+
if var httpClientEngineProxy = httpClientEngineProxy {
96+
let sdkEngine = configuration.httpClientEngine
97+
httpClientEngineProxy.target = sdkEngine
98+
configuration.httpClientEngine = httpClientEngineProxy
99+
}
100+
92101
return CognitoIdentityProviderClient(config: configuration)
93102
default:
94103
fatalError()
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+
@_spi(InternalAmplifyPluginExtension) import AWSPluginsCore
9+
import Foundation
10+
import ClientRuntime
11+
12+
extension AWSCognitoAuthPlugin {
13+
@_spi(InternalAmplifyPluginExtension)
14+
public func add(pluginExtension: AWSPluginExtension) {
15+
if let customHttpEngine = pluginExtension as? HttpClientEngineProxy {
16+
self.httpClientEngineProxy = customHttpEngine
17+
}
18+
}
19+
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift

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

88
import Foundation
99
import Amplify
10+
import AWSPluginsCore
1011

1112
public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior {
1213

@@ -29,6 +30,11 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior {
2930

3031
var taskQueue: TaskQueue<Any>!
3132

33+
var httpClientEngineProxy: HttpClientEngineProxy?
34+
35+
@_spi(InternalAmplifyConfiguration)
36+
internal(set) public var jsonConfiguration: JSONValue?
37+
3238
/// The unique key of the plugin within the auth category.
3339
public var key: PluginKey {
3440
return "awsCognitoAuthPlugin"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
@_spi(InternalHttpEngineProxy) @_spi(InternalAmplifyPluginExtension) import AWSPluginsCore
9+
import ClientRuntime
10+
import Foundation
11+
12+
protocol HttpClientEngineProxy: HttpClientEngine {
13+
var target: HttpClientEngine? { set get }
14+
}
15+
16+
extension UserAgentSuffixAppender: HttpClientEngineProxy {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 Foundation
9+
10+
@_spi(InternalAmplifyPluginExtension)
11+
public protocol AWSPluginExtension {}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 ClientRuntime
9+
10+
@_spi(InternalAmplifyPluginExtension)
11+
public class UserAgentSuffixAppender: AWSPluginExtension {
12+
@_spi(InternalHttpEngineProxy)
13+
public var target: HttpClientEngine? = nil
14+
public let suffix: String
15+
private let userAgentHeader = "User-Agent"
16+
17+
public init(suffix: String) {
18+
self.suffix = suffix
19+
}
20+
}
21+
22+
@_spi(InternalHttpEngineProxy)
23+
extension UserAgentSuffixAppender: HttpClientEngine {
24+
public func execute(request: SdkHttpRequest) async throws -> HttpResponse {
25+
guard let target = target else {
26+
throw ClientError.unknownError("HttpClientEngine is not set")
27+
}
28+
var headers = request.headers
29+
let currentUserAgent = headers.value(for: userAgentHeader) ?? ""
30+
headers.update(
31+
name: userAgentHeader,
32+
value: "\(currentUserAgent) \(suffix)"
33+
)
34+
request.headers = headers
35+
return try await target.execute(request: request)
36+
}
37+
38+
public func close() async {
39+
await target?.close()
40+
}
41+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
@_spi(InternalAmplifyPluginExtension) @_spi(InternalHttpEngineProxy) import AWSPluginsCore
9+
import ClientRuntime
10+
import XCTest
11+
12+
class UserAgentSuffixAppenderTests: XCTestCase {
13+
private let userAgentKey = "User-Agent"
14+
private let customSuffix = "myCustomSuffix"
15+
private var appender: UserAgentSuffixAppender!
16+
private var httpClientEngine: MockHttpClientEngine!
17+
18+
override func setUp() {
19+
appender = UserAgentSuffixAppender(suffix: customSuffix)
20+
httpClientEngine = MockHttpClientEngine()
21+
appender.target = httpClientEngine
22+
}
23+
24+
override func tearDown() {
25+
appender = nil
26+
httpClientEngine = nil
27+
}
28+
29+
/// Given: A UserAgentSuffixAppender with a configured httpClientEngine
30+
/// When: A request is invoked with an existing User-Agent
31+
/// Then: The user agent suffix is appended
32+
func testExecute_withExistingUserAgentHeader_shouldAppendSuffix() async throws {
33+
let request = createRequest()
34+
request.headers.add(name: userAgentKey, value: "existingUserAgent")
35+
36+
_ = try await appender.execute(request: request)
37+
XCTAssertEqual(httpClientEngine.executeCount, 1)
38+
XCTAssertNotNil(httpClientEngine.executeRequest)
39+
let userAgent = try XCTUnwrap(request.headers.value(for: userAgentKey))
40+
XCTAssertTrue(userAgent.hasSuffix(customSuffix))
41+
}
42+
43+
/// Given: A UserAgentSuffixAppender with a configured httpClientEngine
44+
/// When: A request is invoked with no User-Agent
45+
/// Then: The user agent is created containing the suffix
46+
func testExecute_withoutExistingUserAgentHeader_shouldCreateHeader() async throws {
47+
let request = createRequest()
48+
49+
_ = try await appender.execute(request: request)
50+
XCTAssertEqual(httpClientEngine.executeCount, 1)
51+
XCTAssertNotNil(httpClientEngine.executeRequest)
52+
let userAgent = try XCTUnwrap(request.headers.value(for: userAgentKey))
53+
XCTAssertTrue(userAgent.hasSuffix(customSuffix))
54+
}
55+
56+
/// Given: A UserAgentSuffixAppender with no httpClientEngine configured
57+
/// When: A request is invoked
58+
/// Then: An error is thrown
59+
func testExecute_withoutHttpClientEngine_shouldThrowError() async {
60+
appender = UserAgentSuffixAppender(suffix: customSuffix)
61+
do {
62+
_ = try await appender.execute(request: createRequest())
63+
XCTFail("Should not succeed")
64+
} catch {
65+
guard case ClientError.unknownError(_) = error else {
66+
XCTFail("Expected .unknownError, got \(error)")
67+
return
68+
}
69+
}
70+
}
71+
72+
/// Given: A UserAgentSuffixAppender with a configured httpClientEngine
73+
/// When: close is invoked
74+
/// Then: The httpClientEngine's close API should be called
75+
func testClose_shouldInvokeClose() async {
76+
await appender.close()
77+
XCTAssertEqual(httpClientEngine.closeCount, 1)
78+
}
79+
80+
private func createRequest() -> SdkHttpRequest {
81+
return SdkHttpRequest(
82+
method: .get,
83+
endpoint: .init(host: "customHost"),
84+
headers: .init()
85+
)
86+
}
87+
}
88+
89+
private class MockHttpClientEngine: HttpClientEngine {
90+
var executeCount = 0
91+
var executeRequest: SdkHttpRequest?
92+
func execute(request: SdkHttpRequest) async throws -> HttpResponse {
93+
executeCount += 1
94+
executeRequest = request
95+
return .init(body: .empty, statusCode: .accepted)
96+
}
97+
98+
var closeCount = 0
99+
func close() async {
100+
closeCount += 1
101+
}
102+
}

0 commit comments

Comments
 (0)