Skip to content

Commit

Permalink
Add Environment.require(_:), require(_:as:)
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed Feb 10, 2025
1 parent c1d1757 commit 9fda4ac
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 5 deletions.
44 changes: 39 additions & 5 deletions Sources/Hummingbird/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}

Expand Down Expand Up @@ -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<T: LosslessStringConvertible>(_ 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
Expand Down
32 changes: 32 additions & 0 deletions Tests/HummingbirdTests/EnvironmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 9fda4ac

Please sign in to comment.