Skip to content

Commit

Permalink
feat(api): Add AWSAPIPluginConfiguration for explicit config
Browse files Browse the repository at this point in the history
  • Loading branch information
lawmicha committed Jan 18, 2024
1 parent 3d142c1 commit 8beec7b
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@ public extension AWSAPIPlugin {
/// - Throws:
/// - PluginError.pluginConfigurationError: If one of the required configuration values is invalid or empty
func configure(using configuration: Any?) throws {

guard let jsonValue = configuration as? JSONValue else {
throw PluginError.pluginConfigurationError(
"Could not cast incoming configuration to JSONValue",
"""
The specified configuration is either nil, or not convertible to a JSONValue. Review the configuration \
and ensure it contains the expected values, and does not use any types that aren't convertible to a \
corresponding JSONValue:
\(String(describing: configuration))
"""
)
}

let dependencies = try ConfigurationDependencies(configurationValues: jsonValue,

let dependencies: ConfigurationDependencies
if let configuration = self.configuration {
dependencies = try ConfigurationDependencies(configuration: configuration,
apiAuthProviderFactory: authProviderFactory)
} else {
guard let jsonValue = configuration as? JSONValue else {
throw PluginError.pluginConfigurationError(
"Could not cast incoming configuration to JSONValue",
"""
The specified configuration is either nil, or not convertible to a JSONValue. Review the configuration \
and ensure it contains the expected values, and does not use any types that aren't convertible to a \
corresponding JSONValue:
\(String(describing: configuration))
"""
)
}
dependencies = try ConfigurationDependencies(configurationValues: jsonValue,
apiAuthProviderFactory: authProviderFactory)

}
configure(using: dependencies)

// Initialize SwiftSDK's CRT dependency for SigV4 signing functionality
Expand All @@ -56,6 +62,33 @@ extension AWSAPIPlugin {
let subscriptionConnectionFactory: SubscriptionConnectionFactory
let logLevel: Amplify.LogLevel

init(configuration: AWSAPIPluginConfiguration,
apiAuthProviderFactory: APIAuthProviderFactory,
authService: AWSAuthServiceBehavior? = nil,
subscriptionConnectionFactory: SubscriptionConnectionFactory? = nil,
logLevel: Amplify.LogLevel? = nil) throws {
let authService = authService
?? AWSAuthService()

let pluginConfig = try AWSAPICategoryPluginConfiguration(
configuration: configuration,
apiAuthProviderFactory: apiAuthProviderFactory,
authService: authService
)

let subscriptionConnectionFactory = subscriptionConnectionFactory
?? AWSSubscriptionConnectionFactory()

let logLevel = logLevel ?? Amplify.Logging.logLevel

self.init(
pluginConfig: pluginConfig,
authService: authService,
subscriptionConnectionFactory: subscriptionConnectionFactory,
logLevel: logLevel
)
}

init(
configurationValues: JSONValue,
apiAuthProviderFactory: APIAuthProviderFactory,
Expand Down
6 changes: 6 additions & 0 deletions AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ final public class AWSAPIPlugin: NSObject, APICategoryPlugin, APICategoryGraphQL
return "awsAPIPlugin"
}

/// API configuration, takes precedent over configuration in `pluginConfig`
/// Passed in to the initializer of AWSAPIPlugin.
var configuration: AWSAPIPluginConfiguration?

/// A holder for API configurations. This will be populated during the
/// configuration phase, and is clearable by `reset()`.
var pluginConfig: AWSAPICategoryPluginConfiguration!
Expand Down Expand Up @@ -46,10 +50,12 @@ final public class AWSAPIPlugin: NSObject, APICategoryPlugin, APICategoryGraphQL
let reachabilityMapLock: NSLock

public init(
configuration: AWSAPIPluginConfiguration? = nil,
modelRegistration: AmplifyModelRegistration? = nil,
sessionFactory: URLSessionBehaviorFactory? = nil,
apiAuthProviderFactory: APIAuthProviderFactory? = nil
) {
self.configuration = configuration
self.mapper = OperationTaskMapper()
self.queue = OperationQueue()
self.authProviderFactory = apiAuthProviderFactory ?? APIAuthProviderFactory()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,36 @@ public extension AWSAPICategoryPluginConfiguration {
// default authorization configuration
let authorizationConfiguration: AWSAuthorizationConfiguration

let endpointType: AWSAPICategoryPluginEndpointType
let endpointType: AWSAPIPluginEndpointType

var apiKey: String?

public init(configuration: AWSAPIPluginConfiguration.API,
apiAuthProviderFactory: APIAuthProviderFactory,
authService: AWSAuthServiceBehavior? = nil) throws {

guard let baseURL = URL(string: configuration.endpoint) else {
throw PluginError.pluginConfigurationError(
"Could not convert `\(configuration.endpoint)` to a URL",
"""
The "endpoint" value in the specified configuration cannot be converted to a URL. Review the \
configuration and ensure it contains the expected values:
\(configuration.endpoint)
"""
)
}

try self.init(name: configuration.apiName,
baseURL: baseURL,
region: configuration.region,
authorizationType: configuration.authorizationType,
endpointType: configuration.endpointType,
apiKey: configuration.apiKey,
apiAuthProviderFactory: apiAuthProviderFactory,
authService: authService)

}

public init(name: String,
jsonValue: JSONValue,
apiAuthProviderFactory: APIAuthProviderFactory,
Expand Down Expand Up @@ -63,7 +89,7 @@ public extension AWSAPICategoryPluginConfiguration {
baseURL: URL,
region: AWSRegionType?,
authorizationType: AWSAuthorizationType,
endpointType: AWSAPICategoryPluginEndpointType,
endpointType: AWSAPIPluginEndpointType,
apiKey: String? = nil,
apiAuthProviderFactory: APIAuthProviderFactory,
authService: AWSAuthServiceBehavior? = nil) throws {
Expand Down Expand Up @@ -113,7 +139,7 @@ public extension AWSAPICategoryPluginConfiguration {
}

private static func getEndpointType(from endpointJSON: [String: JSONValue]) throws ->
AWSAPICategoryPluginEndpointType {
AWSAPIPluginEndpointType {

guard case .string(let endpointTypeValue) = endpointJSON["endpointType"] else {
throw PluginError.pluginConfigurationError(
Expand All @@ -126,7 +152,7 @@ public extension AWSAPICategoryPluginConfiguration {
)
}

let endpointTypeOptional = AWSAPICategoryPluginEndpointType(rawValue: endpointTypeValue)
let endpointTypeOptional = AWSAPIPluginEndpointType(rawValue: endpointTypeValue)

guard let endpointType = endpointTypeOptional else {
throw PluginError.pluginConfigurationError(
Expand Down Expand Up @@ -201,7 +227,7 @@ extension Dictionary where Key == String, Value == AWSAPICategoryPluginConfigura
/// 3. If nothing is specified, return the endpoint only if there is a single one, with GraphQL taking precedent
/// over REST.
func getConfig(for apiName: String? = nil,
endpointType: AWSAPICategoryPluginEndpointType? = nil) throws ->
endpointType: AWSAPIPluginEndpointType? = nil) throws ->
AWSAPICategoryPluginConfiguration.EndpointConfig {
if let apiName = apiName {
return try getConfig(for: apiName)
Expand Down Expand Up @@ -231,7 +257,7 @@ extension Dictionary where Key == String, Value == AWSAPICategoryPluginConfigura
}

/// Retrieve the endpoint configuration when there is only one endpoint of the specified `endpointType`
private func getConfig(for endpointType: AWSAPICategoryPluginEndpointType) throws ->
private func getConfig(for endpointType: AWSAPIPluginEndpointType) throws ->
AWSAPICategoryPluginConfiguration.EndpointConfig {
let apiForEndpointType = filter { (_, endpointConfig) -> Bool in
return endpointConfig.endpointType == endpointType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@ public struct AWSAPICategoryPluginConfiguration {
private var apiAuthProviderFactory: APIAuthProviderFactory?
private var authService: AWSAuthServiceBehavior?

init(configuration: AWSAPIPluginConfiguration,
apiAuthProviderFactory: APIAuthProviderFactory,
authService: AWSAuthServiceBehavior) throws {

var endpoints: [APIEndpointName: EndpointConfig] = [:]

try configuration.apis.forEach { config in
let endpointConfig = try EndpointConfig(configuration: config,
apiAuthProviderFactory: apiAuthProviderFactory,
authService: authService)
endpoints.updateValue(endpointConfig, forKey: config.apiName)
}

let interceptors = try AWSAPICategoryPluginConfiguration.makeInterceptors(forEndpoints: endpoints,
apiAuthProviderFactory: apiAuthProviderFactory,
authService: authService)

self.init(endpoints: endpoints,
interceptors: interceptors,
apiAuthProviderFactory: apiAuthProviderFactory,
authService: authService)
}

init(jsonValue: JSONValue,
apiAuthProviderFactory: APIAuthProviderFactory,
authService: AWSAuthServiceBehavior) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

enum AWSAPICategoryPluginEndpointType: String {
public enum AWSAPIPluginEndpointType: String, Codable {
case rest = "REST"
case graphQL = "GraphQL"
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct AWSAPIEndpointInterceptors {
}

/// Initialize authorization interceptors
mutating func addAuthInterceptorsToEndpoint(endpointType: AWSAPICategoryPluginEndpointType,
mutating func addAuthInterceptorsToEndpoint(endpointType: AWSAPIPluginEndpointType,
authConfiguration: AWSAuthorizationConfiguration) throws {
switch authConfiguration {
case .none:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation
import AWSPluginsCore

public struct AWSAPIPluginConfiguration: Codable {

public struct API: Codable {
var apiName: String
var endpoint: String
var endpointType: AWSAPIPluginEndpointType
var region: String
var authorizationType: AWSAuthorizationType
var apiKey: String?
}

public let apis: [API]

public init(apiName: String = UUID().uuidString,
endpoint: String,
endpointType: AWSAPIPluginEndpointType,
region: String,
authorizationType: AWSAuthorizationType,
apiKey: String? = nil) {
self.apis = [.init(apiName: apiName,
endpoint: endpoint,
endpointType: endpointType,
region: region,
authorizationType: authorizationType)]
}

public init(_ apis: API...) {
self.apis = apis
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ typealias AWSRegionType = String
struct IAMURLRequestInterceptor: URLRequestInterceptor {
let iamCredentialsProvider: IAMCredentialsProvider
let region: AWSRegionType
let endpointType: AWSAPICategoryPluginEndpointType
let endpointType: AWSAPIPluginEndpointType
private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib

init(iamCredentialsProvider: IAMCredentialsProvider,
region: AWSRegionType,
endpointType: AWSAPICategoryPluginEndpointType) {
endpointType: AWSAPIPluginEndpointType) {
self.iamCredentialsProvider = iamCredentialsProvider
self.region = region
self.endpointType = endpointType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ class AWSAPICategoryPluginConfigureTests: AWSAPICategoryPluginTestBase {
XCTAssertEqual(apiPlugin.key, "awsAPIPlugin")
}

func testExplicitConfiguration() throws {
let configuration = AWSAPIPluginConfiguration(
.init(apiName: "Test",
endpoint: "http://www.example.com",
endpointType: .rest,
region: "us-east-1",
authorizationType: .apiKey,
apiKey: "key"),
.init(apiName: "TEST2",
endpoint: "http://www.example.com",
endpointType: .graphQL,
region: "us-east-2",
authorizationType: .amazonCognitoUserPools))

let apiPlugin = AWSAPIPlugin(configuration: configuration)

do {
try apiPlugin.configure(using: nil)
} catch {
XCTFail("Failed to configure api plugin: \(error)")
}
}

func testConfigureSuccess() throws {
let apiPlugin = AWSAPIPlugin()
let apiPluginConfig: JSONValue = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class AWSAPICategoryPluginReachabilityTests: XCTestCase {

// MARK: - Helpers

func getEndpointConfig(apiName: String, endpointType: AWSAPICategoryPluginEndpointType) throws ->
func getEndpointConfig(apiName: String, endpointType: AWSAPIPluginEndpointType) throws ->
AWSAPICategoryPluginConfiguration.EndpointConfig {
try AWSAPICategoryPluginConfiguration.EndpointConfig(
name: apiName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class AWSAPICategoryPluginConfigurationEndpointConfigTests: XCTestCase {

// MARK: - Helpers

func getEndpointConfig(apiName: String, endpointType: AWSAPICategoryPluginEndpointType) throws ->
func getEndpointConfig(apiName: String, endpointType: AWSAPIPluginEndpointType) throws ->
AWSAPICategoryPluginConfiguration.EndpointConfig {
try AWSAPICategoryPluginConfiguration.EndpointConfig(
name: apiName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class AWSAPICategoryPluginConfigurationTests: XCTestCase {

// MARK: - Helpers

func getEndpointConfig(apiName: String, endpointType: AWSAPICategoryPluginEndpointType) throws ->
func getEndpointConfig(apiName: String, endpointType: AWSAPIPluginEndpointType) throws ->
AWSAPICategoryPluginConfiguration.EndpointConfig {
try AWSAPICategoryPluginConfiguration.EndpointConfig(
name: apiName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class OperationTestBase: XCTestCase {
func setUpPlugin(
sessionFactory: URLSessionBehaviorFactory? = nil,
subscriptionConnectionFactory: SubscriptionConnectionFactory? = nil,
endpointType: AWSAPICategoryPluginEndpointType
endpointType: AWSAPIPluginEndpointType
) throws {
apiPlugin = AWSAPIPlugin(sessionFactory: sessionFactory)

Expand All @@ -50,15 +50,15 @@ class OperationTestBase: XCTestCase {

func setUpPluginForSingleResponse(
sending data: Data,
for endpointType: AWSAPICategoryPluginEndpointType
for endpointType: AWSAPIPluginEndpointType
) throws {
let task = try makeSingleValueSuccessMockTask(sending: data)
let mockSession = MockURLSession(onTaskForRequest: { _ in task })
let sessionFactory = MockSessionFactory(returning: mockSession)
try setUpPlugin(sessionFactory: sessionFactory, endpointType: endpointType)
}

func setUpPluginForSingleError(for endpointType: AWSAPICategoryPluginEndpointType) throws {
func setUpPluginForSingleError(for endpointType: AWSAPIPluginEndpointType) throws {
let task = try makeSingleValueErrorMockTask()
let mockSession = MockURLSession(onTaskForRequest: { _ in task })
let sessionFactory = MockSessionFactory(returning: mockSession)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Foundation
/// GraphQL backend, or an Amazon API Gateway endpoint.
///
/// - SeeAlso: [https://docs.aws.amazon.com/appsync/latest/devguide/security.html](AppSync Security)
public enum AWSAuthorizationType: String {
public enum AWSAuthorizationType: String, Codable {

/// For public APIs
case none = "NONE"
Expand Down

0 comments on commit 8beec7b

Please sign in to comment.