From f2ca9ebf6cc7b547cafd8ebc73bdedd9e7f7d6c2 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Sun, 7 Dec 2025 01:08:55 +0100 Subject: [PATCH] [Docs] Editing pass to prep for 1.0 --- Sources/Configuration/AccessReporter.swift | 24 ++++++++++- .../AccessReporters/AccessLogger.swift | 12 +++--- .../AccessReporters/FileAccessLogger.swift | 10 ++--- Sources/Configuration/ConfigKey.swift | 24 ++++++++--- Sources/Configuration/ConfigProvider.swift | 21 +++++++-- .../Configuration/ConfigSnapshotReader.swift | 14 +++--- .../Documentation.docc/Documentation.md | 5 +-- .../Guides/Best-practices.md | 24 +++++------ .../Guides/Choosing-access-patterns.md | 43 ++++++++----------- .../Guides/Choosing-reader-methods.md | 26 +++++------ .../Guides/Configuring-applications.md | 10 ++--- .../Guides/Configuring-libraries.md | 6 +-- .../Guides/Example-use-cases.md | 2 +- .../Guides/Handling-secrets-correctly.md | 7 +-- .../Guides/Troubleshooting.md | 23 +++++----- .../Guides/Using-in-memory-providers.md | 2 +- .../Guides/Using-reloading-providers.md | 2 +- .../Reference/AccessEvent-Metadata.md | 1 - .../Reference/ConfigReader-Watch.md | 6 +-- .../ExpressibleByConfigString.swift | 2 +- .../Providers/Files/FileProvider.swift | 14 +++--- .../Files/FileProviderSnapshot.swift | 26 ++++++++--- .../Providers/Files/JSONSnapshot.swift | 9 +++- .../Files/ReloadingFileProvider.swift | 20 +++++---- .../Providers/Files/YAMLSnapshot.swift | 9 +++- 25 files changed, 204 insertions(+), 138 deletions(-) diff --git a/Sources/Configuration/AccessReporter.swift b/Sources/Configuration/AccessReporter.swift index 2e8f52c..77b709a 100644 --- a/Sources/Configuration/AccessReporter.swift +++ b/Sources/Configuration/AccessReporter.swift @@ -46,9 +46,14 @@ public protocol AccessReporter: Sendable { public struct AccessEvent: Sendable { /// Metadata describing the configuration access operation. + /// + /// Contains information about the type of access, the key accessed, value type, + /// source location, and timestamp. public struct Metadata: Sendable { - /// The source code location where the configuration access occurred. + /// The source code location where a configuration access occurred. + /// + /// Captures the file identifier and line number for debugging and auditing purposes. public struct SourceLocation: Sendable, CustomStringConvertible { /// The identifier of the source file where the access occurred. @@ -73,6 +78,9 @@ public struct AccessEvent: Sendable { } /// The type of configuration access operation. + /// + /// Indicates whether the access was a synchronous get, asynchronous fetch, + /// or an async watch operation. @frozen public enum AccessKind: String, Sendable { /// A synchronous get operation that returns the current value. @@ -126,6 +134,9 @@ public struct AccessEvent: Sendable { } /// The result of a configuration lookup from a specific provider. + /// + /// Contains the provider's name and the outcome of querying that provider, + /// which can be either a successful lookup result or an error. public struct ProviderResult: Sendable { /// The name of the configuration provider that processed the lookup. @@ -186,6 +197,17 @@ public struct AccessEvent: Sendable { /// Use this reporter to send configuration access events to multiple destinations /// simultaneously. Each upstream reporter receives a copy of every event in the /// order they were provided during initialization. +/// +/// ```swift +/// let fileLogger = try FileAccessLogger(filePath: "/tmp/config.log") +/// let accessLogger = AccessLogger(logger: logger) +/// let broadcaster = BroadcastingAccessReporter(upstreams: [fileLogger, accessLogger]) +/// +/// let config = ConfigReader( +/// provider: EnvironmentVariablesProvider(), +/// accessReporter: broadcaster +/// ) +/// ``` @available(Configuration 1.0, *) public struct BroadcastingAccessReporter: Sendable { diff --git a/Sources/Configuration/AccessReporters/AccessLogger.swift b/Sources/Configuration/AccessReporters/AccessLogger.swift index 134c971..6203bf8 100644 --- a/Sources/Configuration/AccessReporters/AccessLogger.swift +++ b/Sources/Configuration/AccessReporters/AccessLogger.swift @@ -47,12 +47,12 @@ import Synchronization /// ## Log format /// /// Each access event generates a structured log entry with metadata including: -/// - `kind`: The type of access operation (get, fetch, watch) -/// - `key`: The configuration key that was accessed -/// - `location`: The source code location where the access occurred -/// - `value`: The resolved configuration value (redacted for secrets) -/// - `counter`: An incrementing counter for tracking access frequency -/// - Provider-specific information for each provider in the hierarchy +/// - `kind`: The type of access operation (get, fetch, watch). +/// - `key`: The configuration key that was accessed. +/// - `location`: The source code location where the access occurred. +/// - `value`: The resolved configuration value (redacted for secrets). +/// - `counter`: An incrementing counter for tracking access frequency. +/// - Provider-specific information for each provider in the hierarchy. @available(Configuration 1.0, *) public final class AccessLogger: Sendable { diff --git a/Sources/Configuration/AccessReporters/FileAccessLogger.swift b/Sources/Configuration/AccessReporters/FileAccessLogger.swift index cd60224..3b97fbb 100644 --- a/Sources/Configuration/AccessReporters/FileAccessLogger.swift +++ b/Sources/Configuration/AccessReporters/FileAccessLogger.swift @@ -59,11 +59,11 @@ import Synchronization /// ``` /// /// The log entries include: -/// - Status emoji (✅ success, 🟡 default/nil, ❌ error) -/// - Configuration key that was accessed -/// - Resolved value (redacted for secrets) -/// - Provider that supplied the value or error information -/// - Access metadata (operation type, value type, source location, timestamp) +/// - Status emoji (✅ success, 🟡 default/nil, ❌ error). +/// - Configuration key that was accessed. +/// - Resolved value (redacted for secrets). +/// - Provider that supplied the value or error information. +/// - Access metadata (operation type, value type, source location, timestamp). @available(Configuration 1.0, *) public final class FileAccessLogger: Sendable { diff --git a/Sources/Configuration/ConfigKey.swift b/Sources/Configuration/ConfigKey.swift index dc5af21..3e684a5 100644 --- a/Sources/Configuration/ConfigKey.swift +++ b/Sources/Configuration/ConfigKey.swift @@ -12,14 +12,24 @@ // //===----------------------------------------------------------------------===// -/// A configuration key that represents a relative path to a configuration value. +/// A configuration key representing a relative path to a configuration value. /// -/// Configuration keys consist of an array of string components that form a hierarchical -/// path, similar to file system paths or JSON object keys. For example, the key -/// `["http", "timeout"]` represents the value `timeout` nested underneath `http`. +/// Configuration keys consist of hierarchical string components forming paths similar to +/// file system paths or JSON object keys. For example, `["http", "timeout"]` represents +/// the `timeout` value nested under `http`. /// -/// Keys can include additional context information that some providers use to -/// refine value lookups or provide more specific results. +/// Keys support additional context information that providers can use to refine lookups +/// or provide specialized behavior. +/// +/// ## Usage +/// +/// Create keys using string literals, arrays, or the initializers: +/// +/// ```swift +/// let key1: ConfigKey = "database.connection.timeout" +/// let key2 = ConfigKey(["api", "endpoints", "primary"]) +/// let key3 = ConfigKey("server.port", context: ["environment": .string("production")]) +/// ``` @available(Configuration 1.0, *) public struct ConfigKey: Sendable { @@ -46,7 +56,7 @@ public struct ConfigKey: Sendable { /// Creates a new configuration key. /// - Parameters: - /// - string: The string represenation of the key path, for example `"http.timeout"`. + /// - string: The string representation of the key path, for example `"http.timeout"`. /// - context: Additional context information for the key. public init(_ string: String, context: [String: ConfigContextValue] = [:]) { self = DotSeparatorKeyDecoder.decode(string, context: context) diff --git a/Sources/Configuration/ConfigProvider.swift b/Sources/Configuration/ConfigProvider.swift index 4866844..decab2f 100644 --- a/Sources/Configuration/ConfigProvider.swift +++ b/Sources/Configuration/ConfigProvider.swift @@ -161,6 +161,16 @@ public protocol ConfigSnapshot: Sendable { } /// The result of looking up a configuration value in a provider. +/// +/// Providers return this result from value lookup methods, containing both the +/// encoded key used for the lookup and the value found: +/// +/// ```swift +/// let result = try provider.value(forKey: key, type: .string) +/// if let value = result.value { +/// print("Found: \(value)") +/// } +/// ``` @available(Configuration 1.0, *) public struct LookupResult: Sendable, Equatable, Hashable { @@ -435,9 +445,14 @@ public struct LookupResult: Sendable, Equatable, Hashable { /// A configuration value that wraps content with metadata. /// -/// Configuration values include the actual content and a flag indicating whether -/// the value contains sensitive information. Secret values are protected from -/// accidental disclosure in logs and debug output. +/// Configuration values pair raw content with a flag indicating whether the value +/// contains sensitive information. Secret values are protected from accidental +/// disclosure in logs and debug output: +/// +/// ```swift +/// let apiKey = ConfigValue(.string("sk-abc123"), isSecret: true) +/// print(apiKey) // Prints: [string: ] +/// ``` @available(Configuration 1.0, *) public struct ConfigValue: Sendable, Equatable, Hashable { diff --git a/Sources/Configuration/ConfigSnapshotReader.swift b/Sources/Configuration/ConfigSnapshotReader.swift index 3cf7ee7..380981c 100644 --- a/Sources/Configuration/ConfigSnapshotReader.swift +++ b/Sources/Configuration/ConfigSnapshotReader.swift @@ -16,14 +16,14 @@ import Synchronization /// A container type for reading config values from snapshots. /// -/// A config snapshot reader provides read-only access to config values stored in an underlying snapshot. -/// Unlike ``ConfigReader``, which can access live, changing config values from providers, a snapshot reader -/// works with a fixed, immutable snapshot of the configuration data. +/// A config snapshot reader provides read-only access to config values stored in an underlying +/// ``ConfigSnapshot``. Unlike a config reader, which can access live, changing config values +/// from providers, a snapshot reader works with a fixed, immutable snapshot of the configuration data. /// /// ## Usage /// -/// Get a ``ConfigSnapshotReader`` from a ``ConfigReader`` by using ``ConfigReader/snapshot()`` -/// to retrieve a snapshot. All values in the snapshot are guaranteed to be from the same point in time: +/// Get a snapshot reader from a config reader by using the ``ConfigReader/snapshot()`` method. All values in the +/// snapshot are guaranteed to be from the same point in time: /// ```swift /// // Get a snapshot from a ConfigReader /// let config = ConfigReader(provider: EnvironmentVariablesProvider()) @@ -37,7 +37,7 @@ import Synchronization /// let identity = MyIdentity(cert: cert, privateKey: privateKey) /// ``` /// -/// Or you can watch for snapshot updates using the ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)``: +/// Or you can watch for snapshot updates using the ``ConfigReader/watchSnapshot(fileID:line:updatesHandler:)`` method: /// /// ```swift /// try await config.watchSnapshot { snapshots in @@ -211,7 +211,7 @@ public struct ConfigSnapshotReader: Sendable { /// let timeout = httpConfig.int(forKey: "timeout") // Reads from "client.http.timeout" in the snapshot /// ``` /// - /// - Parameters configKey: The key to append to the current key prefix. + /// - Parameter configKey: The key to append to the current key prefix. /// - Returns: A reader for accessing scoped values. public func scoped(to configKey: ConfigKey) -> ConfigSnapshotReader diff --git a/Sources/Configuration/Documentation.docc/Documentation.md b/Sources/Configuration/Documentation.docc/Documentation.md index f8cb1ec..4777d27 100644 --- a/Sources/Configuration/Documentation.docc/Documentation.md +++ b/Sources/Configuration/Documentation.docc/Documentation.md @@ -120,7 +120,7 @@ For example, to read the timeout configuration value for an HTTP client, check o ```swift // Environment variables consulted first, then JSON. let primaryProvider = EnvironmentVariablesProvider() - let secondaryProvider = try await JSONProvider( + let secondaryProvider = try await FileProvider( filePath: "/etc/config.json" ) let config = ConfigReader(providers: [ @@ -246,8 +246,7 @@ You can also implement a custom ``ConfigProvider``. In addition to using providers individually, you can create fallback behavior using an array of providers. The first provider that returns a non-nil value wins. -The following example illustrates a hierarchy of provides, with environmental variables overrides winning -over command line arguments, a file at `/etc/config.json`, and in-memory defaults: +The following example shows a provider hierarchy where environment variables take precedence over command line arguments, a JSON file, and in-memory defaults: ```swift // Create a hierarchy of providers with fallback behavior. diff --git a/Sources/Configuration/Documentation.docc/Guides/Best-practices.md b/Sources/Configuration/Documentation.docc/Guides/Best-practices.md index 5c5f6bb..7f8983c 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Best-practices.md +++ b/Sources/Configuration/Documentation.docc/Guides/Best-practices.md @@ -4,20 +4,18 @@ Follow these principles to make your code easily configurable and composable wit ## Overview -When designing configuration for your Swift libraries and applications, following established patterns helps create -a consistent and maintainable experience for developers. These best practices ensure your configuration integrates -well with the broader Swift ecosystem. +When designing configuration for Swift libraries and applications, follow these patterns to create consistent, maintainable code that integrates well with the Swift ecosystem. ### Document configuration keys -Include comprehensive documentation about what configuration keys your library reads. For each key, document: +Include thorough documentation about what configuration keys your library reads. For each key, document: -- The key name and its hierarchical structure -- The expected data type -- Whether the key is required or optional -- Default values when applicable -- Valid value ranges or constraints -- Usage examples +- The key name and its hierarchical structure. +- The expected data type. +- Whether the key is required or optional. +- Default values when applicable. +- Valid value ranges or constraints. +- Usage examples. ```swift public struct HTTPClientConfiguration { @@ -38,8 +36,7 @@ public struct HTTPClientConfiguration { ### Use sensible defaults -Provide reasonable default values whenever possible to make your library work without extensive configuration. -This reduces the barrier to adoption and ensures your library works out of the box for common use cases. +Provide reasonable default values to make your library work without extensive configuration. ```swift // Good: Provides sensible defaults @@ -114,8 +111,7 @@ For more details, check out . ### Validate configuration values -Consider validating configuration values and throwing meaningful errors if they're invalid. This helps -developers catch configuration issues early. +Validate configuration values and throw meaningful errors for invalid input to catch configuration issues early. ```swift public init(config: ConfigReader) throws { diff --git a/Sources/Configuration/Documentation.docc/Guides/Choosing-access-patterns.md b/Sources/Configuration/Documentation.docc/Guides/Choosing-access-patterns.md index 9cc823e..c95ae28 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Choosing-access-patterns.md +++ b/Sources/Configuration/Documentation.docc/Guides/Choosing-access-patterns.md @@ -4,8 +4,7 @@ Learn how to select the right method for reading configuration values based on y ## Overview -Swift Configuration provides three access patterns for retrieving configuration values, each optimized -for different use cases and performance requirements. +Swift Configuration provides three access patterns for retrieving configuration values, each optimized for different use cases and performance requirements. The three access patterns are: @@ -44,13 +43,11 @@ Use the "get" pattern when: - Returns the currently cached value from the provider. - No network or I/O operations occur during the call. -- Values may become stale if the underlying data source changes and the provider is either non-reloading, or - has a long reload interval. +- Values may become stale if the underlying data source changes and the provider is either non-reloading, or has a long reload interval. ### Fetch: Asynchronous fresh access -The "fetch" pattern asynchronously retrieves the most current value from the authoritative data source. This ensures -you always get up-to-date configuration, even if it requires network calls or file system access. +The "fetch" pattern asynchronously retrieves the most current value from the authoritative data source, ensuring you always get up-to-date configuration. ```swift let config = ConfigReader(provider: remoteConfigProvider) @@ -74,13 +71,12 @@ let dbConnectionString = try await config.fetchRequiredString( #### When to use -Use the `fetch` pattern when: +Use the "fetch" pattern when: - **Freshness is critical**: You need the latest configuration values. - **Remote providers**: Using configuration services, databases, or external APIs that perform evaluation remotely. - **Infrequent access**: Reading configuration occasionally, not in hot paths. -- **Setup operations**: Configuring long-lived resources like database connections where one-time overhead isn't - a concern, and the improved freshness is important. +- **Setup operations**: Configuring long-lived resources like database connections where one-time overhead isn't a concern, and the improved freshness is important. - **Administrative operations**: Fetching current settings for management interfaces. #### Behavior characteristics @@ -115,8 +111,8 @@ try await config.watchInt(forKey: "http.timeout", default: 30) { updates in Use the "watch" pattern when: - **Dynamic configuration**: Values change during application runtime. -- **Hot reloading**: Need to update behavior without restarting the service. -- **Feature toggles**: Enabling/disabling features based on configuration changes. +- **Hot reloading**: You need to update behavior without restarting the service. +- **Feature toggles**: Enabling or disabling features based on configuration changes. - **Resource management**: Adjusting timeouts, limits, or thresholds dynamically. - **A/B testing**: Updating experimental parameters in real-time. @@ -149,8 +145,8 @@ let context: [String: ConfigContextValue] = [ let dbConfig = try await config.fetchRequiredString( forKey: ConfigKey( "database.connection_string", - context: context, - ) + context: context + ), isSecret: true ) @@ -171,16 +167,19 @@ try await config.watchInt( ### Summary of performance considerations #### Get pattern performance + - **Fastest**: No async overhead, immediate return. - **Memory usage**: Minimal, uses cached values. - **Best for**: Request handling, hot code paths, startup configuration. -#### Fetch pattern performance +#### Fetch pattern performance + - **Moderate**: Async overhead plus data source access time. - **Network dependent**: Performance varies with provider implementation. - **Best for**: Infrequent access, setup operations, administrative tasks. #### Watch pattern performance + - **Background monitoring**: Continuous resource usage for monitoring. - **Event-driven**: Efficient updates only when values change. - **Best for**: Long-running services, dynamic configuration, feature toggles. @@ -228,18 +227,10 @@ try await config.watchRequiredInt(forKey: "port") { updates in ### Best practices -1. **Choose based on use case**: Use "get" for performance-critical paths, "fetch" for freshness, and - "watch" for hot reloading. - -2. **Handle errors appropriately**: Design error handling strategies that match your application's - resilience requirements. - -3. **Use context judiciously**: Provide context when you need environment-specific or conditional - configuration values. - -4. **Monitor configuration access**: Use ``AccessReporter`` to understand your application's - configuration dependencies. - +1. **Choose based on use case**: Use "get" for performance-critical paths, "fetch" for freshness, and "watch" for hot reloading. +2. **Handle errors appropriately**: Design error handling strategies that match your application's resilience requirements. +3. **Use context judiciously**: Provide context when you need environment-specific or conditional configuration values. +4. **Monitor configuration access**: Use ``AccessReporter`` to understand your application's configuration dependencies. 5. **Cache wisely**: For frequently accessed values, prefer "get" over repeated "fetch" calls. For more guidance on selecting the right reader methods for your needs, see . diff --git a/Sources/Configuration/Documentation.docc/Guides/Choosing-reader-methods.md b/Sources/Configuration/Documentation.docc/Guides/Choosing-reader-methods.md index c8d03fd..cf103bf 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Choosing-reader-methods.md +++ b/Sources/Configuration/Documentation.docc/Guides/Choosing-reader-methods.md @@ -49,12 +49,13 @@ Use optional variants when: - **Truly optional features**: The configuration controls optional functionality. - **Gradual rollouts**: New configuration that might not be present everywhere. -- **Conditional behavior**: Your code can operate differently based on presence/absence. +- **Conditional behavior**: Your code can operate differently based on presence or absence. - **Debugging and diagnostics**: You want to detect missing configuration explicitly. #### Error handling behavior Optional variants handle errors gracefully by returning `nil`: + - Missing values return `nil`. - Type conversion errors return `nil`. - Provider errors return `nil` (except for fetch variants, which always propagate provider errors). @@ -128,6 +129,7 @@ let maxConnections = config.int(forKey: "pool.max", default: 10) // Conservative #### Error handling behavior Default variants handle errors by returning the default value: + - Missing values return the default. - Type conversion errors return the default. - Provider errors return the default (except for fetch variants). @@ -238,15 +240,18 @@ String configuration values can be automatically converted to other types using This works with: **Built-in convertible types:** -- `SystemPackage.FilePath` - Converts from file paths. -- `Foundation.URL` - Converts from URL strings. -- `Foundation.UUID` - Converts from UUID strings. -- `Foundation.Date` - Converts from ISO8601 date strings. + +- `SystemPackage.FilePath`: Converts from file paths. +- `Foundation.URL`: Converts from URL strings. +- `Foundation.UUID`: Converts from UUID strings. +- `Foundation.Date`: Converts from ISO8601 date strings. **String-backed enums:** + - Types that conform to `RawRepresentable`. **Custom types:** + - Types that you explicitly conform to ``ExpressibleByConfigString``. ```swift @@ -298,12 +303,9 @@ Also check out . ### Best practices 1. **Use required variants** only for truly critical configuration. - -2. **Use default variants for user experience settings** where missing configuration shouldn't break functionality. - -3. **Use optional variants for feature flags and debugging** where the absence of configuration is meaningful. - +2. **Use default variants** for user experience settings where missing configuration shouldn't break functionality. +3. **Use optional variants** for feature flags and debugging where the absence of configuration is meaningful. 4. **Choose safe defaults** that won't cause security issues or performance problems if used in production. -For guidance on selecting between get, fetch, and watch access patterns, see . -For comprehensive best practices on configuration design, check out . +For guidance on selecting between get, fetch, and watch access patterns, see . +For more configuration guidance, check out . diff --git a/Sources/Configuration/Documentation.docc/Guides/Configuring-applications.md b/Sources/Configuration/Documentation.docc/Guides/Configuring-applications.md index d79abbc..7b707d0 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Configuring-applications.md +++ b/Sources/Configuration/Documentation.docc/Guides/Configuring-applications.md @@ -4,16 +4,14 @@ Provide flexible and consistent configuration for your application. ## Overview -Swift Configuration provides a consistent configuration system for your tools and applications, both server and client. - -This guide shows how to: +Swift Configuration provides consistent configuration for your tools and applications. This guide shows how to: 1. Set up a configuration hierarchy with multiple providers. 2. Configure your application's components. -3. Access configuration values in your application and in libraries. +3. Access configuration values in your application and libraries. 4. Monitor configuration access with access reporting. -This pattern works especially well for server applications where configuration might come from various sources such as environment variables, configuration files, and remote configuration services. +This pattern works well for server applications where configuration comes from environment variables, configuration files, and remote services. ### Setting up a configuration hierarchy @@ -188,4 +186,4 @@ export SERVICES_CUSTOMER_TIMEOUT=15.0 For details about the key conversion logic, check out ``EnvironmentVariablesProvider``. -For comprehensive configuration best practices, see . To understand different access patterns and reader methods, refer to and . For handling secrets securely, check out . +For more configuration guidance, see . To understand different access patterns and reader methods, refer to and . For handling secrets securely, check out . diff --git a/Sources/Configuration/Documentation.docc/Guides/Configuring-libraries.md b/Sources/Configuration/Documentation.docc/Guides/Configuring-libraries.md index 551d234..175c9dc 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Configuring-libraries.md +++ b/Sources/Configuration/Documentation.docc/Guides/Configuring-libraries.md @@ -4,9 +4,9 @@ Provide a consistent and flexible way to configure your library. ## Overview -Swift Configuration offers a pattern for configuring libraries that works across various configuration sources: be it from environment variables, a JSON file, or a remote configuration service. +Swift Configuration provides a pattern for configuring libraries that works across various configuration sources: environment variables, JSON files, and remote configuration services. -This guide shows how to adopt this pattern in your own library, to make it easier to compose in a larger application. +This guide shows how to adopt this pattern in your library to make it easier to compose in larger applications. Adopt this pattern in three steps: @@ -126,4 +126,4 @@ extension HTTPClientConfiguration { Built-in ``AccessReporter`` types such as ``AccessLogger`` and ``FileAccessLogger`` automatically redact secret values to avoid leaking sensitive information. -For more guidance on secrets handling, see . For comprehensive configuration best practices, check out . To understand different access patterns and reader methods, refer to and . +For more guidance on secrets handling, see . For more configuration guidance, check out . To understand different access patterns and reader methods, refer to and . diff --git a/Sources/Configuration/Documentation.docc/Guides/Example-use-cases.md b/Sources/Configuration/Documentation.docc/Guides/Example-use-cases.md index 4016ec0..3da9e0b 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Example-use-cases.md +++ b/Sources/Configuration/Documentation.docc/Guides/Example-use-cases.md @@ -247,5 +247,5 @@ let config = ConfigReader(provider: prefixedProvider) let databaseURL = config.string(forKey: "database.url", default: "localhost") ``` -For comprehensive configuration guidance and best practices, see . +For more configuration guidance, see . To understand different reader method variants, check out . diff --git a/Sources/Configuration/Documentation.docc/Guides/Handling-secrets-correctly.md b/Sources/Configuration/Documentation.docc/Guides/Handling-secrets-correctly.md index ec70ff4..fcb643d 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Handling-secrets-correctly.md +++ b/Sources/Configuration/Documentation.docc/Guides/Handling-secrets-correctly.md @@ -4,7 +4,7 @@ Protect sensitive configuration values from accidental disclosure in logs and de ## Overview -Swift Configuration provides built-in support for marking sensitive values as secrets. Secret values are automatically redacted by built-in access reporters and other components to prevent accidental disclosure of sensitive information like API keys, passwords, and tokens. +Swift Configuration provides built-in support for marking sensitive values as secrets. Secret values are automatically redacted by access reporters to prevent accidental disclosure of API keys, passwords, and other sensitive information. ### Marking values as secret when reading @@ -134,11 +134,8 @@ print(provider) ### Best practices 1. **Mark all sensitive data as secret**: API keys, passwords, tokens, private keys, connection strings. - 2. **Use provider-level specification** when you know which keys are always secret. - 3. **Use reader-level marking** for context-specific secrets or when the same key might be secret in some contexts but not others. - -4. **Be conservative**: When in doubt, mark values as secret - it's safer than accidentally leaking sensitive data. +4. **Be conservative**: When in doubt, mark values as secret. It's safer than accidentally leaking sensitive data. For additional guidance on configuration security and overall best practices, see . To debug issues with secret redaction in access logs, check out . When selecting between required, optional, and default method variants for secret values, refer to . diff --git a/Sources/Configuration/Documentation.docc/Guides/Troubleshooting.md b/Sources/Configuration/Documentation.docc/Guides/Troubleshooting.md index 2f541cd..0e39193 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Troubleshooting.md +++ b/Sources/Configuration/Documentation.docc/Guides/Troubleshooting.md @@ -8,11 +8,9 @@ Check out some techniques to debug unexpected issues and to increase visibility If your configuration values aren't being read correctly, check: -1. **Environment variable naming**: When using `EnvironmentVariablesProvider`, keys are automatically converted to uppercase with dots replaced by underscores. For example, `database.url` becomes `DATABASE_URL`. - -2. **Provider ordering**: When using multiple providers, remember they're checked in order, and the first one that returns a value wins. - -3. **Debug with an access reporter**: Use the access reporting feature to see which keys are being queried and what values (if any) are being returned. See the next section for details. +1. **Environment variable naming**: When using ``EnvironmentVariablesProvider``, keys are automatically converted to uppercase with dots replaced by underscores. For example, `database.url` becomes `DATABASE_URL`. +2. **Provider ordering**: When using multiple providers, they're checked in order and the first one that returns a value wins. +3. **Debug with an access reporter**: Use access reporting to see which keys are being queried and what values (if any) are being returned. See the next section for details. For guidance on selecting the right configuration access patterns and reader methods, check out and . @@ -34,6 +32,7 @@ let timeout = config.double(forKey: "http.timeout", default: 30.0) ``` This produces log entries showing: + - Which configuration keys were accessed. - What values were returned (with secret values redacted). - Which provider supplied the value. @@ -68,16 +67,20 @@ tail -f /var/log/myapp/config-access.log ### Error handling #### Provider errors + If any provider throws an error during lookup: -- **Required methods** (`getRequired*`): Error is immediately thrown to the caller. -- **Optional methods** (`get*` with or without defaults): Error is handled gracefully, nil or default value is returned. + +- **Required methods** (`requiredString`, etc.): Error is immediately thrown to the caller. +- **Optional methods** (with or without defaults): Error is handled gracefully; `nil` or the default value is returned. > Tip: Even when an error gets handled gracefully, you can log it using an ``AccessReporter``. #### Missing values + When no provider has the requested value: + - **Methods with defaults**: Return the provided default value. -- **Methods without defaults**: Return nil. +- **Methods without defaults**: Return `nil`. - **Required methods**: Throw an error. #### File not found errors @@ -125,8 +128,8 @@ let provider = try await ReloadingFileProvider( If your reloading provider isn't detecting file changes: -1. **Check ServiceGroup**: Ensure the provider is running in a ServiceGroup. -2. **Enable verbose logging**: The built-in providers use Swift Log for detailed logging, which can help spot the underlying issue. +1. **Check ServiceGroup**: Ensure the provider is running in a `ServiceGroup`. +2. **Enable verbose logging**: The built-in providers use Swift Log for detailed logging, which can help spot issues. 3. **Verify file path**: Confirm the file path is correct, the file exists, and file permissions are correct. 4. **Check poll interval**: Consider if your poll interval is appropriate for your use case. diff --git a/Sources/Configuration/Documentation.docc/Guides/Using-in-memory-providers.md b/Sources/Configuration/Documentation.docc/Guides/Using-in-memory-providers.md index e705f4b..253fbb7 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Using-in-memory-providers.md +++ b/Sources/Configuration/Documentation.docc/Guides/Using-in-memory-providers.md @@ -180,4 +180,4 @@ class ConfigurationBridge { } ``` -For comparison with reloading providers, see . To understand different access patterns and when to use each provider type, check out . For comprehensive configuration best practices, refer to . +For comparison with reloading providers, see . To understand different access patterns and when to use each provider type, check out . For more configuration guidance, refer to . diff --git a/Sources/Configuration/Documentation.docc/Guides/Using-reloading-providers.md b/Sources/Configuration/Documentation.docc/Guides/Using-reloading-providers.md index 1f4d4c6..6f5fa94 100644 --- a/Sources/Configuration/Documentation.docc/Guides/Using-reloading-providers.md +++ b/Sources/Configuration/Documentation.docc/Guides/Using-reloading-providers.md @@ -101,7 +101,7 @@ try await config.watchSnapshot { updates in ### Handling missing files during reloading -Reloading providers support the `allowMissing` parameter to handle cases where configuration files might be temporarily missing or optional. This is particularly useful for: +Reloading providers support the `allowMissing` parameter to handle cases where configuration files might be temporarily missing or optional. This is useful for: - Optional configuration files that might not exist in all environments. - Configuration files that are created or removed dynamically. diff --git a/Sources/Configuration/Documentation.docc/Reference/AccessEvent-Metadata.md b/Sources/Configuration/Documentation.docc/Reference/AccessEvent-Metadata.md index 2d9f9ef..805c8c6 100644 --- a/Sources/Configuration/Documentation.docc/Reference/AccessEvent-Metadata.md +++ b/Sources/Configuration/Documentation.docc/Reference/AccessEvent-Metadata.md @@ -5,7 +5,6 @@ ### Creating access event metadata - ``init(accessKind:key:valueType:sourceLocation:accessTimestamp:)`` -- ``init(metadata:providerResults:conversionError:result:)`` - ``AccessKind`` ### Inspecting access event metadata diff --git a/Sources/Configuration/Documentation.docc/Reference/ConfigReader-Watch.md b/Sources/Configuration/Documentation.docc/Reference/ConfigReader-Watch.md index 3a6b2e2..71c0824 100644 --- a/Sources/Configuration/Documentation.docc/Reference/ConfigReader-Watch.md +++ b/Sources/Configuration/Documentation.docc/Reference/ConfigReader-Watch.md @@ -35,11 +35,11 @@ ### Watching required Boolean values - ``ConfigReader/watchRequiredBool(forKey:isSecret:fileID:line:updatesHandler:)`` -### Watching lists Boolean values +### Watching lists of Boolean values - ``ConfigReader/watchBoolArray(forKey:isSecret:fileID:line:updatesHandler:)`` - ``ConfigReader/watchBoolArray(forKey:isSecret:default:fileID:line:updatesHandler:)`` -### Watching required lists Boolean values +### Watching required lists of Boolean values - ``ConfigReader/watchRequiredBoolArray(forKey:isSecret:fileID:line:updatesHandler:)`` ### Watching integer values @@ -67,7 +67,7 @@ - ``ConfigReader/watchDoubleArray(forKey:isSecret:fileID:line:updatesHandler:)`` - ``ConfigReader/watchDoubleArray(forKey:isSecret:default:fileID:line:updatesHandler:)`` -### Watching required double of integer values +### Watching required lists of double values - ``ConfigReader/watchRequiredDoubleArray(forKey:isSecret:fileID:line:updatesHandler:)`` ### Watching bytes diff --git a/Sources/Configuration/ExpressibleByConfigString.swift b/Sources/Configuration/ExpressibleByConfigString.swift index 40948b5..0d6ee64 100644 --- a/Sources/Configuration/ExpressibleByConfigString.swift +++ b/Sources/Configuration/ExpressibleByConfigString.swift @@ -26,7 +26,7 @@ public import SystemPackage /// /// > Tip: If your type is a string-based enum, you don't need to explicitly conform it to /// > ``ExpressibleByConfigString``, as the same conversions work for types -/// > that conform to `RawRepresentable` automatically. +/// > that conform to `RawRepresentable` with a `String` raw value automatically. /// /// ## Custom types /// diff --git a/Sources/Configuration/Providers/Files/FileProvider.swift b/Sources/Configuration/Providers/Files/FileProvider.swift index 06afd44..8f6c425 100644 --- a/Sources/Configuration/Providers/Files/FileProvider.swift +++ b/Sources/Configuration/Providers/Files/FileProvider.swift @@ -98,11 +98,12 @@ public struct FileProvider: Sendable { /// and creates a snapshot from that file. /// /// ## Configuration keys + /// /// - `filePath` (string, required): The path to the configuration file to read. /// - `allowMissing` (bool, optional, default: false): A flag controlling how - /// the provider handles a missing file. - /// - When `false` (the default), if the file is missing or malformed, throws an error. - /// - When `true`, if the file is missing, treats it as empty. Malformed files still throw an error. + /// the provider handles a missing file. When `false` (the default), if the file + /// is missing or malformed, throws an error. When `true`, if the file is missing, + /// treats it as empty. Malformed files still throw an error. /// /// - Parameters: /// - snapshotType: The type of snapshot to create from the file contents. @@ -129,11 +130,12 @@ public struct FileProvider: Sendable { /// and creates a snapshot from that file. /// /// ## Configuration keys + /// /// - `filePath` (string, required): The path to the configuration file to read. /// - `allowMissing` (bool, optional, default: false): A flag controlling how - /// the provider handles a missing file. - /// - When `false` (the default), if the file is missing or malformed, throws an error. - /// - When `true`, if the file is missing, treats it as empty. Malformed files still throw an error. + /// the provider handles a missing file. When `false` (the default), if the file + /// is missing or malformed, throws an error. When `true`, if the file is missing, + /// treats it as empty. Malformed files still throw an error. /// /// - Parameters: /// - snapshotType: The type of snapshot to create from the file contents. diff --git a/Sources/Configuration/Providers/Files/FileProviderSnapshot.swift b/Sources/Configuration/Providers/Files/FileProviderSnapshot.swift index e77a554..13dcccb 100644 --- a/Sources/Configuration/Providers/Files/FileProviderSnapshot.swift +++ b/Sources/Configuration/Providers/Files/FileProviderSnapshot.swift @@ -16,25 +16,39 @@ import SystemPackage /// A type that provides parsing options for file configuration snapshots. /// -/// This protocol defines the requirements for parsing options types used when creating -/// file-based configuration snapshots. Types conforming to this protocol can provide -/// additional configuration or processing parameters that affect how file data is -/// interpreted and parsed. +/// This protocol defines the requirements for parsing options types used with ``FileConfigSnapshot`` +/// implementations. Types conforming to this protocol provide configuration parameters that control +/// how file data is interpreted and parsed during snapshot creation. +/// +/// The parsing options are passed to the ``FileConfigSnapshot/init(data:providerName:parsingOptions:)`` +/// initializer, allowing custom file format implementations to access format-specific parsing +/// settings such as character encoding, date formats, or validation rules. /// /// ## Usage /// -/// Implement this protocol to provide parsing options: +/// Implement this protocol to provide parsing options for your custom ``FileConfigSnapshot``: /// /// ```swift /// struct MyParsingOptions: FileParsingOptions { /// let encoding: String.Encoding /// let dateFormat: String? +/// let strictValidation: Bool /// /// static let `default` = MyParsingOptions( /// encoding: .utf8, -/// dateFormat: nil +/// dateFormat: nil, +/// strictValidation: false /// ) /// } +/// +/// struct MyFormatSnapshot: FileConfigSnapshot { +/// typealias ParsingOptions = MyParsingOptions +/// +/// init(data: RawSpan, providerName: String, parsingOptions: ParsingOptions) throws { +/// // Implementation that inspects `parsingOptions` properties like `encoding`, +/// // `dateFormat`, and `strictValidation`. +/// } +/// } /// ``` @available(Configuration 1.0, *) public protocol FileParsingOptions: Sendable { diff --git a/Sources/Configuration/Providers/Files/JSONSnapshot.swift b/Sources/Configuration/Providers/Files/JSONSnapshot.swift index 688e8ab..ae6bdd1 100644 --- a/Sources/Configuration/Providers/Files/JSONSnapshot.swift +++ b/Sources/Configuration/Providers/Files/JSONSnapshot.swift @@ -24,7 +24,14 @@ import Foundation /// This structure represents a point-in-time view of configuration values. It handles /// the conversion from JSON types to configuration value types. /// -/// Commonly used with ``FileProvider`` and ``ReloadingFileProvider``. +/// ## Usage +/// +/// Use with ``FileProvider`` or ``ReloadingFileProvider``: +/// +/// ```swift +/// let provider = try await FileProvider(filePath: "/etc/config.json") +/// let config = ConfigReader(provider: provider) +/// ``` @available(Configuration 1.0, *) public struct JSONSnapshot { diff --git a/Sources/Configuration/Providers/Files/ReloadingFileProvider.swift b/Sources/Configuration/Providers/Files/ReloadingFileProvider.swift index 31582db..5979faa 100644 --- a/Sources/Configuration/Providers/Files/ReloadingFileProvider.swift +++ b/Sources/Configuration/Providers/Files/ReloadingFileProvider.swift @@ -286,12 +286,14 @@ public final class ReloadingFileProvider: Sendable /// Creates a reloading file provider using configuration from a reader. /// /// ## Configuration keys + /// /// - `filePath` (string, required): The path to the configuration file to monitor. /// - `allowMissing` (bool, optional, default: false): A flag controlling how - /// the provider handles a missing file. - /// - When `false` (the default), if the file is missing or malformed, throws an error. - /// - When `true`, if the file is missing, treats it as empty. Malformed files still throw an error. - /// - `pollIntervalSeconds` (int, optional, default: 15): How often to check for file changes in seconds. + /// the provider handles a missing file. When `false` (the default), if the file + /// is missing or malformed, throws an error. When `true`, if the file is missing, + /// treats it as empty. Malformed files still throw an error. + /// - `pollIntervalSeconds` (int, optional, default: 15): How often to check for file + /// changes in seconds. /// /// - Parameters: /// - snapshotType: The type of snapshot to create from the file contents. @@ -320,12 +322,14 @@ public final class ReloadingFileProvider: Sendable /// Creates a reloading file provider using configuration from a reader. /// /// ## Configuration keys + /// /// - `filePath` (string, required): The path to the configuration file to monitor. /// - `allowMissing` (bool, optional, default: false): A flag controlling how - /// the provider handles a missing file. - /// - When `false` (the default), if the file is missing or malformed, throws an error. - /// - When `true`, if the file is missing, treats it as empty. Malformed files still throw an error. - /// - `pollIntervalSeconds` (int, optional, default: 15): How often to check for file changes in seconds. + /// the provider handles a missing file. When `false` (the default), if the file + /// is missing or malformed, throws an error. When `true`, if the file is missing, + /// treats it as empty. Malformed files still throw an error. + /// - `pollIntervalSeconds` (int, optional, default: 15): How often to check for file + /// changes in seconds. /// /// - Parameters: /// - snapshotType: The type of snapshot to create from the file contents. diff --git a/Sources/Configuration/Providers/Files/YAMLSnapshot.swift b/Sources/Configuration/Providers/Files/YAMLSnapshot.swift index 6a9fc44..95ffd1b 100644 --- a/Sources/Configuration/Providers/Files/YAMLSnapshot.swift +++ b/Sources/Configuration/Providers/Files/YAMLSnapshot.swift @@ -28,7 +28,14 @@ import SystemPackage /// This class represents a point-in-time view of configuration values. It handles /// the conversion from YAML types to configuration value types. /// -/// Commonly used with ``FileProvider`` and ``ReloadingFileProvider``. +/// ## Usage +/// +/// Use with ``FileProvider`` or ``ReloadingFileProvider``: +/// +/// ```swift +/// let provider = try await FileProvider(filePath: "/etc/config.yaml") +/// let config = ConfigReader(provider: provider) +/// ``` @available(Configuration 1.0, *) public final class YAMLSnapshot: Sendable {