import Dependencies import DependenciesMacros import FileClient import Foundation public extension DependencyValues { /// Perform operations with configuration files. var configurationClient: ConfigurationClient { get { self[ConfigurationClient.self] } set { self[ConfigurationClient.self] = newValue } } } /// Handles interactions with configuration files. @DependencyClient public struct ConfigurationClient: Sendable { /// Find a configuration file in the given directory or in current working directory. public var find: @Sendable (URL?) async throws -> ConfigurationFile? /// Load a configuration file. public var load: @Sendable (ConfigurationFile) async throws -> Configuration /// Write a configuration file. public var write: @Sendable (Configuration, ConfigurationFile) async throws -> Void /// Find a configuration file and load it if found. public func findAndLoad(_ url: URL? = nil) async throws -> Configuration { guard let url = try await find(url) else { throw ConfigurationClientError.configurationNotFound } return try await load(url) } } extension ConfigurationClient: DependencyKey { public static let testValue: ConfigurationClient = Self() public static var liveValue: ConfigurationClient { .init( find: { try await findConfiguration($0) }, load: { try await $0.load() ?? .mock }, write: { try await $1.write($0) } ) } } private func findConfiguration(_ url: URL?) async throws -> ConfigurationFile? { @Dependency(\.fileClient) var fileClient var url: URL! = url if url == nil { url = try await URL(filePath: fileClient.currentDirectory()) } // Check if url is a valid configuration url. var configurationFile = ConfigurationFile(url: url) if let configurationFile, fileClient.fileExists(configurationFile.url) { return configurationFile } guard try await fileClient.isDirectory(url.cleanFilePath) else { throw ConfigurationClientError.invalidConfigurationDirectory(path: url.cleanFilePath) } // Check for toml file. let tomlUrl = url.appending(path: "\(ConfigurationClient.Constants.defaultFileNameWithoutExtension).toml") configurationFile = ConfigurationFile(url: tomlUrl) if let configurationFile, fileClient.fileExists(configurationFile.url) { return configurationFile } // Check for json file. let jsonUrl = url.appending(path: "\(ConfigurationClient.Constants.defaultFileNameWithoutExtension).json") configurationFile = ConfigurationFile(url: jsonUrl) if let configurationFile, fileClient.fileExists(configurationFile.url) { return configurationFile } // Couldn't find valid configuration file. return nil } enum ConfigurationClientError: Error { case configurationNotFound case invalidConfigurationDirectory(path: String) }