-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add @EnvironmentVariable property wrapper
- Loading branch information
Showing
5 changed files
with
254 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// | ||
// EnvironmentVariable.swift | ||
// Environment | ||
// | ||
// Created by Will Lisac on 7/27/19. | ||
// | ||
|
||
#if swift(>=5.1) | ||
/// A property wrapper that uses the specified environment variable name for property access and backing storage. | ||
/// | ||
/// The following example shows how to use the `EnvironmentVariable` property wrapper to expose | ||
/// static properties backed by enviornment variables (`"HOST"` and `"PORT"`). | ||
/// | ||
/// enum ServerSettings { | ||
/// @EnvironmentVariable(name: "HOST") | ||
/// static var host: URL? | ||
/// | ||
/// @EnvironmentVariable(name: "PORT", defaultValue: 8000) | ||
/// static var port: Int | ||
/// } | ||
/// | ||
@propertyWrapper | ||
public struct EnvironmentVariable<T> { | ||
private let name: String | ||
private let defaultValue: T | ||
|
||
private let fromEnvironmentString: (String) -> T? | ||
private let toEnvironmentString: (T) -> String? | ||
|
||
/// The environment variable value converted to type `T` using the `EnvironmentStringConvertible` protocol | ||
/// for the specified environment variable `name` if `name` is in the environment; otherwise, the specified `defaultValue`. | ||
public var wrappedValue: T { | ||
get { | ||
Environment[name].flatMap { fromEnvironmentString($0) } ?? defaultValue | ||
} | ||
nonmutating set { | ||
Environment[name] = toEnvironmentString(newValue) | ||
} | ||
} | ||
} | ||
|
||
extension EnvironmentVariable where T: EnvironmentStringConvertible { | ||
/// Instantiates an `EnvironmentVariable` property wrapper for the specified environment variable name and default value. | ||
/// - Parameter name: The environment variable name to use for property access and backing storage. | ||
/// - Parameter defaultValue: The default value to use if the name does not exist in the environment or if type conversion fails. | ||
public init(name: String, defaultValue: T) { | ||
self.name = name | ||
self.defaultValue = defaultValue | ||
self.fromEnvironmentString = { T(environmentString: $0) } | ||
self.toEnvironmentString = { $0.environmentString } | ||
} | ||
} | ||
|
||
extension EnvironmentVariable { | ||
/// Instantiates an `EnvironmentVariable` property wrapper for the specified environment variable name and default value. | ||
/// - Parameter name: The environment variable name to use for property access and backing storage. | ||
/// - Parameter defaultValue: The default value to use if the name does not exist in the environment or if type conversion fails. | ||
public init<U: EnvironmentStringConvertible>(name: String, defaultValue: T = nil) where T == U? { | ||
self.name = name | ||
self.defaultValue = defaultValue | ||
self.fromEnvironmentString = { U(environmentString: $0) } | ||
self.toEnvironmentString = { $0?.environmentString } | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// | ||
// EnvironmentVariableTests.swift | ||
// EnvironmentTests | ||
// | ||
// Created by Will Lisac on 7/27/19. | ||
// | ||
|
||
import Environment | ||
import XCTest | ||
|
||
//swiftlint:disable let_var_whitespace | ||
class EnvironmentVariableTests: XCTestCase { | ||
override func setUp() { | ||
super.setUp() | ||
resetEnvironment() | ||
} | ||
|
||
func testString() { | ||
#if swift(>=5.1) | ||
XCTAssertEqual(EnvironmentSettings.string, "hello") | ||
|
||
Environment.STRING = "string" | ||
XCTAssertEqual(EnvironmentSettings.string, "string") | ||
|
||
Environment.STRING = nil | ||
XCTAssertEqual(EnvironmentSettings.string, "hello") | ||
|
||
EnvironmentSettings.string = "custom" | ||
XCTAssertEqual(EnvironmentSettings.string, "custom") | ||
XCTAssertEqual(Environment.STRING, "custom") | ||
#endif | ||
} | ||
|
||
func testOptionalString() { | ||
#if swift(>=5.1) | ||
XCTAssertNil(EnvironmentSettings.optionalString) | ||
|
||
Environment.OPTIONAL_STRING = "string" | ||
XCTAssertEqual(EnvironmentSettings.optionalString, "string") | ||
|
||
Environment.OPTIONAL_STRING = nil | ||
XCTAssertNil(EnvironmentSettings.optionalString) | ||
|
||
EnvironmentSettings.optionalString = "custom" | ||
XCTAssertEqual(EnvironmentSettings.optionalString, "custom") | ||
XCTAssertEqual(Environment.OPTIONAL_STRING, "custom") | ||
|
||
EnvironmentSettings.optionalString = nil | ||
XCTAssertNil(EnvironmentSettings.optionalString) | ||
XCTAssertNil(Environment.OPTIONAL_STRING) | ||
#endif | ||
} | ||
|
||
func testInt() { | ||
#if swift(>=5.1) | ||
XCTAssertEqual(EnvironmentSettings.int, 10) | ||
|
||
Environment.INT = 50 | ||
XCTAssertEqual(EnvironmentSettings.int, 50) | ||
|
||
Environment.INT = nil | ||
XCTAssertEqual(EnvironmentSettings.int, 10) | ||
|
||
EnvironmentSettings.int = 30 | ||
XCTAssertEqual(EnvironmentSettings.int, 30) | ||
XCTAssertEqual(Environment.INT, 30) | ||
XCTAssertEqual(Environment.INT, "30") | ||
#endif | ||
} | ||
|
||
func testOptionalInt() { | ||
#if swift(>=5.1) | ||
XCTAssertNil(EnvironmentSettings.optionalInt) | ||
|
||
Environment.OPTIONAL_INT = 10 | ||
XCTAssertEqual(EnvironmentSettings.optionalInt, 10) | ||
|
||
Environment.OPTIONAL_INT = nil | ||
XCTAssertNil(EnvironmentSettings.optionalInt) | ||
|
||
EnvironmentSettings.optionalInt = 30 | ||
XCTAssertEqual(EnvironmentSettings.optionalInt, 30) | ||
XCTAssertEqual(Environment.OPTIONAL_INT, 30) | ||
XCTAssertEqual(Environment.OPTIONAL_INT, "30") | ||
|
||
EnvironmentSettings.optionalInt = nil | ||
XCTAssertNil(EnvironmentSettings.optionalInt) | ||
XCTAssertNil(Environment.OPTIONAL_INT) | ||
#endif | ||
} | ||
|
||
func testIntArray() { | ||
#if swift(>=5.1) | ||
XCTAssertEqual(EnvironmentSettings.intArray, []) | ||
|
||
Environment.INT_ARRAY = "1,2,3" | ||
XCTAssertEqual(EnvironmentSettings.intArray, [1, 2, 3]) | ||
|
||
Environment.INT_ARRAY = nil | ||
XCTAssertEqual(EnvironmentSettings.intArray, []) | ||
|
||
EnvironmentSettings.intArray = [3, 2, 1] | ||
XCTAssertEqual(EnvironmentSettings.intArray, [3, 2, 1]) | ||
XCTAssertEqual(Environment.INT_ARRAY, "3,2,1") | ||
#endif | ||
} | ||
|
||
func testOptionalIntArray() { | ||
#if swift(>=5.1) | ||
XCTAssertNil(EnvironmentSettings.optionalIntArray) | ||
|
||
Environment.OPTIONAL_INT_ARRAY = [10, 20] | ||
XCTAssertEqual(EnvironmentSettings.optionalIntArray, [10, 20]) | ||
|
||
Environment.OPTIONAL_INT_ARRAY = nil | ||
XCTAssertNil(EnvironmentSettings.optionalIntArray) | ||
|
||
EnvironmentSettings.optionalIntArray = [30, 40] | ||
XCTAssertEqual(EnvironmentSettings.optionalIntArray, [30, 40]) | ||
XCTAssertEqual(Environment.OPTIONAL_INT_ARRAY, [30, 40]) | ||
XCTAssertEqual(Environment.OPTIONAL_INT_ARRAY, "30,40") | ||
|
||
EnvironmentSettings.optionalIntArray = nil | ||
XCTAssertNil(EnvironmentSettings.optionalIntArray) | ||
XCTAssertNil(Environment.OPTIONAL_INT_ARRAY) | ||
#endif | ||
} | ||
|
||
private func resetEnvironment() { | ||
ProcessInfo.processInfo.environment.keys.forEach { unsetenv($0) } | ||
|
||
XCTAssert(ProcessInfo.processInfo.environment.isEmpty) | ||
} | ||
} | ||
|
||
#if swift(>=5.1) | ||
enum EnvironmentSettings { | ||
@EnvironmentVariable(name: "STRING", defaultValue: "hello") | ||
static var string: String | ||
|
||
@EnvironmentVariable(name: "OPTIONAL_STRING") | ||
static var optionalString: String? | ||
|
||
@EnvironmentVariable(name: "INT", defaultValue: 10) | ||
static var int: Int | ||
|
||
@EnvironmentVariable(name: "OPTIONAL_INT") | ||
static var optionalInt: Int? | ||
|
||
@EnvironmentVariable(name: "INT_ARRAY", defaultValue: []) | ||
static var intArray: [Int] | ||
|
||
@EnvironmentVariable(name: "OPTIONAL_INT_ARRAY") | ||
// swiftlint:disable:next discouraged_optional_collection | ||
static var optionalIntArray: [Int]? | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters