import Foundation struct Config { /// The port where the server runs let port: Int /** The path to the file containing the containing the device authentication token. If the path is relative, then it is relative to the `Resources` folder. */ let keyFileName: String /// The seconds to wait for a response from the device let deviceTimeout: Int64 /// The authentication tokens to use for monitoring of the service let authenticationTokens: Set /** The path to the folder where the metric logs are stored If no path is provided, then a folder `logs` in the resources directory is created. If the path is relative, then it is assumed relative to the resources directory. */ let logPath: String? func logURL(possiblyRelativeTo resourcesDirectory: URL) -> URL { guard let logPath else { return resourcesDirectory.appendingPathComponent("logs") } return Config.url(logPath, possiblyRelativeTo: resourcesDirectory) } func keyURL(possiblyRelativeTo resourcesDirectory: URL) -> URL { Config.url(keyFileName, possiblyRelativeTo: resourcesDirectory) } private static func url(_ name: String, possiblyRelativeTo resourcesDirectory: URL) -> URL { guard !name.hasPrefix("/") else { return .init(fileURLWithPath: name) } return resourcesDirectory.appendingPathComponent(name) } } extension Config: Codable { } extension Config { init(loadFrom url: URL) throws { guard FileManager.default.fileExists(atPath: url.path) else { printAndFlush("No configuration file found at \(url.path)") fatalError("No configuration file found") } let data: Data do { data = try Data(contentsOf: url) } catch { printAndFlush("Failed to read config data: \(error)") throw error } do { self = try JSONDecoder().decode(Config.self, from: data) } catch { printAndFlush("Failed to decode config data: \(error)") throw error } } }