amplify-swift/Amplify/Core/Configuration/AmplifyConfiguration.swift

198 lines
7.6 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
/// Configures the Amplify system with sub-configurations for each supported category
public struct AmplifyConfiguration: Codable {
enum CodingKeys: String, CodingKey {
case analytics
case api
case auth
case dataStore
case geo
case hub
case logging
case predictions
case storage
}
/// Configurations for the Amplify Analytics category
let analytics: AnalyticsCategoryConfiguration?
/// Configurations for the Amplify API category
let api: APICategoryConfiguration?
/// Configurations for the Amplify Auth category
let auth: AuthCategoryConfiguration?
/// Configurations for the Amplify DataStore category
let dataStore: DataStoreCategoryConfiguration?
/// Configurations for the Amplify Geo category
let geo: GeoCategoryConfiguration?
/// Configurations for the Amplify Hub category
let hub: HubCategoryConfiguration?
/// Configurations for the Amplify Logging category
let logging: LoggingCategoryConfiguration?
/// Configurations for the Amplify Predictions category
let predictions: PredictionsCategoryConfiguration?
/// Configurations for the Amplify Storage category
let storage: StorageCategoryConfiguration?
public init(analytics: AnalyticsCategoryConfiguration? = nil,
api: APICategoryConfiguration? = nil,
auth: AuthCategoryConfiguration? = nil,
dataStore: DataStoreCategoryConfiguration? = nil,
geo: GeoCategoryConfiguration? = nil,
hub: HubCategoryConfiguration? = nil,
logging: LoggingCategoryConfiguration? = nil,
predictions: PredictionsCategoryConfiguration? = nil,
storage: StorageCategoryConfiguration? = nil) {
self.analytics = analytics
self.api = api
self.auth = auth
self.dataStore = dataStore
self.geo = geo
self.hub = hub
self.logging = logging
self.predictions = predictions
self.storage = storage
}
/// Initialize `AmplifyConfiguration` by loading it from a URL representing the configuration file.
public init(configurationFile url: URL) throws {
self = try AmplifyConfiguration.loadAmplifyConfiguration(from: url)
}
}
// MARK: - Configure
extension Amplify {
/// Configures Amplify with the specified configuration.
///
/// This method must be invoked after registering plugins, and before using any Amplify category. It must not be
/// invoked more than once.
///
/// **Lifecycle**
///
/// Internally, Amplify configures the Hub and Logging categories first, so they are available to plugins in the
/// remaining categories during the configuration phase. Plugins for the Hub and Logging categories must not
/// assume that any other categories are available.
///
/// After Amplify has configured all of its categories, it will dispatch a `HubPayload.EventName.Amplify.configured`
/// event to each Amplify Hub channel. After this point, plugins may invoke calls on other Amplify categories.
///
/// - Parameter configuration: The AmplifyConfiguration for specified Categories
public static func configure(_ configuration: AmplifyConfiguration? = nil) throws {
log.info("Configuring")
log.debug("Configuration: \(String(describing: configuration))")
guard !isConfigured else {
let error = ConfigurationError.amplifyAlreadyConfigured(
"Amplify has already been configured.",
"""
Remove the duplicate call to `Amplify.configure()`
"""
)
throw error
}
let resolvedConfiguration: AmplifyConfiguration
do {
resolvedConfiguration = try Amplify.resolve(configuration: configuration)
} catch {
log.info("Failed to find Amplify configuration.")
if isRunningForSwiftUIPreviews {
log.info("Running for SwiftUI previews with no configuration file present, skipping configuration.")
return
} else {
throw error
}
}
// Always configure Logging, Hub and Auth first, so they are available to other categories.
// Auth is a special case for other plugins which depend on using Auth when being configured themselves.
let manuallyConfiguredCategories = [CategoryType.logging, .hub, .auth]
for categoryType in manuallyConfiguredCategories {
try configure(categoryType.category, using: resolvedConfiguration)
}
// Looping through all categories to ensure we don't accidentally forget a category at some point in the future
let remainingCategories = CategoryType.allCases.filter { !manuallyConfiguredCategories.contains($0) }
for categoryType in remainingCategories {
switch categoryType {
case .analytics:
try configure(Analytics, using: resolvedConfiguration)
case .api:
try configure(API, using: resolvedConfiguration)
case .dataStore:
try configure(DataStore, using: resolvedConfiguration)
case .geo:
try configure(Geo, using: resolvedConfiguration)
case .predictions:
try configure(Predictions, using: resolvedConfiguration)
case .storage:
try configure(Storage, using: resolvedConfiguration)
case .hub, .logging, .auth:
// Already configured
break
}
}
isConfigured = true
notifyAllHubChannels()
}
/// Notifies all hub channels that Amplify is configured, in case any plugins need to be notified of the end of the
/// configuration phase (e.g., to set up cross-channel dependencies)
private static func notifyAllHubChannels() {
let payload = HubPayload(eventName: HubPayload.EventName.Amplify.configured)
for channel in HubChannel.amplifyChannels {
Hub.plugins.values.forEach { $0.dispatch(to: channel, payload: payload) }
}
}
/// If `candidate` is `CategoryConfigurable`, then invokes `candidate.configure(using: configuration)`.
private static func configure(_ candidate: Category, using configuration: AmplifyConfiguration) throws {
guard let configurable = candidate as? CategoryConfigurable else {
return
}
try configurable.configure(using: configuration)
}
/// Configures a list of plugins with the specified CategoryConfiguration. If any configurations do not match the
/// specified plugins, emits a log warning.
static func configure(plugins: [Plugin], using configuration: CategoryConfiguration?) throws {
var pluginConfigurations = configuration?.plugins
for plugin in plugins {
let pluginConfiguration = pluginConfigurations?[plugin.key]
try plugin.configure(using: pluginConfiguration)
pluginConfigurations?.removeValue(forKey: plugin.key)
}
if let pluginKeys = pluginConfigurations?.keys {
for unusedPluginKey in pluginKeys {
log.warn("No plugin found for configuration key `\(unusedPluginKey)`. Add a plugin for that key.")
}
}
}
//// Indicates is the runtime is for SwiftUI Previews
private static var isRunningForSwiftUIPreviews: Bool {
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != nil
}
}