import Foundation struct Configuration { let serverPort: Int let mail: EMail? struct EMail { /// The url to the root of the server let serverDomain: String /// SMTP server address let emailHostname: String /// username to login let email: String /// password to login let password: String /// The number of minutes until a password reset token is no longer valid let tokenExpiryDuration: Int } /** The folder where the data should be stored. If the folder is set to `nil`, then the `Resources` folder is used. */ let dataDirectory: String? /// The authentication tokens to access the metrics let monitoringTokens: 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") } guard !logPath.hasPrefix("/") else { return .init(fileURLWithPath: logPath) } return resourcesDirectory.appendingPathComponent(logPath) } func customDataDirectory(or publicDirectory: String) -> URL { guard let dataDirectory else { return URL(fileURLWithPath: publicDirectory) } return URL(fileURLWithPath: dataDirectory) } } extension Configuration { init(loadFromUrl url: URL) throws { let data: Data do { data = try Data(contentsOf: url) } catch { log("Failed to load configuration data from \(url.path): \(error)") throw error } try self.init(data: data) } init(data: Data) throws { do { self = try JSONDecoder().decode(Configuration.self, from: data) } catch { log("Failed to decode configuration: \(error)") throw error } } } extension Configuration.EMail: Codable { } extension Configuration: Codable { }