214 lines
5.3 KiB
Swift
214 lines
5.3 KiB
Swift
import Dependencies
|
|
import DependenciesMacros
|
|
import DotEnv
|
|
import Foundation
|
|
import Logging
|
|
import Models
|
|
import MQTTNIO
|
|
import NIO
|
|
|
|
public extension DependencyValues {
|
|
var cliClient: CliClient {
|
|
get { self[CliClient.self] }
|
|
set { self[CliClient.self] = newValue }
|
|
}
|
|
}
|
|
|
|
/// Represents the interface needed for the command line application.
|
|
///
|
|
///
|
|
@DependencyClient
|
|
public struct CliClient {
|
|
|
|
/// Parse a log level from the given `EnvVars`.
|
|
public var logLevel: @Sendable (EnvVars) -> Logger.Level = { _ in .debug }
|
|
|
|
/// Generate the `EnvVars` with the given parameters.
|
|
public var makeEnvVars: @Sendable (EnvVarsRequest) async throws -> EnvVars
|
|
|
|
/// Generate the `MQTTClient` with the given parameters.
|
|
public var makeClient: @Sendable (ClientRequest) throws -> MQTTClient
|
|
|
|
/// Attempt to parse a string to an `MQTTClient.Version`.
|
|
public var parseMqttClientVersion: @Sendable (String) -> MQTTClient.Version?
|
|
|
|
/// Represents the parameters needed to create an `MQTTClient`.
|
|
///
|
|
///
|
|
public struct ClientRequest: Sendable {
|
|
public let environment: EnvVars
|
|
public let eventLoopGroup: MultiThreadedEventLoopGroup
|
|
public let logger: Logger?
|
|
|
|
public init(
|
|
environment: EnvVars,
|
|
eventLoopGroup: MultiThreadedEventLoopGroup,
|
|
logger: Logger?
|
|
) {
|
|
self.environment = environment
|
|
self.eventLoopGroup = eventLoopGroup
|
|
self.logger = logger
|
|
}
|
|
|
|
}
|
|
|
|
public struct EnvVarsRequest: Sendable {
|
|
|
|
public let envFilePath: String?
|
|
public let logger: Logger?
|
|
public let mqttClientVersion: String?
|
|
|
|
public init(
|
|
envFilePath: String? = nil,
|
|
logger: Logger? = nil,
|
|
version mqttClientVersion: String? = nil
|
|
) {
|
|
self.envFilePath = envFilePath
|
|
self.logger = logger
|
|
self.mqttClientVersion = mqttClientVersion
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
extension CliClient: DependencyKey {
|
|
public static let testValue: CliClient = Self()
|
|
public static var liveValue: CliClient {
|
|
Self(
|
|
logLevel: { Logger.Level.from(environment: $0) },
|
|
makeEnvVars: {
|
|
try await EnvVars.load(
|
|
dotEnvFile: $0.envFilePath,
|
|
logger: $0.logger,
|
|
version: $0.mqttClientVersion
|
|
)
|
|
},
|
|
makeClient: {
|
|
MQTTClient(
|
|
environment: $0.environment,
|
|
eventLoopGroup: $0.eventLoopGroup,
|
|
logger: $0.logger
|
|
)
|
|
},
|
|
parseMqttClientVersion: { .init(string: $0) }
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
extension EnvironmentDependency {
|
|
func dotEnvDict(path: String?) async throws -> [String: String] {
|
|
guard let path,
|
|
let file = FileType(path: path)
|
|
else { return [:] }
|
|
return try await load(file)
|
|
}
|
|
}
|
|
|
|
extension EnvVars {
|
|
|
|
/// Load the `EnvVars` from the environment.
|
|
///
|
|
/// - Paramaters:
|
|
/// - dotEnvFile: An optional environment file to load.
|
|
/// - logger: An optional logger to use for debugging.
|
|
/// - version: A version that is specified from command line, ignoring any environment variable.
|
|
static func load(
|
|
dotEnvFile: String?,
|
|
logger: Logger?,
|
|
version: String?
|
|
) async throws -> EnvVars {
|
|
@Dependency(\.environment) var environment
|
|
|
|
let defaultEnvVars = EnvVars()
|
|
let encoder = environment.jsonEncoder()
|
|
let decoder = environment.jsonDecoder()
|
|
|
|
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
|
|
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
|
?? [:]
|
|
|
|
let dotEnvDict = try await environment.dotEnvDict(path: dotEnvFile)
|
|
let envVarsDict = defaultEnvDict
|
|
.merging(environment.processInfo(), uniquingKeysWith: { $1 })
|
|
.merging(dotEnvDict, uniquingKeysWith: { $1 })
|
|
|
|
var envVars = (try? JSONSerialization.data(withJSONObject: envVarsDict))
|
|
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
|
?? defaultEnvVars
|
|
|
|
if let version {
|
|
envVars.version = version
|
|
}
|
|
|
|
logger?.debug("Done loading EnvVars...")
|
|
|
|
return envVars
|
|
}
|
|
}
|
|
|
|
@_spi(Internal)
|
|
public extension MQTTClient {
|
|
convenience init(
|
|
environment envVars: EnvVars,
|
|
eventLoopGroup: EventLoopGroup,
|
|
logger: Logger?
|
|
) {
|
|
self.init(
|
|
host: envVars.host,
|
|
port: envVars.port != nil ? Int(envVars.port!) : nil,
|
|
identifier: envVars.identifier,
|
|
eventLoopGroupProvider: .shared(eventLoopGroup),
|
|
logger: logger,
|
|
configuration: .init(
|
|
version: .parseOrDefualt(string: envVars.version),
|
|
disablePing: false,
|
|
userName: envVars.userName,
|
|
password: envVars.password
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
extension MQTTClient.Version {
|
|
static let `default` = Self.v3_1_1
|
|
|
|
static func parseOrDefualt(string: String?) -> Self {
|
|
guard let string, let value = Self(string: string) else {
|
|
return .default
|
|
}
|
|
return value
|
|
}
|
|
|
|
init?(string: String) {
|
|
if string.contains("5") {
|
|
self = .v5_0
|
|
} else if string.contains("3") {
|
|
self = .v3_1_1
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Logger.Level {
|
|
|
|
/// Parse a `Logger.Level` from the loaded `EnvVars`.
|
|
static func from(environment envVars: EnvVars) -> Self {
|
|
// If the log level was set via an environment variable.
|
|
if let logLevel = envVars.logLevel {
|
|
return logLevel
|
|
}
|
|
// Parse the appEnv to derive an log level.
|
|
switch envVars.appEnv {
|
|
case .staging, .development:
|
|
return .debug
|
|
case .production:
|
|
return .info
|
|
case .testing:
|
|
return .trace
|
|
}
|
|
}
|
|
}
|