Skip to content

Commit 8b19c11

Browse files
committed
feat(geo): add Geo category (#1417)
* feat(geo): add Geo category
1 parent b48df4e commit 8b19c11

29 files changed

+1770
-2
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 124 additions & 0 deletions
Large diffs are not rendered by default.

Amplify/Amplify.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class Amplify {
2929
public static internal(set) var API: APICategory = AmplifyAPICategory()
3030
public static internal(set) var Auth = AuthCategory()
3131
public static internal(set) var DataStore = DataStoreCategory()
32+
public static internal(set) var Geo = GeoCategory()
3233
public static internal(set) var Hub = HubCategory()
3334
public static internal(set) var Predictions = PredictionsCategory()
3435
public static internal(set) var Storage = StorageCategory()
@@ -45,9 +46,9 @@ public class Amplify {
4546
}
4647
private static let loggingAtomic = AtomicValue<LoggingCategory>(initialValue: LoggingCategory())
4748

48-
/// Adds `plugin` to the Analytics category
49+
/// Adds `plugin` to the category
4950
///
50-
/// - Parameter plugin: The AnalyticsCategoryPlugin to add
51+
/// - Parameter plugin: The plugin to add
5152
public static func add<P: Plugin>(plugin: P) throws {
5253
log.debug("Adding plugin: \(plugin))")
5354
switch plugin {
@@ -59,6 +60,8 @@ public class Amplify {
5960
try Auth.add(plugin: plugin)
6061
case let plugin as DataStoreCategoryPlugin:
6162
try DataStore.add(plugin: plugin)
63+
case let plugin as GeoCategoryPlugin:
64+
try Geo.add(plugin: plugin)
6265
case let plugin as HubCategoryPlugin:
6366
try Hub.add(plugin: plugin)
6467
case let plugin as LoggingCategoryPlugin:
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
/// Geo Error
11+
public enum GeoError {
12+
/// Configuration Error
13+
case configuration(ErrorDescription, RecoverySuggestion, Error? = nil)
14+
/// Unknown Error
15+
case unknown(ErrorDescription, RecoverySuggestion, Error? = nil)
16+
}
17+
18+
extension GeoError: AmplifyError {
19+
/// Initializer
20+
/// - Parameters:
21+
/// - errorDescription: Error Description
22+
/// - recoverySuggestion: Recovery Suggestion
23+
/// - error: Underlying Error
24+
public init(
25+
errorDescription: ErrorDescription = "An unknown error occurred",
26+
recoverySuggestion: RecoverySuggestion = "See `underlyingError` for more details",
27+
error: Error) {
28+
if let error = error as? Self {
29+
self = error
30+
} else if error.isOperationCancelledError {
31+
self = .unknown("Operation cancelled", "", error)
32+
} else {
33+
self = .unknown(errorDescription, recoverySuggestion, error)
34+
}
35+
}
36+
37+
/// Error Description
38+
public var errorDescription: ErrorDescription {
39+
switch self {
40+
case .configuration(let errorDescription, _, _):
41+
return errorDescription
42+
case .unknown(let errorDescription, _, _):
43+
return "Unexpected error occurred with message: \(errorDescription)"
44+
}
45+
}
46+
47+
/// Recovery Suggestion
48+
public var recoverySuggestion: RecoverySuggestion {
49+
switch self {
50+
case .configuration(_, let recoverySuggestion, _):
51+
return recoverySuggestion
52+
case .unknown:
53+
return AmplifyErrorMessages.shouldNotHappenReportBugToAWS()
54+
}
55+
}
56+
57+
/// Underlying Error
58+
public var underlyingError: Error? {
59+
switch self {
60+
case .configuration(_, _, let error):
61+
return error
62+
case .unknown(_, _, let error):
63+
return error
64+
}
65+
}
66+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
extension GeoCategory: GeoCategoryBehavior {
9+
// MARK: - Search
10+
11+
/// Search for places or points of interest.
12+
/// - Parameters:
13+
/// - text: The place name or address to be used in the search. (case insensitive)
14+
/// - area: The area (.near or .boundingBox) for the search. (optional)
15+
/// - countries: Limits the search to the given a list of countries/regions. (optional)
16+
/// - maxResults: The maximum number of results returned per request. (optional)
17+
/// - placeIndexName: The name of the Place Index to query. (optional)
18+
/// - completionHandler: The completion handler receives a Response object. The
19+
/// success case provides a Place array.
20+
public func search(for text: String,
21+
area: Geo.SearchArea? = nil,
22+
countries: [Geo.Country]? = nil,
23+
maxResults: Int? = nil,
24+
placeIndexName: String? = nil,
25+
completionHandler: @escaping Geo.ResultsHandler<[Geo.Place]>) {
26+
plugin.search(for: text,
27+
area: area,
28+
countries: countries,
29+
maxResults: maxResults,
30+
placeIndexName: placeIndexName,
31+
completionHandler: completionHandler)
32+
}
33+
34+
/// Reverse geocodes a given pair of coordinates and returns a list of Places
35+
/// closest to the specified position.
36+
/// - Parameters:
37+
/// - coordinates: Specifies a coordinate for the query.
38+
/// - maxResults: The maximum number of results returned per request. (optional)
39+
/// - placeIndexName: The name of the Place Index to query. (optional)
40+
/// - completionHandler: The completion handler receives a Response object. The
41+
/// success case provides a Place array.
42+
public func search(for coordinates: Geo.Coordinates,
43+
maxResults: Int? = nil,
44+
placeIndexName: String? = nil,
45+
completionHandler: @escaping Geo.ResultsHandler<[Geo.Place]>) {
46+
plugin.search(for: coordinates,
47+
maxResults: maxResults,
48+
placeIndexName: placeIndexName,
49+
completionHandler: completionHandler)
50+
}
51+
52+
// MARK: - Maps
53+
54+
/// Retrieves metadata for available Map resources.
55+
/// - Parameter completionHandler: The completion handler receives a Response
56+
/// object. The success case provides an array of available Map resources.
57+
public func getAvailableMaps() -> [Geo.MapStyle] {
58+
plugin.getAvailableMaps()
59+
}
60+
61+
/// Retrieves the default Map resource.
62+
/// - Parameter completionHandler: The completion handler receives a Response
63+
/// object. The success case provides an array of available Map resources.
64+
public func getDefaultMap() -> Geo.MapStyle {
65+
plugin.getDefaultMap()
66+
}
67+
}
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+
public extension HubPayload.EventName {
9+
/// Geo hub events
10+
struct Geo { }
11+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
/// The Geo category enables you to interact with geospacial services.
9+
final public class GeoCategory: Category {
10+
/// Geo category type
11+
public let categoryType = CategoryType.geo
12+
13+
/// Geo category plugins
14+
var plugins = [PluginKey: GeoCategoryPlugin]()
15+
16+
/// Returns the plugin added to the category, if only one plugin is added. Accessing this property if no plugins
17+
/// are added, or if more than one plugin is added, will cause a preconditionFailure.
18+
var plugin: GeoCategoryPlugin {
19+
guard isConfigured else {
20+
preconditionFailure(
21+
"""
22+
\(categoryType.displayName) category is not configured. Call Amplify.configure() before using \
23+
any methods on the category.
24+
"""
25+
)
26+
}
27+
28+
guard !plugins.isEmpty else {
29+
preconditionFailure("No plugins added to \(categoryType.displayName) category.")
30+
}
31+
32+
guard plugins.count == 1, let plugin = plugins.first?.value else {
33+
preconditionFailure(
34+
"""
35+
More than 1 plugin added to \(categoryType.displayName) category. \
36+
You must invoke operations on this category by getting the plugin you want, as in:
37+
#"Amplify.\(categoryType.displayName).getPlugin(for: "ThePluginKey").foo()
38+
"""
39+
)
40+
}
41+
42+
return plugin
43+
}
44+
45+
var isConfigured = false
46+
47+
// MARK: - Plugin handling
48+
49+
/// Adds `plugin` to the list of Plugins that implement functionality for this category.
50+
///
51+
/// - Parameter plugin: The Plugin to add
52+
public func add(plugin: GeoCategoryPlugin) throws {
53+
log.debug("Adding plugin: \(String(describing: plugin))")
54+
let key = plugin.key
55+
guard !key.isEmpty else {
56+
let pluginDescription = String(describing: plugin)
57+
let error = GeoError.configuration(
58+
"Plugin \(pluginDescription) has an empty `key`.",
59+
"Set the `key` property for \(String(describing: plugin))")
60+
throw error
61+
}
62+
63+
guard !isConfigured else {
64+
let pluginDescription = String(describing: plugin)
65+
let error = ConfigurationError.amplifyAlreadyConfigured(
66+
"\(pluginDescription) cannot be added after `Amplify.configure()`.",
67+
"Do not add plugins after calling `Amplify.configure()`."
68+
)
69+
throw error
70+
}
71+
72+
plugins[plugin.key] = plugin
73+
}
74+
75+
/// Returns the added plugin with the specified `key` property.
76+
///
77+
/// - Parameter key: The PluginKey (String) of the plugin to retrieve
78+
/// - Returns: The wrapped plugin
79+
public func getPlugin(for key: PluginKey) throws -> GeoCategoryPlugin {
80+
guard let plugin = plugins[key] else {
81+
let keys = plugins.keys.joined(separator: ", ")
82+
let error = GeoError.configuration(
83+
"No plugin has been added for '\(key)'.",
84+
"Either add a plugin for '\(key)', or use one of the known keys: \(keys)")
85+
throw error
86+
}
87+
return plugin
88+
}
89+
90+
/// Removes the plugin registered for `key` from the list of Plugins that implement functionality for this category.
91+
/// If no plugin has been added for `key`, no action is taken, making this method safe to call multiple times.
92+
///
93+
/// - Parameter key: The key used to `add` the plugin
94+
public func removePlugin(for key: PluginKey) {
95+
plugins.removeValue(forKey: key)
96+
}
97+
98+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
/// Behavior of the Geo category that clients will use
11+
public protocol GeoCategoryBehavior {
12+
13+
// MARK: - Search
14+
15+
/// Search for places or points of interest.
16+
/// - Parameters:
17+
/// - text: The place name or address to be used in the search. (case insensitive)
18+
/// - area: The area (.near or .boundingBox) for the search. (optional)
19+
/// - countries: Limits the search to the given a list of countries/regions. (optional)
20+
/// - maxResults: The maximum number of results returned per request. (optional)
21+
/// - placeIndexName: The name of the Place Index to query. (optional)
22+
/// - completionHandler: The completion handler receives a Response object. The
23+
/// success case provides a Place array.
24+
func search(for text: String, // swiftlint:disable:this function_parameter_count
25+
area: Geo.SearchArea?,
26+
countries: [Geo.Country]?,
27+
maxResults: Int?,
28+
placeIndexName: String?,
29+
completionHandler: @escaping Geo.ResultsHandler<[Geo.Place]>)
30+
31+
/// Reverse geocodes a given pair of coordinates and returns a list of Places
32+
/// closest to the specified position.
33+
/// - Parameters:
34+
/// - coordinates: Specifies a coordinate for the query.
35+
/// - maxResults: The maximum number of results returned per request. (optional)
36+
/// - placeIndexName: The name of the Place Index to query. (optional)
37+
/// - completionHandler: The completion handler receives a Response object. The
38+
/// success case provides a Place array.
39+
func search(for coordinates: Geo.Coordinates,
40+
maxResults: Int?,
41+
placeIndexName: String?,
42+
completionHandler: @escaping Geo.ResultsHandler<[Geo.Place]>)
43+
44+
// MARK: - Maps
45+
46+
/// Retrieves metadata for available Map resources.
47+
/// - Parameter completionHandler: The completion handler receives a Response
48+
/// object. The success case provides an array of available Map resources.
49+
func getAvailableMaps() -> [Geo.MapStyle]
50+
51+
/// Retrieves the default Map resource (first map in amplifyconfiguration.json).
52+
/// - Parameter completionHandler: The completion handler receives a Response
53+
/// object. The success case provides an array of available Map resources.
54+
func getDefaultMap() -> Geo.MapStyle
55+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
/// Geo category configuration
11+
public struct GeoCategoryConfiguration: CategoryConfiguration {
12+
13+
/// Dictionary of plugin keys to plugin configurations
14+
public let plugins: [String: JSONValue]
15+
16+
/// Initializer
17+
/// - Parameter plugins: Plugin configuration dictionary
18+
public init(plugins: [String: JSONValue] = [:]) {
19+
self.plugins = plugins
20+
}
21+
}
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+
/// Geo category plugin
9+
public protocol GeoCategoryPlugin: Plugin, GeoCategoryBehavior { }
10+
11+
public extension GeoCategoryPlugin {
12+
/// Geo category type
13+
var categoryType: CategoryType {
14+
return .geo
15+
}
16+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
extension GeoCategory: CategoryConfigurable {
9+
10+
func configure(using configuration: CategoryConfiguration?) throws {
11+
guard !isConfigured else {
12+
let error = ConfigurationError.amplifyAlreadyConfigured(
13+
"\(categoryType.displayName) has already been configured.",
14+
"Remove the duplicate call to `Amplify.configure()`"
15+
)
16+
throw error
17+
}
18+
19+
try Amplify.configure(plugins: Array(plugins.values), using: configuration)
20+
21+
isConfigured = true
22+
}
23+
24+
func configure(using amplifyConfiguration: AmplifyConfiguration) throws {
25+
try configure(using: categoryConfiguration(from: amplifyConfiguration))
26+
}
27+
28+
}

0 commit comments

Comments
 (0)