diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift index 1e41e22894..7da01cca44 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift @@ -16,12 +16,17 @@ struct AWSAPIEndpointInterceptors { let apiAuthProviderFactory: APIAuthProviderFactory let authService: AWSAuthServiceBehavior? - var amplifyInterceptors: [URLRequestInterceptor] = [] - - var checksumInterceptors: [URLRequestInterceptor] = [] + /// The order of interceptor decoration is as follows: + /// 1. **prelude interceptors**: append default auth info + /// 2. **cutomize headers**: headers provided when constructing the request + /// 3. **customer interceptors**: customer defined interceptors + /// 4. **postlude interceptors**: calculate checksums, signatures + var preludeInterceptors: [URLRequestInterceptor] = [] var interceptors: [URLRequestInterceptor] = [] + var postludeInterceptors: [URLRequestInterceptor] = [] + init(endpointName: APIEndpointName, apiAuthProviderFactory: APIAuthProviderFactory, authService: AWSAuthServiceBehavior? = nil) { @@ -46,7 +51,7 @@ struct AWSAPIEndpointInterceptors { case .apiKey(let apiKeyConfig): let provider = BasicAPIKeyProvider(apiKey: apiKeyConfig.apiKey) let interceptor = APIKeyURLRequestInterceptor(apiKeyProvider: provider) - amplifyInterceptors.append(interceptor) + preludeInterceptors.append(interceptor) case .awsIAM(let iamConfig): guard let authService = authService else { throw PluginError.pluginConfigurationError("AuthService is not set for IAM", @@ -56,7 +61,7 @@ struct AWSAPIEndpointInterceptors { let interceptor = IAMURLRequestInterceptor(iamCredentialsProvider: provider, region: iamConfig.region, endpointType: endpointType) - checksumInterceptors.append(interceptor) + postludeInterceptors.append(interceptor) case .amazonCognitoUserPools: guard let authService = authService else { throw PluginError.pluginConfigurationError("AuthService not set for cognito user pools", @@ -64,7 +69,7 @@ struct AWSAPIEndpointInterceptors { } let provider = BasicUserPoolTokenProvider(authService: authService) let interceptor = AuthTokenURLRequestInterceptor(authTokenProvider: provider) - amplifyInterceptors.append(interceptor) + preludeInterceptors.append(interceptor) case .openIDConnect: guard let oidcAuthProvider = apiAuthProviderFactory.oidcAuthProvider() else { throw PluginError.pluginConfigurationError("AuthService not set for OIDC", @@ -72,7 +77,7 @@ struct AWSAPIEndpointInterceptors { } let wrappedAuthProvider = AuthTokenProviderWrapper(tokenAuthProvider: oidcAuthProvider) let interceptor = AuthTokenURLRequestInterceptor(authTokenProvider: wrappedAuthProvider) - amplifyInterceptors.append(interceptor) + preludeInterceptors.append(interceptor) case .function: guard let functionAuthProvider = apiAuthProviderFactory.functionAuthProvider() else { throw PluginError.pluginConfigurationError("AuthService not set for function auth", @@ -80,7 +85,7 @@ struct AWSAPIEndpointInterceptors { } let wrappedAuthProvider = AuthTokenProviderWrapper(tokenAuthProvider: functionAuthProvider) let interceptor = AuthTokenURLRequestInterceptor(authTokenProvider: wrappedAuthProvider) - amplifyInterceptors.append(interceptor) + preludeInterceptors.append(interceptor) } } } diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLOperation.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLOperation.swift index 51386e8c0e..faa35764d9 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLOperation.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLOperation.swift @@ -34,6 +34,10 @@ final public class AWSGraphQLOperation: GraphQLOperation { } override public func main() { + Task { await mainAsync() } + } + + private func mainAsync() async { Amplify.API.log.debug("Starting \(request.operationType) \(id)") if isCancelled { @@ -41,56 +45,54 @@ final public class AWSGraphQLOperation: GraphQLOperation { return } - Task { - let urlRequest = validateRequest(request).flatMap(buildURLRequest(from:)) - let finalRequest = await getEndpointInterceptors(from: request).flatMapAsync { requestInterceptors in - var finalResult = urlRequest - let amplifyInterceptors = requestInterceptors?.amplifyInterceptors ?? [] - let customerInterceptors = requestInterceptors?.interceptors ?? [] - let checksumInterceptors = requestInterceptors?.checksumInterceptors ?? [] - // apply amplify interceptors - for interceptor in amplifyInterceptors { - finalResult = await finalResult.flatMapAsync { request in - await applyInterceptor(interceptor, request: request) - } + let urlRequest = validateRequest(request).flatMap(buildURLRequest(from:)) + let finalRequest = await getEndpointInterceptors(from: request).flatMapAsync { requestInterceptors in + let preludeInterceptors = requestInterceptors?.preludeInterceptors ?? [] + let customerInterceptors = requestInterceptors?.interceptors ?? [] + let postludeInterceptors = requestInterceptors?.postludeInterceptors ?? [] + + var finalResult = urlRequest + // apply prelude interceptors + for interceptor in preludeInterceptors { + finalResult = await finalResult.flatMapAsync { request in + await applyInterceptor(interceptor, request: request) } + } - // there is no customer headers for GraphQLOperationRequest - - // apply customer interceptors - for interceptor in customerInterceptors { - finalResult = await finalResult.flatMapAsync { request in - await applyInterceptor(interceptor, request: request) - } - } + // there is no customize headers for GraphQLOperationRequest - // apply checksum interceptor - for interceptor in checksumInterceptors { - finalResult = await finalResult.flatMapAsync { request in - await applyInterceptor(interceptor, request: request) - } + // apply customer interceptors + for interceptor in customerInterceptors { + finalResult = await finalResult.flatMapAsync { request in + await applyInterceptor(interceptor, request: request) } - return finalResult } - switch finalRequest { - case .success(let finalRequest): - if isCancelled { - finish() - return + // apply postlude interceptor + for interceptor in postludeInterceptors { + finalResult = await finalResult.flatMapAsync { request in + await applyInterceptor(interceptor, request: request) } + } + return finalResult + } - // Begin network task - Amplify.API.log.debug("Starting network task for \(request.operationType) \(id)") - let task = session.dataTaskBehavior(with: finalRequest) - mapper.addPair(operation: self, task: task) - task.resume() - case .failure(let error): - dispatch(result: .failure(error)) + switch finalRequest { + case .success(let finalRequest): + if isCancelled { finish() + return } - } + // Begin network task + Amplify.API.log.debug("Starting network task for \(request.operationType) \(id)") + let task = session.dataTaskBehavior(with: finalRequest) + mapper.addPair(operation: self, task: task) + task.resume() + case .failure(let error): + dispatch(result: .failure(error)) + finish() + } } private func validateRequest(_ request: GraphQLOperationRequest) -> Result, APIError> { diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSRESTOperation.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSRESTOperation.swift index 38fd7160bd..18c2979097 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSRESTOperation.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSRESTOperation.swift @@ -40,69 +40,71 @@ final public class AWSRESTOperation: AmplifyOperation< /// The work to execute for this operation override public func main() { + Task { await mainAsync() } + } + + private func mainAsync() async { if isCancelled { finish() return } - Task { - let urlRequest = validateRequest(request).flatMap(buildURLRequest(from:)) - let finalRequest = await getEndpointConfig(from: request).flatMapAsync { endpointConfig in - let interceptorConfig = pluginConfig.interceptorsForEndpoint(withConfig: endpointConfig) - let amplifyInterceptors = interceptorConfig?.amplifyInterceptors ?? [] - let customerInterceptors = interceptorConfig?.interceptors ?? [] - let checksumInterceptors = interceptorConfig?.checksumInterceptors ?? [] - - var finalResult = urlRequest - // apply amplify interceptors - for interceptor in amplifyInterceptors { - finalResult = await finalResult.flatMapAsync { request in - await applyInterceptor(interceptor, request: request) - } - } - - // apply customer headers - finalResult = finalResult.map { urlRequest in - var mutableRequest = urlRequest - for (key, value) in request.headers ?? [:] { - mutableRequest.setValue(value, forHTTPHeaderField: key) - } - return mutableRequest + let urlRequest = validateRequest(request).flatMap(buildURLRequest(from:)) + let finalRequest = await getEndpointConfig(from: request).flatMapAsync { endpointConfig in + let interceptorConfig = pluginConfig.interceptorsForEndpoint(withConfig: endpointConfig) + let preludeInterceptors = interceptorConfig?.preludeInterceptors ?? [] + let customerInterceptors = interceptorConfig?.interceptors ?? [] + let postludeInterceptors = interceptorConfig?.postludeInterceptors ?? [] + + var finalResult = urlRequest + // apply prelude interceptors + for interceptor in preludeInterceptors { + finalResult = await finalResult.flatMapAsync { request in + await applyInterceptor(interceptor, request: request) } + } - // apply customer interceptors - for interceptor in customerInterceptors { - finalResult = await finalResult.flatMapAsync { request in - await applyInterceptor(interceptor, request: request) - } + // apply customize headers + finalResult = finalResult.map { urlRequest in + var mutableRequest = urlRequest + for (key, value) in request.headers ?? [:] { + mutableRequest.setValue(value, forHTTPHeaderField: key) } + return mutableRequest + } - // apply checksum interceptor - for interceptor in checksumInterceptors { - finalResult = await finalResult.flatMapAsync { request in - await applyInterceptor(interceptor, request: request) - } + // apply customer interceptors + for interceptor in customerInterceptors { + finalResult = await finalResult.flatMapAsync { request in + await applyInterceptor(interceptor, request: request) } - return finalResult } - switch finalRequest { - case .success(let finalRequest): - if isCancelled { - finish() - return + // apply postlude interceptor + for interceptor in postludeInterceptors { + finalResult = await finalResult.flatMapAsync { request in + await applyInterceptor(interceptor, request: request) } + } + return finalResult + } - // Begin network task - Amplify.API.log.debug("Starting network task for \(request.operationType) \(id)") - let task = session.dataTaskBehavior(with: finalRequest) - mapper.addPair(operation: self, task: task) - task.resume() - case .failure(let error): - Amplify.API.log.debug("Dispatching error \(error)") - dispatch(result: .failure(error)) + switch finalRequest { + case .success(let finalRequest): + if isCancelled { finish() + return } + + // Begin network task + Amplify.API.log.debug("Starting network task for \(request.operationType) \(id)") + let task = session.dataTaskBehavior(with: finalRequest) + mapper.addPair(operation: self, task: task) + task.resume() + case .failure(let error): + Amplify.API.log.debug("Dispatching error \(error)") + dispatch(result: .failure(error)) + finish() } } diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift index 943f1911d9..cb4c3ac7a5 100644 --- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift +++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/GraphQLConnectionScenario4Tests.swift @@ -213,7 +213,7 @@ class GraphQLConnectionScenario4Tests: XCTestCase { } let predicate = field("postID").eq(post.id) var results: List? - let result = try await Amplify.API.query(request: .list(Comment4.self, where: predicate, limit: 1)) + let result = try await Amplify.API.query(request: .list(Comment4.self, where: predicate, limit: 3000)) switch result { case .success(let comments): results = comments diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift index c746d36606..2974af9f4d 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift @@ -14,16 +14,16 @@ class AWSAPICategoryPluginInterceptorBehaviorTests: AWSAPICategoryPluginTestBase func testAddInterceptor() throws { XCTAssertNotNil(apiPlugin.pluginConfig.endpoints[apiName]) - XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.amplifyInterceptors.count, 0) + XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.preludeInterceptors.count, 0) XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.interceptors.count, 0) - XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.checksumInterceptors.count, 0) + XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.postludeInterceptors.count, 0) let provider = BasicUserPoolTokenProvider(authService: authService) let requestInterceptor = AuthTokenURLRequestInterceptor(authTokenProvider: provider) try apiPlugin.add(interceptor: requestInterceptor, for: apiName) - XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.amplifyInterceptors.count, 0) + XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.preludeInterceptors.count, 0) XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.interceptors.count, 1) - XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.checksumInterceptors.count, 0) + XCTAssertEqual(apiPlugin.pluginConfig.interceptorsForEndpoint(named: apiName)?.postludeInterceptors.count, 0) } } diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift index b50c929011..a2f7503f01 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift @@ -58,9 +58,9 @@ class AWSAPICategoryPluginConfigurationTests: XCTestCase { func testAddInterceptors() { let apiKeyInterceptor = APIKeyURLRequestInterceptor(apiKeyProvider: BasicAPIKeyProvider(apiKey: apiKey)) config?.addInterceptor(apiKeyInterceptor, toEndpoint: graphQLAPI) - XCTAssertEqual(config?.interceptorsForEndpoint(named: graphQLAPI)?.amplifyInterceptors.count, 0) + XCTAssertEqual(config?.interceptorsForEndpoint(named: graphQLAPI)?.preludeInterceptors.count, 0) XCTAssertEqual(config?.interceptorsForEndpoint(named: graphQLAPI)?.interceptors.count, 1) - XCTAssertEqual(config?.interceptorsForEndpoint(named: graphQLAPI)?.checksumInterceptors.count, 0) + XCTAssertEqual(config?.interceptorsForEndpoint(named: graphQLAPI)?.postludeInterceptors.count, 0) } /// Given: multiple interceptors conforming to URLRequestInterceptor and an EndpointConfig @@ -71,9 +71,9 @@ class AWSAPICategoryPluginConfigurationTests: XCTestCase { config?.addInterceptor(apiKeyInterceptor, toEndpoint: graphQLAPI) config?.addInterceptor(CustomURLInterceptor(), toEndpoint: graphQLAPI) let interceptors = config?.interceptorsForEndpoint(withConfig: endpointConfig!) - XCTAssertEqual(interceptors!.amplifyInterceptors.count, 0) + XCTAssertEqual(interceptors!.preludeInterceptors.count, 0) XCTAssertEqual(interceptors!.interceptors.count, 2) - XCTAssertEqual(interceptors!.checksumInterceptors.count, 0) + XCTAssertEqual(interceptors!.postludeInterceptors.count, 0) } /// Given: multiple interceptors conforming to URLRequestInterceptor @@ -88,8 +88,8 @@ class AWSAPICategoryPluginConfigurationTests: XCTestCase { let interceptors = try config?.interceptorsForEndpoint(withConfig: endpointConfig!, authType: .amazonCognitoUserPools) - XCTAssertEqual(interceptors!.amplifyInterceptors.count, 1) - XCTAssertNotNil(interceptors!.amplifyInterceptors[0] as? AuthTokenURLRequestInterceptor) + XCTAssertEqual(interceptors!.preludeInterceptors.count, 1) + XCTAssertNotNil(interceptors!.preludeInterceptors[0] as? AuthTokenURLRequestInterceptor) XCTAssertNotNil(interceptors!.interceptors[1] as? CustomURLInterceptor) } @@ -103,8 +103,8 @@ class AWSAPICategoryPluginConfigurationTests: XCTestCase { let interceptors = try config?.interceptorsForEndpoint(withConfig: endpointConfig!, authType: .apiKey) - XCTAssertEqual(interceptors!.amplifyInterceptors.count, 1) - XCTAssertNotNil(interceptors!.amplifyInterceptors[0] as? APIKeyURLRequestInterceptor) + XCTAssertEqual(interceptors!.preludeInterceptors.count, 1) + XCTAssertNotNil(interceptors!.preludeInterceptors[0] as? APIKeyURLRequestInterceptor) } // MARK: - Helpers diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift index d9a435b236..64443e9ffc 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift @@ -31,9 +31,9 @@ class AWSAPIEndpointInterceptorsTests: XCTestCase { try interceptorConfig.addAuthInterceptorsToEndpoint(endpointType: .graphQL, authConfiguration: config!) - XCTAssertEqual(interceptorConfig.amplifyInterceptors.count, 1) + XCTAssertEqual(interceptorConfig.preludeInterceptors.count, 1) XCTAssertEqual(interceptorConfig.interceptors.count, 0) - XCTAssertEqual(interceptorConfig.checksumInterceptors.count, 0) + XCTAssertEqual(interceptorConfig.postludeInterceptors.count, 0) } /// Given: an AWSAPIEndpointInterceptors @@ -45,9 +45,9 @@ class AWSAPIEndpointInterceptorsTests: XCTestCase { try interceptorConfig.addAuthInterceptorsToEndpoint(endpointType: .graphQL, authConfiguration: config!) interceptorConfig.addInterceptor(CustomInterceptor()) - XCTAssertEqual(interceptorConfig.amplifyInterceptors.count, 1) + XCTAssertEqual(interceptorConfig.preludeInterceptors.count, 1) XCTAssertEqual(interceptorConfig.interceptors.count, 1) - XCTAssertEqual(interceptorConfig.checksumInterceptors.count, 0) + XCTAssertEqual(interceptorConfig.postludeInterceptors.count, 0) } func testaddMultipleAuthInterceptors() throws { @@ -72,12 +72,12 @@ class AWSAPIEndpointInterceptorsTests: XCTestCase { try interceptorConfig.addAuthInterceptorsToEndpoint(endpointType: .graphQL, authConfiguration: userPoolConfig) - XCTAssertEqual(interceptorConfig.amplifyInterceptors.count, 2) + XCTAssertEqual(interceptorConfig.preludeInterceptors.count, 2) XCTAssertEqual(interceptorConfig.interceptors.count, 0) - XCTAssertEqual(interceptorConfig.checksumInterceptors.count, 1) - XCTAssertNotNil(interceptorConfig.amplifyInterceptors[0] as? APIKeyURLRequestInterceptor) - XCTAssertNotNil(interceptorConfig.amplifyInterceptors[1] as? AuthTokenURLRequestInterceptor) - XCTAssertNotNil(interceptorConfig.checksumInterceptors[0] as? IAMURLRequestInterceptor) + XCTAssertEqual(interceptorConfig.postludeInterceptors.count, 1) + XCTAssertNotNil(interceptorConfig.preludeInterceptors[0] as? APIKeyURLRequestInterceptor) + XCTAssertNotNil(interceptorConfig.preludeInterceptors[1] as? AuthTokenURLRequestInterceptor) + XCTAssertNotNil(interceptorConfig.postludeInterceptors[0] as? IAMURLRequestInterceptor) } // MARK: - Test Helpers