diff --git a/Sources/Hummingbird/Environment.swift b/Sources/Hummingbird/Environment.swift index dabe9e0a..0920a3b5 100644 --- a/Sources/Hummingbird/Environment.swift +++ b/Sources/Hummingbird/Environment.swift @@ -33,16 +33,29 @@ import Darwin.C /// Access environment variables public struct Environment: Sendable, Decodable, ExpressibleByDictionaryLiteral { - struct Error: Swift.Error, Equatable { - enum Value { + public struct Error: Swift.Error, Equatable { + enum Code { case dotEnvParseError + case variableDoesNotExist + case variableDoesNotConvert } - private let value: Value - private init(_ value: Value) { - self.value = value + fileprivate let code: Code + public let message: String? + fileprivate init(_ code: Code, message: String? = nil) { + self.code = code + self.message = message } + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.code == rhs.code + } + + /// Required variable does not exist + public static var variableDoesNotExist: Self { .init(.variableDoesNotExist) } + /// Required variable does not convert to type + public static var variableDoesNotConvert: Self { .init(.variableDoesNotConvert) } + /// Error while parsing dot env file public static var dotEnvParseError: Self { .init(.dotEnvParseError) } } @@ -93,6 +106,27 @@ public struct Environment: Sendable, Decodable, ExpressibleByDictionaryLiteral { self.values[s.lowercased()].map { T(String($0)) } ?? nil } + /// Require environment variable with name + /// - Parameter s: Environment variable name + public func require(_ s: String) throws -> String { + guard let value = self.values[s.lowercased()] else { + throw Error(.variableDoesNotExist, message: "Environment variable '\(s)' does not exist") + } + return value + } + + /// Require environment variable with name as a certain type + /// - Parameters: + /// - s: Environment variable name + /// - as: Type we want variable to be cast to + public func require(_ s: String, as: T.Type) throws -> T { + let stringValue = try self.require(s) + guard let value = T(stringValue) else { + throw Error(.variableDoesNotConvert, message: "Environment variable '\(s)' can not be converted to \(T.self)") + } + return value + } + /// Set environment variable /// /// This sets the variable within this type and also calls `setenv` so future versions diff --git a/Tests/HummingbirdTests/EnvironmentTests.swift b/Tests/HummingbirdTests/EnvironmentTests.swift index f27bf764..272d1cab 100644 --- a/Tests/HummingbirdTests/EnvironmentTests.swift +++ b/Tests/HummingbirdTests/EnvironmentTests.swift @@ -36,6 +36,38 @@ final class EnvironmentTests: XCTestCase { XCTAssertEqual(env?.get("TEST_VAR"), "testSetFromCodable") } + func testRequire() throws { + var env = Environment() + env.set("TEST_REQUIRE", value: "testing") + let value = try env.require("TEST_REQUIRE") + XCTAssertEqual(value, "testing") + XCTAssertThrowsError(try env.require("TEST_REQUIRE2")) { error in + if let error = error as? Environment.Error, error == .variableDoesNotExist { + return + } + XCTFail() + } + } + + func testRequireAs() throws { + var env = Environment() + env.set("TEST_REQUIRE_AS", value: "testing") + let value = try env.require("TEST_REQUIRE_AS", as: String.self) + XCTAssertEqual(value, "testing") + XCTAssertThrowsError(try env.require("TEST_REQUIRE_AS_2", as: Int.self)) { error in + if let error = error as? Environment.Error, error == .variableDoesNotExist { + return + } + XCTFail() + } + XCTAssertThrowsError(try env.require("TEST_REQUIRE_AS", as: Int.self)) { error in + if let error = error as? Environment.Error, error == .variableDoesNotConvert { + return + } + XCTFail() + } + } + func testSet() { var env = Environment() env.set("TEST_VAR", value: "testSet")