From 9b669f215cb41745cb422bf7be2f634c49f3bcb5 Mon Sep 17 00:00:00 2001 From: Prachi Gauriar Date: Sat, 28 Feb 2026 01:49:13 -0500 Subject: [PATCH] Simplify ConfigVariableReader - Eliminates ConfigValueReadable - Call ConfigReader functions that take default values directly from ConfigVariableReader - Update ConfigVariableReaderTests to be a bit simpler --- .../Core/ConfigValueReadable.swift | 574 ------------ .../Core/ConfigVariableReader.swift | 848 ++++++++++++++++-- .../Core/ConfigVariableMetadataTests.swift | 2 +- .../Core/ConfigVariableReaderTests.swift | 806 +++++++++-------- 4 files changed, 1192 insertions(+), 1038 deletions(-) delete mode 100644 Sources/DevConfiguration/Core/ConfigValueReadable.swift diff --git a/Sources/DevConfiguration/Core/ConfigValueReadable.swift b/Sources/DevConfiguration/Core/ConfigValueReadable.swift deleted file mode 100644 index f544652..0000000 --- a/Sources/DevConfiguration/Core/ConfigValueReadable.swift +++ /dev/null @@ -1,574 +0,0 @@ -// -// ConfigValueReadable.swift -// DevConfiguration -// -// Created by Duncan Lewis on 2/16/2026. -// - -import Configuration -import Foundation - -/// A type of that can be read by a ConfigReader. -/// -/// This protocol provides the bridge between `ConfigVariableReader` and `ConfigReader`, allowing generic -/// implementations that dispatch to the appropriate type-specific methods. -public protocol ConfigValueReadable: Sendable { - /// Gets the required value for the specified key from the reader. - /// - /// - Parameters: - /// - key: The configuration key. - /// - reader: The configuration reader. - /// - isSecret: Whether the value is secret. - /// - fileID: The source file identifier. - /// - line: The source line number. - /// - Returns: The configuration value. - /// - Throws: An error if the value cannot be retrieved. - static func requiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> Self - - /// Asynchronously fetches the required value for the specified key from the reader. - /// - /// - Parameters: - /// - key: The configuration key. - /// - reader: The configuration reader. - /// - isSecret: Whether the value is secret. - /// - fileID: The source file identifier. - /// - line: The source line number. - /// - Returns: The configuration value. - /// - Throws: An error if the value cannot be fetched. - static func fetchRequiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> Self - - /// Watches for updates to the value for the specified key, using a default if the key is not found. - /// - /// - Parameters: - /// - key: The configuration key. - /// - reader: The configuration reader. - /// - isSecret: Whether the value is secret. - /// - defaultValue: The default value to use if the key is not found. - /// - fileID: The source file identifier. - /// - line: The source line number. - /// - updatesHandler: A closure that handles the async sequence of updates. - /// - Returns: The result produced by the handler. - static func watchValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: Self, - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return - ) async throws -> Return - - - /// Gets the required array value for the specified key from the reader. - /// - /// - Parameters: - /// - key: The configuration key. - /// - reader: The configuration reader. - /// - isSecret: Whether the value is secret. - /// - fileID: The source file identifier. - /// - line: The source line number. - /// - Returns: The configuration array value. - /// - Throws: An error if the value cannot be retrieved. - static func requiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> [Self] - - /// Asynchronously fetches the required array value for the specified key from the reader. - /// - /// - Parameters: - /// - key: The configuration key. - /// - reader: The configuration reader. - /// - isSecret: Whether the value is secret. - /// - fileID: The source file identifier. - /// - line: The source line number. - /// - Returns: The configuration array value. - /// - Throws: An error if the value cannot be fetched. - static func fetchRequiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> [Self] - - /// Watches for updates to the array value for the specified key, using a default if the key is not found. - /// - /// - Parameters: - /// - key: The configuration key. - /// - reader: The configuration reader. - /// - isSecret: Whether the value is secret. - /// - defaultValue: The default value to use if the key is not found. - /// - fileID: The source file identifier. - /// - line: The source line number. - /// - updatesHandler: A closure that handles the async sequence of updates. - /// - Returns: The result produced by the handler. - static func watchArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: [Self], - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Self], Never>) async throws -> Return - ) async throws -> Return where Return: ~Copyable -} - - -// MARK: - Bool Conformance - -extension Bool: ConfigValueReadable { - public static func requiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> Bool { - try reader.requiredBool(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> Bool { - try await reader.fetchRequiredBool(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: Bool, - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchBool( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } - - - public static func requiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> [Bool] { - try reader.requiredBoolArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> [Bool] { - try await reader.fetchRequiredBoolArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: [Bool], - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Bool], Never>) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchBoolArray( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } -} - - -// MARK: - Data Conformance - -extension Data: ConfigValueReadable { - public static func requiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> Data { - Data(try reader.requiredBytes(forKey: key, isSecret: isSecret, fileID: fileID, line: line)) - } - - - public static func fetchRequiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> Data { - Data(try await reader.fetchRequiredBytes(forKey: key, isSecret: isSecret, fileID: fileID, line: line)) - } - - - public static func watchValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: Data, - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchBytes( - forKey: key, - isSecret: isSecret, - default: Array(defaultValue), - fileID: fileID, - line: line - ) { (updates) in - try await updatesHandler(ConfigUpdatesAsyncSequence(updates.map { Data($0) })) - } - } - - - public static func requiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> [Data] { - try reader.requiredByteChunkArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - .map { Data($0) } - } - - - public static func fetchRequiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> [Data] { - try await reader.fetchRequiredByteChunkArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - .map { Data($0) } - } - - - public static func watchArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: [Data], - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Data], Never>) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchByteChunkArray( - forKey: key, - isSecret: isSecret, - default: defaultValue.map { Array($0) }, - fileID: fileID, - line: line - ) { (updates) in - try await updatesHandler(ConfigUpdatesAsyncSequence(updates.map { $0.map { Data($0) } })) - } - } -} - - -// MARK: - Float64 Conformance - -extension Float64: ConfigValueReadable { - public static func requiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> Float64 { - try reader.requiredDouble(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> Float64 { - try await reader.fetchRequiredDouble(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: Float64, - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchDouble( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } - - - public static func requiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> [Float64] { - try reader.requiredDoubleArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> [Float64] { - try await reader.fetchRequiredDoubleArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: [Float64], - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Float64], Never>) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchDoubleArray( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } -} - - -// MARK: - Int Conformance - -extension Int: ConfigValueReadable { - public static func requiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> Int { - try reader.requiredInt(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> Int { - try await reader.fetchRequiredInt(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: Int, - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchInt( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } - - - public static func requiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> [Int] { - try reader.requiredIntArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> [Int] { - try await reader.fetchRequiredIntArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: [Int], - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Int], Never>) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchIntArray( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } -} - - -// MARK: - String Conformance - -extension String: ConfigValueReadable { - public static func requiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> String { - try reader.requiredString(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> String { - try await reader.fetchRequiredString(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: String, - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchString( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } - - - public static func requiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) throws -> [String] { - try reader.requiredStringArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func fetchRequiredArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - fileID: String, - line: UInt - ) async throws -> [String] { - try await reader.fetchRequiredStringArray(forKey: key, isSecret: isSecret, fileID: fileID, line: line) - } - - - public static func watchArrayValue( - forKey key: ConfigKey, - reader: ConfigReader, - isSecret: Bool, - default defaultValue: [String], - fileID: String, - line: UInt, - updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[String], Never>) async throws -> Return - ) async throws -> Return where Return: ~Copyable { - try await reader.watchStringArray( - forKey: key, - isSecret: isSecret, - default: defaultValue, - fileID: fileID, - line: line, - updatesHandler: updatesHandler - ) - } -} diff --git a/Sources/DevConfiguration/Core/ConfigVariableReader.swift b/Sources/DevConfiguration/Core/ConfigVariableReader.swift index de95bd4..57887db 100644 --- a/Sources/DevConfiguration/Core/ConfigVariableReader.swift +++ b/Sources/DevConfiguration/Core/ConfigVariableReader.swift @@ -84,57 +84,223 @@ public struct ConfigVariableReader { // MARK: - Get extension ConfigVariableReader { - /// Gets the value for the specified `ConfigVariable`. + /// Gets the value for the specified `Bool` config variable. /// /// - Parameters: /// - variable: The variable to get a value for. /// - fileID: The source file identifier for access reporting. /// - line: The source line number for access reporting. /// - Returns: The configuration value of the variable, or the default value if resolution fails. - public func value( - for variable: ConfigVariable, + public func value( + for variable: ConfigVariable, fileID: String = #fileID, line: UInt = #line - ) -> Value - where Value: ConfigValueReadable { - do { - return try Value.requiredValue( - forKey: variable.key, - reader: reader, - isSecret: variable.isSecret, - fileID: fileID, - line: line - ) - } catch { - return variable.defaultValue - } + ) -> Bool { + return reader.bool( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `[Bool]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable<[Bool]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [Bool] { + return reader.boolArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `Float64` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> Float64 { + return reader.double( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `[Float64]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable<[Float64]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [Float64] { + return reader.doubleArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `Int` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> Int { + return reader.int( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `[Int]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable<[Int]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [Int] { + return reader.intArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `String` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> String { + return reader.string( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `[String]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable<[String]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [String] { + return reader.stringArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Gets the value for the specified `[UInt8]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func value( + for variable: ConfigVariable<[UInt8]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [UInt8] { + return reader.bytes( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) } - /// Gets the value for the specified array `ConfigVariable`. + /// Gets the value for the specified `[[UInt8]]` config variable. /// /// - Parameters: - /// - variable: The variable to get an array value for. + /// - variable: The variable to get a value for. /// - fileID: The source file identifier for access reporting. /// - line: The source line number for access reporting. /// - Returns: The configuration value of the variable, or the default value if resolution fails. - public func value( - for variable: ConfigVariable<[Element]>, + public func value( + for variable: ConfigVariable<[[UInt8]]>, fileID: String = #fileID, line: UInt = #line - ) -> [Element] - where Element: ConfigValueReadable { - do { - return try Element.requiredArrayValue( - forKey: variable.key, - reader: reader, - isSecret: variable.isSecret, - fileID: fileID, - line: line - ) - } catch { - return variable.defaultValue - } + ) -> [[UInt8]] { + return reader.byteChunkArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) } } @@ -142,36 +308,162 @@ extension ConfigVariableReader { // MARK: - Subscript Get extension ConfigVariableReader { - /// Gets the value for the specified `ConfigVariable`. + /// Gets the value for the specified `Bool` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> Bool { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `[Bool]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable<[Bool]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [Bool] { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `Float64` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> Float64 { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `[Float64]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable<[Float64]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [Float64] { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `Int` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> Int { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `[Int]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable<[Int]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [Int] { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `String` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) -> String { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `[String]` config variable. + /// + /// - Parameters: + /// - variable: The variable to get a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public subscript( + variable: ConfigVariable<[String]>, + fileID: String = #fileID, + line: UInt = #line + ) -> [String] { + value(for: variable, fileID: fileID, line: line) + } + + + /// Gets the value for the specified `[UInt8]` config variable. /// /// - Parameters: /// - variable: The variable to get a value for. /// - fileID: The source file identifier for access reporting. /// - line: The source line number for access reporting. /// - Returns: The configuration value of the variable, or the default value if resolution fails. - public subscript( - variable: ConfigVariable, + public subscript( + variable: ConfigVariable<[UInt8]>, fileID: String = #fileID, line: UInt = #line - ) -> Value - where Value: ConfigValueReadable { + ) -> [UInt8] { value(for: variable, fileID: fileID, line: line) } - /// Gets the value for the specified array `ConfigVariable`. + /// Gets the value for the specified `[[UInt8]]` config variable. /// /// - Parameters: - /// - variable: The variable to get an array value for. + /// - variable: The variable to get a value for. /// - fileID: The source file identifier for access reporting. /// - line: The source line number for access reporting. /// - Returns: The configuration value of the variable, or the default value if resolution fails. - public subscript( - variable: ConfigVariable<[Element]>, + public subscript( + variable: ConfigVariable<[[UInt8]]>, fileID: String = #fileID, line: UInt = #line - ) -> [Element] - where Element: ConfigValueReadable { + ) -> [[UInt8]] { value(for: variable, fileID: fileID, line: line) } } @@ -180,65 +472,431 @@ extension ConfigVariableReader { // MARK: - Fetch extension ConfigVariableReader { - /// Asynchronously fetches the value for the specified `ConfigVariable`. + /// Asynchronously fetches the value for the specified `Bool` config variable. /// /// - Parameters: /// - variable: The variable to fetch a value for. /// - fileID: The source file identifier for access reporting. /// - line: The source line number for access reporting. /// - Returns: The configuration value of the variable, or the default value if resolution fails. - public func fetchValue( - for variable: ConfigVariable, + public func fetchValue( + for variable: ConfigVariable, fileID: String = #fileID, line: UInt = #line - ) async -> Value - where Value: ConfigValueReadable { - do { - return try await Value.fetchRequiredValue( - forKey: variable.key, - reader: reader, - isSecret: variable.isSecret, - fileID: fileID, - line: line - ) - } catch { - return variable.defaultValue - } + ) async throws -> Bool { + return try await reader.fetchBool( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) } - /// Asynchronously fetches the value for the specified array `ConfigVariable`. + /// Asynchronously fetches the value for the specified `[Bool]` config variable. /// /// - Parameters: - /// - variable: The variable to fetch an array value for. + /// - variable: The variable to fetch a value for. /// - fileID: The source file identifier for access reporting. /// - line: The source line number for access reporting. /// - Returns: The configuration value of the variable, or the default value if resolution fails. - public func fetchValue( - for variable: ConfigVariable<[Element]>, + public func fetchValue( + for variable: ConfigVariable<[Bool]>, fileID: String = #fileID, line: UInt = #line - ) async -> [Element] - where Element: ConfigValueReadable { - do { - return try await Element.fetchRequiredArrayValue( - forKey: variable.key, - reader: reader, - isSecret: variable.isSecret, - fileID: fileID, - line: line - ) - } catch { - return variable.defaultValue - } + ) async throws -> [Bool] { + return try await reader.fetchBoolArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) } -} -// MARK: - Watch - -extension ConfigVariableReader { - /// Watches for updates to the value for the specified `ConfigVariable`. + /// Asynchronously fetches the value for the specified `Float64` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> Float64 { + return try await reader.fetchDouble( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `[Float64]` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable<[Float64]>, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> [Float64] { + return try await reader.fetchDoubleArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `Int` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> Int { + return try await reader.fetchInt( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `[Int]` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable<[Int]>, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> [Int] { + return try await reader.fetchIntArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `String` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> String { + return try await reader.fetchString( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `[String]` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable<[String]>, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> [String] { + return try await reader.fetchStringArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `[UInt8]` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable<[UInt8]>, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> [UInt8] { + return try await reader.fetchBytes( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } + + + /// Asynchronously fetches the value for the specified `[[UInt8]]` config variable. + /// + /// - Parameters: + /// - variable: The variable to fetch a value for. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - Returns: The configuration value of the variable, or the default value if resolution fails. + public func fetchValue( + for variable: ConfigVariable<[[UInt8]]>, + fileID: String = #fileID, + line: UInt = #line + ) async throws -> [[UInt8]] { + return try await reader.fetchByteChunkArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line + ) + } +} + + +// MARK: - Watch + +extension ConfigVariableReader { + /// Watches for updates to the value for the specified `Bool` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchBool( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `[Bool]` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable<[Bool]>, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Bool], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchBoolArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `Float64` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchDouble( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `[Float64]` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable<[Float64]>, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Float64], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchDoubleArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `Int` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchInt( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `[Int]` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable<[Int]>, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[Int], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchIntArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `String` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchString( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `[String]` config variable. + /// + /// - Parameters: + /// - variable: The variable to watch for updates. + /// - fileID: The source file identifier for access reporting. + /// - line: The source line number for access reporting. + /// - updatesHandler: A closure that handles an async sequence of updates to the value. + /// - Returns: The result produced by the handler. + public func watchValue( + for variable: ConfigVariable<[String]>, + fileID: String = #fileID, + line: UInt = #line, + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[String], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchStringArray( + forKey: variable.key, + isSecret: variable.isSecret, + default: variable.defaultValue, + fileID: fileID, + line: line, + updatesHandler: updatesHandler + ) + } + + + /// Watches for updates to the value for the specified `[UInt8]` config variable. /// /// - Parameters: /// - variable: The variable to watch for updates. @@ -246,16 +904,14 @@ extension ConfigVariableReader { /// - line: The source line number for access reporting. /// - updatesHandler: A closure that handles an async sequence of updates to the value. /// - Returns: The result produced by the handler. - public func watchValue( - for variable: ConfigVariable, + public func watchValue( + for variable: ConfigVariable<[UInt8]>, fileID: String = #fileID, line: UInt = #line, - updatesHandler: (_ updates: any AsyncSequence) async throws -> Return - ) async throws -> Return - where Value: ConfigValueReadable, Return: ~Copyable { - try await Value.watchValue( + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[UInt8], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchBytes( forKey: variable.key, - reader: reader, isSecret: variable.isSecret, default: variable.defaultValue, fileID: fileID, @@ -265,7 +921,7 @@ extension ConfigVariableReader { } - /// Watches for updates to the value for the specified array `ConfigVariable`. + /// Watches for updates to the value for the specified `[[UInt8]]` config variable. /// /// - Parameters: /// - variable: The variable to watch for updates. @@ -273,16 +929,14 @@ extension ConfigVariableReader { /// - line: The source line number for access reporting. /// - updatesHandler: A closure that handles an async sequence of updates to the value. /// - Returns: The result produced by the handler. - public func watchValue( - for variable: ConfigVariable<[Element]>, + public func watchValue( + for variable: ConfigVariable<[[UInt8]]>, fileID: String = #fileID, line: UInt = #line, - updatesHandler: (_ updates: any AsyncSequence<[Element], Never>) async throws -> Return - ) async throws -> Return - where Element: ConfigValueReadable, Return: ~Copyable { - try await Element.watchArrayValue( + updatesHandler: (_ updates: ConfigUpdatesAsyncSequence<[[UInt8]], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchByteChunkArray( forKey: variable.key, - reader: reader, isSecret: variable.isSecret, default: variable.defaultValue, fileID: fileID, diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableMetadataTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableMetadataTests.swift index d928b17..070d601 100644 --- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableMetadataTests.swift +++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableMetadataTests.swift @@ -105,7 +105,7 @@ struct ConfigVariableMetadataTests: RandomValueGenerating { @Test mutating func displayTextForOptionalRawRepresentableReturnsRawValueWhenNotNil() { let value = randomCase(of: MetadataEnum.self)! - #expect(OptionalEnumMetadataKey.displayText(for: .valueB) == value.rawValue) + #expect(OptionalEnumMetadataKey.displayText(for: value) == value.rawValue) } diff --git a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift index ed44010..d5e1347 100644 --- a/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift +++ b/Tests/DevConfigurationTests/Unit Tests/Core/ConfigVariableReaderTests.swift @@ -8,7 +8,6 @@ import Configuration import DevFoundation import DevTesting -import Foundation import Testing @testable import DevConfiguration @@ -43,14 +42,14 @@ struct ConfigVariableReaderTests: RandomValueGenerating { @Test - mutating func fetchValueForBoolReturnsProviderValue() async { - await testFetchValueReturnsProviderValue(using: BoolTestHelper()) + mutating func fetchValueForBoolReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: BoolTestHelper()) } @Test - mutating func fetchValueForBoolReturnsDefaultWhenKeyNotFound() async { - await testFetchValueReturnsDefaultWhenKeyNotFound(using: BoolTestHelper()) + mutating func fetchValueForBoolReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: BoolTestHelper()) } @@ -66,41 +65,41 @@ struct ConfigVariableReaderTests: RandomValueGenerating { } - // MARK: - Data tests + // MARK: - [Bool] tests @Test - mutating func valueForDataReturnsProviderValue() { - testValueReturnsProviderValue(using: DataTestHelper()) + mutating func valueForBoolArrayReturnsProviderValue() { + testValueReturnsProviderValue(using: BoolArrayTestHelper()) } @Test - mutating func valueForDataReturnsDefaultWhenKeyNotFound() { - testValueReturnsDefaultWhenKeyNotFound(using: DataTestHelper()) + mutating func valueForBoolArrayReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: BoolArrayTestHelper()) } @Test - mutating func fetchValueForDataReturnsProviderValue() async { - await testFetchValueReturnsProviderValue(using: DataTestHelper()) + mutating func fetchValueForBoolArrayReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: BoolArrayTestHelper()) } @Test - mutating func fetchValueForDataReturnsDefaultWhenKeyNotFound() async { - await testFetchValueReturnsDefaultWhenKeyNotFound(using: DataTestHelper()) + mutating func fetchValueForBoolArrayReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: BoolArrayTestHelper()) } @Test - mutating func watchValueForDataReceivesUpdates() async throws { - try await testWatchValueReceivesUpdates(using: DataTestHelper()) + mutating func watchValueForBoolArrayReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: BoolArrayTestHelper()) } @Test - mutating func subscriptDataReturnsProviderValue() { - testSubscriptReturnsProviderValue(using: DataTestHelper()) + mutating func subscriptBoolArrayReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: BoolArrayTestHelper()) } @@ -119,14 +118,14 @@ struct ConfigVariableReaderTests: RandomValueGenerating { @Test - mutating func fetchValueForFloat64ReturnsProviderValue() async { - await testFetchValueReturnsProviderValue(using: Float64TestHelper()) + mutating func fetchValueForFloat64ReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: Float64TestHelper()) } @Test - mutating func fetchValueForFloat64ReturnsDefaultWhenKeyNotFound() async { - await testFetchValueReturnsDefaultWhenKeyNotFound(using: Float64TestHelper()) + mutating func fetchValueForFloat64ReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: Float64TestHelper()) } @@ -142,276 +141,276 @@ struct ConfigVariableReaderTests: RandomValueGenerating { } - // MARK: - Int tests + // MARK: - [Float64] tests @Test - mutating func valueForIntReturnsProviderValue() { - testValueReturnsProviderValue(using: IntTestHelper()) + mutating func valueForFloat64ArrayReturnsProviderValue() { + testValueReturnsProviderValue(using: Float64ArrayTestHelper()) } @Test - mutating func valueForIntReturnsDefaultWhenKeyNotFound() { - testValueReturnsDefaultWhenKeyNotFound(using: IntTestHelper()) + mutating func valueForFloat64ArrayReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: Float64ArrayTestHelper()) } @Test - mutating func fetchValueForIntReturnsProviderValue() async { - await testFetchValueReturnsProviderValue(using: IntTestHelper()) + mutating func fetchValueForFloat64ArrayReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: Float64ArrayTestHelper()) } @Test - mutating func fetchValueForIntReturnsDefaultWhenKeyNotFound() async { - await testFetchValueReturnsDefaultWhenKeyNotFound(using: IntTestHelper()) + mutating func fetchValueForFloat64ArrayReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: Float64ArrayTestHelper()) } @Test - mutating func watchValueForIntReceivesUpdates() async throws { - try await testWatchValueReceivesUpdates(using: IntTestHelper()) + mutating func watchValueForFloat64ArrayReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: Float64ArrayTestHelper()) } @Test - mutating func subscriptIntReturnsProviderValue() { - testSubscriptReturnsProviderValue(using: IntTestHelper()) + mutating func subscriptFloat64ArrayReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: Float64ArrayTestHelper()) } - // MARK: - String tests + // MARK: - Int tests @Test - mutating func valueForStringReturnsProviderValue() { - testValueReturnsProviderValue(using: StringTestHelper()) + mutating func valueForIntReturnsProviderValue() { + testValueReturnsProviderValue(using: IntTestHelper()) } @Test - mutating func valueForStringReturnsDefaultWhenKeyNotFound() { - testValueReturnsDefaultWhenKeyNotFound(using: StringTestHelper()) + mutating func valueForIntReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: IntTestHelper()) } @Test - mutating func fetchValueForStringReturnsProviderValue() async { - await testFetchValueReturnsProviderValue(using: StringTestHelper()) + mutating func fetchValueForIntReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: IntTestHelper()) } @Test - mutating func fetchValueForStringReturnsDefaultWhenKeyNotFound() async { - await testFetchValueReturnsDefaultWhenKeyNotFound(using: StringTestHelper()) + mutating func fetchValueForIntReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: IntTestHelper()) } @Test - mutating func watchValueForStringReceivesUpdates() async throws { - try await testWatchValueReceivesUpdates(using: StringTestHelper()) + mutating func watchValueForIntReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: IntTestHelper()) } @Test - mutating func subscriptStringReturnsProviderValue() { - testSubscriptReturnsProviderValue(using: StringTestHelper()) + mutating func subscriptIntReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: IntTestHelper()) } - // MARK: - [Bool] tests + // MARK: - [Int] tests @Test - mutating func valueForBoolArrayReturnsProviderValue() { - testArrayValueReturnsProviderValue(using: BoolArrayTestHelper()) + mutating func valueForIntArrayReturnsProviderValue() { + testValueReturnsProviderValue(using: IntArrayTestHelper()) } @Test - mutating func valueForBoolArrayReturnsDefaultWhenKeyNotFound() { - testArrayValueReturnsDefaultWhenKeyNotFound(using: BoolArrayTestHelper()) + mutating func valueForIntArrayReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: IntArrayTestHelper()) } @Test - mutating func fetchValueForBoolArrayReturnsProviderValue() async { - await testArrayFetchValueReturnsProviderValue(using: BoolArrayTestHelper()) + mutating func fetchValueForIntArrayReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: IntArrayTestHelper()) } @Test - mutating func fetchValueForBoolArrayReturnsDefaultWhenKeyNotFound() async { - await testArrayFetchValueReturnsDefaultWhenKeyNotFound(using: BoolArrayTestHelper()) + mutating func fetchValueForIntArrayReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: IntArrayTestHelper()) } @Test - mutating func watchValueForBoolArrayReceivesUpdates() async throws { - try await testArrayWatchValueReceivesUpdates(using: BoolArrayTestHelper()) + mutating func watchValueForIntArrayReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: IntArrayTestHelper()) } @Test - mutating func subscriptBoolArrayReturnsProviderValue() { - testArraySubscriptReturnsProviderValue(using: BoolArrayTestHelper()) + mutating func subscriptIntArrayReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: IntArrayTestHelper()) } - // MARK: - [Data] tests + // MARK: - String tests @Test - mutating func valueForDataArrayReturnsProviderValue() { - testArrayValueReturnsProviderValue(using: DataArrayTestHelper()) + mutating func valueForStringReturnsProviderValue() { + testValueReturnsProviderValue(using: StringTestHelper()) } @Test - mutating func valueForDataArrayReturnsDefaultWhenKeyNotFound() { - testArrayValueReturnsDefaultWhenKeyNotFound(using: DataArrayTestHelper()) + mutating func valueForStringReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: StringTestHelper()) } @Test - mutating func fetchValueForDataArrayReturnsProviderValue() async { - await testArrayFetchValueReturnsProviderValue(using: DataArrayTestHelper()) + mutating func fetchValueForStringReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: StringTestHelper()) } @Test - mutating func fetchValueForDataArrayReturnsDefaultWhenKeyNotFound() async { - await testArrayFetchValueReturnsDefaultWhenKeyNotFound(using: DataArrayTestHelper()) + mutating func fetchValueForStringReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: StringTestHelper()) } @Test - mutating func watchValueForDataArrayReceivesUpdates() async throws { - try await testArrayWatchValueReceivesUpdates(using: DataArrayTestHelper()) + mutating func watchValueForStringReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: StringTestHelper()) } @Test - mutating func subscriptDataArrayReturnsProviderValue() { - testArraySubscriptReturnsProviderValue(using: DataArrayTestHelper()) + mutating func subscriptStringReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: StringTestHelper()) } - // MARK: - [Float64] tests + // MARK: - [String] tests @Test - mutating func valueForFloat64ArrayReturnsProviderValue() { - testArrayValueReturnsProviderValue(using: Float64ArrayTestHelper()) + mutating func valueForStringArrayReturnsProviderValue() { + testValueReturnsProviderValue(using: StringArrayTestHelper()) } @Test - mutating func valueForFloat64ArrayReturnsDefaultWhenKeyNotFound() { - testArrayValueReturnsDefaultWhenKeyNotFound(using: Float64ArrayTestHelper()) + mutating func valueForStringArrayReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: StringArrayTestHelper()) } @Test - mutating func fetchValueForFloat64ArrayReturnsProviderValue() async { - await testArrayFetchValueReturnsProviderValue(using: Float64ArrayTestHelper()) + mutating func fetchValueForStringArrayReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: StringArrayTestHelper()) } @Test - mutating func fetchValueForFloat64ArrayReturnsDefaultWhenKeyNotFound() async { - await testArrayFetchValueReturnsDefaultWhenKeyNotFound(using: Float64ArrayTestHelper()) + mutating func fetchValueForStringArrayReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: StringArrayTestHelper()) } @Test - mutating func watchValueForFloat64ArrayReceivesUpdates() async throws { - try await testArrayWatchValueReceivesUpdates(using: Float64ArrayTestHelper()) + mutating func watchValueForStringArrayReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: StringArrayTestHelper()) } @Test - mutating func subscriptFloat64ArrayReturnsProviderValue() { - testArraySubscriptReturnsProviderValue(using: Float64ArrayTestHelper()) + mutating func subscriptStringArrayReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: StringArrayTestHelper()) } - // MARK: - [Int] tests + // MARK: - [UInt8] tests @Test - mutating func valueForIntArrayReturnsProviderValue() { - testArrayValueReturnsProviderValue(using: IntArrayTestHelper()) + mutating func valueForBytesReturnsProviderValue() { + testValueReturnsProviderValue(using: BytesTestHelper()) } @Test - mutating func valueForIntArrayReturnsDefaultWhenKeyNotFound() { - testArrayValueReturnsDefaultWhenKeyNotFound(using: IntArrayTestHelper()) + mutating func valueForBytesReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: BytesTestHelper()) } @Test - mutating func fetchValueForIntArrayReturnsProviderValue() async { - await testArrayFetchValueReturnsProviderValue(using: IntArrayTestHelper()) + mutating func fetchValueForBytesReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: BytesTestHelper()) } + @Test - mutating func fetchValueForIntArrayReturnsDefaultWhenKeyNotFound() async { - await testArrayFetchValueReturnsDefaultWhenKeyNotFound(using: IntArrayTestHelper()) + mutating func fetchValueForBytesReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: BytesTestHelper()) } @Test - mutating func watchValueForIntArrayReceivesUpdates() async throws { - try await testArrayWatchValueReceivesUpdates(using: IntArrayTestHelper()) + mutating func watchValueForBytesReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: BytesTestHelper()) } @Test - mutating func subscriptIntArrayReturnsProviderValue() { - testArraySubscriptReturnsProviderValue(using: IntArrayTestHelper()) + mutating func subscriptBytesReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: BytesTestHelper()) } - // MARK: - [String] tests + // MARK: - [[UInt8]] tests @Test - mutating func valueForStringArrayReturnsProviderValue() { - testArrayValueReturnsProviderValue(using: StringArrayTestHelper()) + mutating func valueForByteChunkArrayReturnsProviderValue() { + testValueReturnsProviderValue(using: ByteChunkArrayTestHelper()) } @Test - mutating func valueForStringArrayReturnsDefaultWhenKeyNotFound() { - testArrayValueReturnsDefaultWhenKeyNotFound(using: StringArrayTestHelper()) + mutating func valueForByteChunkArrayReturnsDefaultWhenKeyNotFound() { + testValueReturnsDefaultWhenKeyNotFound(using: ByteChunkArrayTestHelper()) } @Test - mutating func fetchValueForStringArrayReturnsProviderValue() async { - await testArrayFetchValueReturnsProviderValue(using: StringArrayTestHelper()) + mutating func fetchValueForByteChunkArrayReturnsProviderValue() async throws { + try await testFetchValueReturnsProviderValue(using: ByteChunkArrayTestHelper()) } + @Test - mutating func fetchValueForStringArrayReturnsDefaultWhenKeyNotFound() async { - await testArrayFetchValueReturnsDefaultWhenKeyNotFound(using: StringArrayTestHelper()) + mutating func fetchValueForByteChunkArrayReturnsDefaultWhenKeyNotFound() async throws { + try await testFetchValueReturnsDefaultWhenKeyNotFound(using: ByteChunkArrayTestHelper()) } @Test - mutating func watchValueForStringArrayReceivesUpdates() async throws { - try await testArrayWatchValueReceivesUpdates(using: StringArrayTestHelper()) + mutating func watchValueForByteChunkArrayReceivesUpdates() async throws { + try await testWatchValueReceivesUpdates(using: ByteChunkArrayTestHelper()) } @Test - mutating func subscriptStringArrayReturnsProviderValue() { - testArraySubscriptReturnsProviderValue(using: StringArrayTestHelper()) + mutating func subscriptByteChunkArrayReturnsProviderValue() { + testSubscriptReturnsProviderValue(using: ByteChunkArrayTestHelper()) } // MARK: - Generic Test Helpers /// Tests that `value(for:)` returns the provider value when the key exists. - mutating func testValueReturnsProviderValue( - using helper: Helper - ) where Helper: ConfigValueTestHelper { + mutating func testValueReturnsProviderValue(using helper: Helper) { // set up let key = randomConfigKey() let expectedValue = helper.randomValue(using: &randomNumberGenerator) @@ -426,7 +425,7 @@ struct ConfigVariableReaderTests: RandomValueGenerating { ) // exercise - let result = reader.value(for: variable) + let result = helper.getValue(from: reader, for: variable) // expect #expect(result == expectedValue) @@ -434,16 +433,14 @@ struct ConfigVariableReaderTests: RandomValueGenerating { /// Tests that `value(for:)` returns the default value when the key is not found. - mutating func testValueReturnsDefaultWhenKeyNotFound( - using helper: Helper - ) where Helper: ConfigValueTestHelper { + mutating func testValueReturnsDefaultWhenKeyNotFound(using helper: Helper) { // set up let key = randomConfigKey() let defaultValue = helper.randomValue(using: &randomNumberGenerator) let variable = ConfigVariable(key: key, defaultValue: defaultValue) // exercise - let result = reader.value(for: variable) + let result = helper.getValue(from: reader, for: variable) // expect #expect(result == defaultValue) @@ -451,9 +448,9 @@ struct ConfigVariableReaderTests: RandomValueGenerating { /// Tests that `fetchValue(for:)` returns the provider value when the key exists. - mutating func testFetchValueReturnsProviderValue( + mutating func testFetchValueReturnsProviderValue( using helper: Helper - ) async where Helper: ConfigValueTestHelper { + ) async throws { // set up let key = randomConfigKey() let expectedValue = helper.randomValue(using: &randomNumberGenerator) @@ -468,7 +465,7 @@ struct ConfigVariableReaderTests: RandomValueGenerating { ) // exercise - let result = await reader.fetchValue(for: variable) + let result = try await helper.fetchValue(from: reader, for: variable) // expect #expect(result == expectedValue) @@ -476,26 +473,26 @@ struct ConfigVariableReaderTests: RandomValueGenerating { /// Tests that `fetchValue(for:)` returns the default value when the key is not found. - mutating func testFetchValueReturnsDefaultWhenKeyNotFound( + mutating func testFetchValueReturnsDefaultWhenKeyNotFound( using helper: Helper - ) async where Helper: ConfigValueTestHelper { + ) async throws { // set up let key = randomConfigKey() let defaultValue = helper.randomValue(using: &randomNumberGenerator) let variable = ConfigVariable(key: key, defaultValue: defaultValue) // exercise - let result = await reader.fetchValue(for: variable) + let result = try await helper.fetchValue(from: reader, for: variable) // expect #expect(result == defaultValue) } - /// Tests that `watchValue(for:)` receives updates when the provider value changes. - mutating func testWatchValueReceivesUpdates( + /// Tests that `watchValue(for:updatesHandler:)` receives updates when the provider value changes. + mutating func testWatchValueReceivesUpdates( using helper: Helper - ) async throws where Helper: ConfigValueTestHelper { + ) async throws { // set up let key = randomConfigKey() let initialValue = helper.randomValue(using: &randomNumberGenerator) @@ -511,7 +508,7 @@ struct ConfigVariableReaderTests: RandomValueGenerating { ) // exercise and expect - try await reader.watchValue(for: variable) { (updates) in + try await helper.watchValue(from: reader, for: variable) { (updates) in var iterator = updates.makeAsyncIterator() // first value should be initial @@ -535,9 +532,7 @@ struct ConfigVariableReaderTests: RandomValueGenerating { /// Tests that subscript returns the provider value when the key exists. - mutating func testSubscriptReturnsProviderValue( - using helper: Helper - ) where Helper: ConfigValueTestHelper { + mutating func testSubscriptReturnsProviderValue(using helper: Helper) { // set up let key = randomConfigKey() let expectedValue = helper.randomValue(using: &randomNumberGenerator) @@ -552,160 +547,7 @@ struct ConfigVariableReaderTests: RandomValueGenerating { ) // exercise - let result = reader[variable] - - // expect - #expect(result == expectedValue) - } - - - // MARK: - Generic Array Test Helpers - - /// Tests that `value(for:)` returns the provider value when the key exists for array types. - mutating func testArrayValueReturnsProviderValue( - using helper: Helper - ) where Helper: ConfigArrayValueTestHelper { - // set up - let key = randomConfigKey() - let expectedValue = helper.randomValue(using: &randomNumberGenerator) - let defaultValue = helper.differentValue(from: expectedValue, using: &randomNumberGenerator) - let variable = ConfigVariable<[Helper.Element]>(key: key, defaultValue: defaultValue) - provider.setValue( - .init( - helper.configContent(for: expectedValue), - isSecret: randomBool() - ), - forKey: .init(key) - ) - - // exercise - let result = reader.value(for: variable) - - // expect - #expect(result == expectedValue) - } - - - /// Tests that `value(for:)` returns the default value when the key is not found for array types. - mutating func testArrayValueReturnsDefaultWhenKeyNotFound( - using helper: Helper - ) where Helper: ConfigArrayValueTestHelper { - // set up - let key = randomConfigKey() - let defaultValue = helper.randomValue(using: &randomNumberGenerator) - let variable = ConfigVariable<[Helper.Element]>(key: key, defaultValue: defaultValue) - - // exercise - let result = reader.value(for: variable) - - // expect - #expect(result == defaultValue) - } - - - /// Tests that `fetchValue(for:)` returns the provider value when the key exists for array types. - mutating func testArrayFetchValueReturnsProviderValue( - using helper: Helper - ) async where Helper: ConfigArrayValueTestHelper { - // set up - let key = randomConfigKey() - let expectedValue = helper.randomValue(using: &randomNumberGenerator) - let defaultValue = helper.differentValue(from: expectedValue, using: &randomNumberGenerator) - let variable = ConfigVariable<[Helper.Element]>(key: key, defaultValue: defaultValue) - provider.setValue( - .init( - helper.configContent(for: expectedValue), - isSecret: randomBool() - ), - forKey: .init(key) - ) - - // exercise - let result = await reader.fetchValue(for: variable) - - // expect - #expect(result == expectedValue) - } - - - /// Tests that `fetchValue(for:)` returns the default value when the key is not found for array types. - mutating func testArrayFetchValueReturnsDefaultWhenKeyNotFound( - using helper: Helper - ) async where Helper: ConfigArrayValueTestHelper { - // set up - let key = randomConfigKey() - let defaultValue = helper.randomValue(using: &randomNumberGenerator) - let variable = ConfigVariable<[Helper.Element]>(key: key, defaultValue: defaultValue) - - // exercise - let result = await reader.fetchValue(for: variable) - - // expect - #expect(result == defaultValue) - } - - - /// Tests that `watchValue(for:)` receives updates when the provider value changes for array types. - mutating func testArrayWatchValueReceivesUpdates( - using helper: Helper - ) async throws where Helper: ConfigArrayValueTestHelper { - // set up - let key = randomConfigKey() - let initialValue = helper.randomValue(using: &randomNumberGenerator) - let updatedValue = helper.differentValue(from: initialValue, using: &randomNumberGenerator) - let defaultValue = helper.differentValue(from: initialValue, using: &randomNumberGenerator) - let variable = ConfigVariable<[Helper.Element]>(key: key, defaultValue: defaultValue) - provider.setValue( - .init( - helper.configContent(for: initialValue), - isSecret: randomBool() - ), - forKey: .init(key) - ) - - // exercise and expect - try await reader.watchValue(for: variable) { (updates) in - var iterator = updates.makeAsyncIterator() - - // first value should be initial - let value1 = try await iterator.next() - #expect(value1 == initialValue) - - // update the provider - provider.setValue( - .init( - helper.configContent(for: updatedValue), - isSecret: randomBool() - ), - forKey: .init(key) - ) - - // next value should be updated - let value2 = try await iterator.next() - #expect(value2 == updatedValue) - } - } - - - /// Tests that subscript returns the provider value when the key exists for array types. - mutating func testArraySubscriptReturnsProviderValue( - using helper: Helper - ) where Helper: ConfigArrayValueTestHelper { - // set up - let key = randomConfigKey() - let expectedValue = helper.randomValue(using: &randomNumberGenerator) - let defaultValue = helper.differentValue(from: expectedValue, using: &randomNumberGenerator) - let variable = ConfigVariable<[Helper.Element]>(key: key, defaultValue: defaultValue) - provider.setValue( - .init( - helper.configContent(for: expectedValue), - isSecret: randomBool() - ), - forKey: .init(key) - ) - - // exercise - let result = reader[variable] + let result = helper.subscriptValue(from: reader, for: variable) // expect #expect(result == expectedValue) @@ -744,43 +586,19 @@ struct ConfigVariableReaderTests: RandomValueGenerating { #expect(postedEvent.key == AbsoluteConfigKey(variable.key)) #expect(postedEvent.value.content == .bool(expectedValue)) } - - - @Test - mutating func valuePostsAccessFailedEventWhenNotFound() async throws { - // set up - let observer = ContextualBusEventObserver(context: ()) - eventBus.addObserver(observer) - - let key = randomConfigKey() - let defaultValue = randomBool() - let variable = ConfigVariable(key: key, defaultValue: defaultValue) - - let (eventStream, continuation) = AsyncStream.makeStream() - observer.addHandler(for: ConfigVariableAccessFailedEvent.self) { (event, _) in - continuation.yield(event) - } - - // exercise - _ = reader.value(for: variable) - - // expect - let postedEvent = try #require(await eventStream.first { _ in true }) - #expect(postedEvent.key == AbsoluteConfigKey(variable.key)) - } } -// MARK: - ConfigValueTestHelper Protocol +// MARK: - ReaderTestHelper Protocol /// A protocol that abstracts the type-specific details needed to test `ConfigVariableReader` with different value /// types. /// -/// Conforming types provide the logic for generating random values, creating config content, and producing different -/// values for testing default value fallback behavior. -protocol ConfigValueTestHelper { +/// Each conforming type encapsulates random value generation, config content conversion, and reader interaction for a +/// specific value type. +protocol ReaderTestHelper { /// The configuration value type being tested. - associatedtype Value: ConfigValueReadable & Equatable + associatedtype Value: Equatable & Sendable /// Generates a random value of the associated type. func randomValue(using generator: inout some RandomNumberGenerator) -> Value @@ -790,33 +608,28 @@ protocol ConfigValueTestHelper { /// Converts the value to its corresponding `ConfigContent` representation. func configContent(for value: Value) -> ConfigContent -} + /// Gets the value from the reader using `value(for:)`. + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Value -// MARK: - ConfigArrayValueTestHelper Protocol + /// Fetches the value from the reader using `fetchValue(for:)`. + func fetchValue(from reader: ConfigVariableReader, for variable: ConfigVariable) async throws -> Value -/// A protocol that abstracts the type-specific details needed to test `ConfigVariableReader` with array value types. -/// -/// This is separate from `ConfigValueTestHelper` because array types have their element type conform to -/// `ConfigValueReadable`, not the array type itself. -protocol ConfigArrayValueTestHelper { - /// The element type of the array being tested. - associatedtype Element: ConfigValueReadable & Equatable + /// Watches the value from the reader using `watchValue(for:updatesHandler:)`. + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable, + updatesHandler: (ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return - /// Generates a random array value. - func randomValue(using generator: inout some RandomNumberGenerator) -> [Element] - - /// Returns an array that is different from the provided array. - func differentValue(from value: [Element], using generator: inout some RandomNumberGenerator) -> [Element] - - /// Converts the array to its corresponding `ConfigContent` representation. - func configContent(for value: [Element]) -> ConfigContent + /// Gets the value from the reader using the subscript. + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Value } // MARK: - BoolTestHelper -private struct BoolTestHelper: ConfigValueTestHelper { +private struct BoolTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> Bool { Bool.random(using: &generator) } @@ -830,32 +643,36 @@ private struct BoolTestHelper: ConfigValueTestHelper { func configContent(for value: Bool) -> ConfigContent { .bool(value) } -} -// MARK: - DataTestHelper + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Bool { + reader.value(for: variable) + } -private struct DataTestHelper: ConfigValueTestHelper { - func randomValue(using generator: inout some RandomNumberGenerator) -> Data { - let count = Int.random(in: 1 ... 32, using: &generator) - return Data.random(count: count, using: &generator) + + func fetchValue(from reader: ConfigVariableReader, for variable: ConfigVariable) async throws -> Bool { + try await reader.fetchValue(for: variable) } - func differentValue(from value: Data, using generator: inout some RandomNumberGenerator) -> Data { - value + randomValue(using: &generator) + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable, + updatesHandler: (ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) } - func configContent(for value: Data) -> ConfigContent { - .bytes(Array(value)) + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Bool { + reader[variable] } } // MARK: - Float64TestHelper -private struct Float64TestHelper: ConfigValueTestHelper { +private struct Float64TestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> Float64 { Float64.random(in: 1 ... 100_000, using: &generator) } @@ -869,12 +686,39 @@ private struct Float64TestHelper: ConfigValueTestHelper { func configContent(for value: Float64) -> ConfigContent { .double(value) } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Float64 { + reader.value(for: variable) + } + + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable + ) async throws -> Float64 { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable, + updatesHandler: (ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Float64 { + reader[variable] + } } // MARK: - IntTestHelper -private struct IntTestHelper: ConfigValueTestHelper { +private struct IntTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> Int { Int.random(in: 1 ... 100_000, using: &generator) } @@ -888,12 +732,36 @@ private struct IntTestHelper: ConfigValueTestHelper { func configContent(for value: Int) -> ConfigContent { .int(value) } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Int { + reader.value(for: variable) + } + + + func fetchValue(from reader: ConfigVariableReader, for variable: ConfigVariable) async throws -> Int { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable, + updatesHandler: (ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> Int { + reader[variable] + } } // MARK: - StringTestHelper -private struct StringTestHelper: ConfigValueTestHelper { +private struct StringTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> String { let count = Int.random(in: 5 ... 20, using: &generator) return String.randomAlphanumeric(count: count, using: &generator) @@ -908,12 +776,83 @@ private struct StringTestHelper: ConfigValueTestHelper { func configContent(for value: String) -> ConfigContent { .string(value) } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> String { + reader.value(for: variable) + } + + + func fetchValue(from reader: ConfigVariableReader, for variable: ConfigVariable) async throws -> String { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable, + updatesHandler: (ConfigUpdatesAsyncSequence) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable) -> String { + reader[variable] + } +} + + +// MARK: - BytesTestHelper + +private struct BytesTestHelper: ReaderTestHelper { + func randomValue(using generator: inout some RandomNumberGenerator) -> [UInt8] { + let count = Int.random(in: 1 ... 32, using: &generator) + return Array(count: count) { UInt8.random(in: .min ... .max, using: &generator) } + } + + + func differentValue(from value: [UInt8], using generator: inout some RandomNumberGenerator) -> [UInt8] { + value + randomValue(using: &generator) + } + + + func configContent(for value: [UInt8]) -> ConfigContent { + .bytes(value) + } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[UInt8]>) -> [UInt8] { + reader.value(for: variable) + } + + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[UInt8]> + ) async throws -> [UInt8] { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[UInt8]>, + updatesHandler: (ConfigUpdatesAsyncSequence<[UInt8], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[UInt8]>) -> [UInt8] { + reader[variable] + } } // MARK: - BoolArrayTestHelper -private struct BoolArrayTestHelper: ConfigArrayValueTestHelper { +private struct BoolArrayTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> [Bool] { let count = Int.random(in: 1 ... 5, using: &generator) return Array(count: count) { Bool.random(using: &generator) } @@ -928,35 +867,39 @@ private struct BoolArrayTestHelper: ConfigArrayValueTestHelper { func configContent(for value: [Bool]) -> ConfigContent { .boolArray(value) } -} -// MARK: - DataArrayTestHelper + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[Bool]>) -> [Bool] { + reader.value(for: variable) + } -private struct DataArrayTestHelper: ConfigArrayValueTestHelper { - func randomValue(using generator: inout some RandomNumberGenerator) -> [Data] { - let count = Int.random(in: 1 ... 5, using: &generator) - return Array(count: count) { - let byteCount = Int.random(in: 1 ... 32, using: &generator) - return Data.random(count: byteCount, using: &generator) - } + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[Bool]> + ) async throws -> [Bool] { + try await reader.fetchValue(for: variable) } - func differentValue(from value: [Data], using generator: inout some RandomNumberGenerator) -> [Data] { - value + randomValue(using: &generator) + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[Bool]>, + updatesHandler: (ConfigUpdatesAsyncSequence<[Bool], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) } - func configContent(for value: [Data]) -> ConfigContent { - .byteChunkArray(value.map { Array($0) }) + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[Bool]>) -> [Bool] { + reader[variable] } } // MARK: - Float64ArrayTestHelper -private struct Float64ArrayTestHelper: ConfigArrayValueTestHelper { +private struct Float64ArrayTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> [Float64] { let count = Int.random(in: 1 ... 5, using: &generator) return Array(count: count) { Float64.random(in: 1 ... 100_000, using: &generator) } @@ -971,12 +914,39 @@ private struct Float64ArrayTestHelper: ConfigArrayValueTestHelper { func configContent(for value: [Float64]) -> ConfigContent { .doubleArray(value) } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[Float64]>) -> [Float64] { + reader.value(for: variable) + } + + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[Float64]> + ) async throws -> [Float64] { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[Float64]>, + updatesHandler: (ConfigUpdatesAsyncSequence<[Float64], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[Float64]>) -> [Float64] { + reader[variable] + } } // MARK: - IntArrayTestHelper -private struct IntArrayTestHelper: ConfigArrayValueTestHelper { +private struct IntArrayTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> [Int] { let count = Int.random(in: 1 ... 5, using: &generator) return Array(count: count) { Int.random(in: 1 ... 100_000, using: &generator) } @@ -991,12 +961,39 @@ private struct IntArrayTestHelper: ConfigArrayValueTestHelper { func configContent(for value: [Int]) -> ConfigContent { .intArray(value) } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[Int]>) -> [Int] { + reader.value(for: variable) + } + + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[Int]> + ) async throws -> [Int] { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[Int]>, + updatesHandler: (ConfigUpdatesAsyncSequence<[Int], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[Int]>) -> [Int] { + reader[variable] + } } // MARK: - StringArrayTestHelper -private struct StringArrayTestHelper: ConfigArrayValueTestHelper { +private struct StringArrayTestHelper: ReaderTestHelper { func randomValue(using generator: inout some RandomNumberGenerator) -> [String] { let count = Int.random(in: 1 ... 5, using: &generator) return Array(count: count) { String.randomAlphanumeric(count: count * 3, using: &generator) } @@ -1004,11 +1001,88 @@ private struct StringArrayTestHelper: ConfigArrayValueTestHelper { func differentValue(from value: [String], using generator: inout some RandomNumberGenerator) -> [String] { - return value + randomValue(using: &generator) + value + randomValue(using: &generator) } func configContent(for value: [String]) -> ConfigContent { .stringArray(value) } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[String]>) -> [String] { + reader.value(for: variable) + } + + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[String]> + ) async throws -> [String] { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[String]>, + updatesHandler: (ConfigUpdatesAsyncSequence<[String], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[String]>) -> [String] { + reader[variable] + } +} + + +// MARK: - ByteChunkArrayTestHelper + +private struct ByteChunkArrayTestHelper: ReaderTestHelper { + func randomValue(using generator: inout some RandomNumberGenerator) -> [[UInt8]] { + let count = Int.random(in: 1 ... 5, using: &generator) + return Array(count: count) { + let byteCount = Int.random(in: 1 ... 32, using: &generator) + return Array(count: byteCount) { UInt8.random(in: .min ... .max, using: &generator) } + } + } + + + func differentValue(from value: [[UInt8]], using generator: inout some RandomNumberGenerator) -> [[UInt8]] { + value + randomValue(using: &generator) + } + + + func configContent(for value: [[UInt8]]) -> ConfigContent { + .byteChunkArray(value) + } + + + func getValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[[UInt8]]>) -> [[UInt8]] { + reader.value(for: variable) + } + + + func fetchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[[UInt8]]> + ) async throws -> [[UInt8]] { + try await reader.fetchValue(for: variable) + } + + + func watchValue( + from reader: ConfigVariableReader, + for variable: ConfigVariable<[[UInt8]]>, + updatesHandler: (ConfigUpdatesAsyncSequence<[[UInt8]], Never>) async throws -> Return + ) async throws -> Return { + try await reader.watchValue(for: variable, updatesHandler: updatesHandler) + } + + + func subscriptValue(from reader: ConfigVariableReader, for variable: ConfigVariable<[[UInt8]]>) -> [[UInt8]] { + reader[variable] + } }