From aa3718e791862dcdb82c7c57aab30160f0fd83ee Mon Sep 17 00:00:00 2001 From: Christer Date: Sun, 9 Nov 2025 13:56:29 +0100 Subject: [PATCH 1/4] docs(config): Added ValueSourceType doc comments --- packages/config/lib/src/config/source_type.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/config/lib/src/config/source_type.dart b/packages/config/lib/src/config/source_type.dart index b6058bc..9e500cb 100644 --- a/packages/config/lib/src/config/source_type.dart +++ b/packages/config/lib/src/config/source_type.dart @@ -1,10 +1,23 @@ /// The type of source that an option's value was resolved from. enum ValueSourceType { + /// The option has no value. noValue, + + /// The value was preset via code rather than normally resolved. preset, + + /// The value was parsed from command-line arguments. arg, + + /// The value was parsed from environment variables. envVar, + + /// The value was parsed from a configuration file. config, + + /// The value was provided by a custom callback. custom, + + /// The value is the default for the option. defaultValue, } From 62bcb5b504c881d587803ef01af23ba30e1c7f0c Mon Sep 17 00:00:00 2001 From: Christer Date: Sun, 9 Nov 2025 14:37:39 +0100 Subject: [PATCH 2/4] fix(config): Created a public OptionResolution interface Closes [config] `OptionResolution` is not exported #72 --- packages/config/lib/src/config/config.dart | 1 + .../config/lib/src/config/configuration.dart | 29 +++++---- .../lib/src/config/option_resolution.dart | 61 ++++++------------- .../src/config/option_resolution_impl.dart | 55 +++++++++++++++++ packages/config/lib/src/config/options.dart | 49 +++++++-------- 5 files changed, 114 insertions(+), 81 deletions(-) create mode 100644 packages/config/lib/src/config/option_resolution_impl.dart diff --git a/packages/config/lib/src/config/config.dart b/packages/config/lib/src/config/config.dart index 66bcc38..64a7a7b 100644 --- a/packages/config/lib/src/config/config.dart +++ b/packages/config/lib/src/config/config.dart @@ -8,6 +8,7 @@ export 'exceptions.dart'; export 'file_system_options.dart'; export 'multi_config_source.dart'; export 'option_groups.dart'; +export 'option_resolution.dart'; export 'option_types.dart'; export 'options.dart'; export 'output_formatting.dart' show formatConfigError; diff --git a/packages/config/lib/src/config/configuration.dart b/packages/config/lib/src/config/configuration.dart index 54ab1c3..e1d9f58 100644 --- a/packages/config/lib/src/config/configuration.dart +++ b/packages/config/lib/src/config/configuration.dart @@ -4,7 +4,7 @@ import 'package:collection/collection.dart'; import 'configuration_broker.dart'; import 'exceptions.dart'; -import 'option_resolution.dart'; +import 'option_resolution_impl.dart'; import 'options.dart'; import 'output_formatting.dart'; import 'source_type.dart'; @@ -43,7 +43,7 @@ import 'source_type.dart'; /// ``` class Configuration { final List _options; - final Map _config; + final Map _config; final List _errors; /// Creates a configuration with the provided option values. @@ -86,7 +86,7 @@ class Configuration { final Map? presetValues, final bool ignoreUnexpectedPositionalArgs = false, }) : _options = List.from(options), - _config = {}, + _config = {}, _errors = [] { if (argResults == null && args != null) { argResults = _prepareArgResults(args); @@ -118,7 +118,7 @@ class Configuration { final Map? presetValues, final bool ignoreUnexpectedPositionalArgs = false, }) : _options = List.from(options), - _config = {}, + _config = {}, _errors = [] { if (argResults == null && args != null) { argResults = _prepareArgResults(args); @@ -148,7 +148,8 @@ class Configuration { } on FormatException catch (e) { _errors.add(e.message); for (final o in _options) { - _config[o] = const OptionResolution.error('Previous ArgParser error'); + _config[o] = + const OptionResolutionImpl.error('Previous ArgParser error'); } return null; } @@ -270,7 +271,9 @@ class Configuration { return resolution.source; } - OptionResolution _getOptionResolution(final OptionDefinition option) { + OptionResolutionImpl _getOptionResolution( + final OptionDefinition option, + ) { if (!_options.contains(option)) { throw ArgumentError( '${option.qualifiedString()} is not part of this configuration'); @@ -302,10 +305,10 @@ class Configuration { final orderedOpts = _options.sorted((final a, final b) => (a.option.argPos ?? -1).compareTo(b.option.argPos ?? -1)); - final optionGroups = >{}; + final optionGroups = >{}; for (final opt in orderedOpts) { - OptionResolution resolution; + OptionResolutionImpl resolution; try { if (presetValues != null && presetValues.containsKey(opt)) { resolution = _resolvePresetValue(opt, presetValues[opt]); @@ -336,7 +339,7 @@ class Configuration { // Represents an option resolution that depends on another option // whose resolution failed, so this resolution fails in turn. // Not adding to _errors to avoid double reporting. - resolution = OptionResolution.error(e.message); + resolution = OptionResolutionImpl.error(e.message); } _config[opt] = resolution; @@ -351,13 +354,13 @@ class Configuration { } } - OptionResolution _resolvePresetValue( + OptionResolutionImpl _resolvePresetValue( final O option, final Object? value, ) { final resolution = value == null - ? const OptionResolution.noValue() - : OptionResolution(value: value, source: ValueSourceType.preset); + ? const OptionResolutionImpl.noValue() + : OptionResolutionImpl(value: value, source: ValueSourceType.preset); final error = option.option.validateOptionValue(value); if (error != null) return resolution.copyWithError(error); @@ -365,7 +368,7 @@ class Configuration { } void _validateGroups( - final Map> optionGroups, + final Map> optionGroups, ) { optionGroups.forEach((final group, final optionResolutions) { final error = group.validateValues(optionResolutions); diff --git a/packages/config/lib/src/config/option_resolution.dart b/packages/config/lib/src/config/option_resolution.dart index bba7af4..733761b 100644 --- a/packages/config/lib/src/config/option_resolution.dart +++ b/packages/config/lib/src/config/option_resolution.dart @@ -1,51 +1,24 @@ import 'source_type.dart'; -final class OptionResolution { - final String? stringValue; - final V? value; - final String? error; - final ValueSourceType source; - - const OptionResolution._({ - required this.source, - this.stringValue, - this.value, - this.error, - }); - - const OptionResolution({ - required this.source, - this.stringValue, - this.value, - }) : error = null; - - const OptionResolution.noValue() - : source = ValueSourceType.noValue, - stringValue = null, - value = null, - error = null; - - const OptionResolution.error(this.error) - : source = ValueSourceType.noValue, - stringValue = null, - value = null; - - OptionResolution copyWithValue(final V newValue) => OptionResolution._( - source: source, - stringValue: stringValue, - value: newValue, - error: error, - ); - - OptionResolution copyWithError(final String error) => OptionResolution._( - source: source, - stringValue: stringValue, - value: value, - error: error, - ); +/// Describes the resolution of a configuration option. +abstract class OptionResolution { + const OptionResolution(); + + /// The resolved value of the option, or null if there was no value + /// or if there was an error. + V? get value; + + /// The error message if the option was not succesfully resolved. + String? get error; + + /// The source type of the option's value. + ValueSourceType get source; + + /// Whether the option was not succesfully resolved. + bool get hasError => error != null; /// Whether the option has a proper value (without errors). - bool get hasValue => source != ValueSourceType.noValue && error == null; + bool get hasValue => source != ValueSourceType.noValue && !hasError; /// Whether the option has a value that was specified explicitly (not default). bool get isSpecified => hasValue && source != ValueSourceType.defaultValue; diff --git a/packages/config/lib/src/config/option_resolution_impl.dart b/packages/config/lib/src/config/option_resolution_impl.dart new file mode 100644 index 0000000..fda2a3b --- /dev/null +++ b/packages/config/lib/src/config/option_resolution_impl.dart @@ -0,0 +1,55 @@ +import 'option_resolution.dart'; +import 'source_type.dart'; + +final class OptionResolutionImpl extends OptionResolution { + final String? stringValue; + + @override + final V? value; + + @override + final String? error; + + @override + final ValueSourceType source; + + const OptionResolutionImpl._({ + required this.source, + this.stringValue, + this.value, + this.error, + }); + + const OptionResolutionImpl({ + required this.source, + this.stringValue, + this.value, + }) : error = null; + + const OptionResolutionImpl.noValue() + : source = ValueSourceType.noValue, + stringValue = null, + value = null, + error = null; + + const OptionResolutionImpl.error(this.error) + : source = ValueSourceType.noValue, + stringValue = null, + value = null; + + OptionResolutionImpl copyWithValue(final V newValue) => + OptionResolutionImpl._( + source: source, + stringValue: stringValue, + value: newValue, + error: error, + ); + + OptionResolutionImpl copyWithError(final String error) => + OptionResolutionImpl._( + source: source, + stringValue: stringValue, + value: value, + error: error, + ); +} diff --git a/packages/config/lib/src/config/options.dart b/packages/config/lib/src/config/options.dart index 00d0bad..662fb14 100644 --- a/packages/config/lib/src/config/options.dart +++ b/packages/config/lib/src/config/options.dart @@ -7,6 +7,7 @@ import 'configuration.dart' show Configuration; import 'configuration_broker.dart'; import 'exceptions.dart'; import 'option_resolution.dart'; +import 'option_resolution_impl.dart'; import 'source_type.dart'; /// Common interface to enable same treatment for [ConfigOptionBase] @@ -325,14 +326,14 @@ abstract class ConfigOptionBase implements OptionDefinition { /// Returns the result with the resolved value or error. /// /// This method is intended for internal use. - OptionResolution resolveValue( + OptionResolutionImpl resolveValue( final Configuration cfg, { final ArgResults? args, final Iterator? posArgs, final Map? env, final ConfigurationBroker? configBroker, }) { - OptionResolution res; + OptionResolutionImpl res; try { res = _doResolve( cfg, @@ -342,7 +343,7 @@ abstract class ConfigOptionBase implements OptionDefinition { configBroker: configBroker, ); } on Exception catch (e) { - return OptionResolution.error( + return OptionResolutionImpl.error( 'Failed to resolve ${option.qualifiedString()}: $e', ); } @@ -371,14 +372,14 @@ abstract class ConfigOptionBase implements OptionDefinition { return res; } - OptionResolution _doResolve( + OptionResolutionImpl _doResolve( final Configuration cfg, { final ArgResults? args, final Iterator? posArgs, final Map? env, final ConfigurationBroker? configBroker, }) { - OptionResolution? result; + OptionResolutionImpl? result; result = _resolveNamedArg(args); if (result != null) return result; @@ -398,42 +399,42 @@ abstract class ConfigOptionBase implements OptionDefinition { result = _resolveDefaultValue(); if (result != null) return result; - return const OptionResolution.noValue(); + return const OptionResolutionImpl.noValue(); } - OptionResolution? _resolveNamedArg(final ArgResults? args) { + OptionResolutionImpl? _resolveNamedArg(final ArgResults? args) { final argOptName = argName; if (argOptName == null || args == null || !args.wasParsed(argOptName)) { return null; } - return OptionResolution( + return OptionResolutionImpl( stringValue: args.option(argOptName), source: ValueSourceType.arg, ); } - OptionResolution? _resolvePosArg(final Iterator? posArgs) { + OptionResolutionImpl? _resolvePosArg(final Iterator? posArgs) { final argOptPos = argPos; if (argOptPos == null || posArgs == null) return null; if (!posArgs.moveNext()) return null; - return OptionResolution( + return OptionResolutionImpl( stringValue: posArgs.current, source: ValueSourceType.arg, ); } - OptionResolution? _resolveEnvVar(final Map? env) { + OptionResolutionImpl? _resolveEnvVar(final Map? env) { final envVarName = envName; if (envVarName == null || env == null || !env.containsKey(envVarName)) { return null; } - return OptionResolution( + return OptionResolutionImpl( stringValue: env[envVarName], source: ValueSourceType.envVar, ); } - OptionResolution? _resolveConfigValue( + OptionResolutionImpl? _resolveConfigValue( final Configuration cfg, final ConfigurationBroker? configBroker, ) { @@ -442,36 +443,36 @@ abstract class ConfigOptionBase implements OptionDefinition { final value = configBroker.valueOrNull(key, cfg); if (value == null) return null; if (value is String) { - return OptionResolution( + return OptionResolutionImpl( stringValue: value, source: ValueSourceType.config, ); } if (value is V) { - return OptionResolution( + return OptionResolutionImpl( value: value as V, source: ValueSourceType.config, ); } - return OptionResolution.error( + return OptionResolutionImpl.error( '${option.qualifiedString()} value $value ' 'is of type ${value.runtimeType}, not $V.', ); } - OptionResolution? _resolveCustomValue(final Configuration cfg) { + OptionResolutionImpl? _resolveCustomValue(final Configuration cfg) { final value = fromCustom?.call(cfg); if (value == null) return null; - return OptionResolution( + return OptionResolutionImpl( value: value, source: ValueSourceType.custom, ); } - OptionResolution? _resolveDefaultValue() { + OptionResolutionImpl? _resolveDefaultValue() { final value = fromDefault?.call() ?? defaultsTo; if (value == null) return null; - return OptionResolution( + return OptionResolutionImpl( value: value, source: ValueSourceType.defaultValue, ); @@ -567,12 +568,12 @@ class FlagOption extends ConfigOptionBase { } @override - OptionResolution? _resolveNamedArg(final ArgResults? args) { + OptionResolutionImpl? _resolveNamedArg(final ArgResults? args) { final argOptName = argName; if (argOptName == null || args == null || !args.wasParsed(argOptName)) { return null; } - return OptionResolution( + return OptionResolutionImpl( value: args.flag(argOptName), source: ValueSourceType.arg, ); @@ -670,13 +671,13 @@ class MultiOption extends ConfigOptionBase> { } @override - OptionResolution>? _resolveNamedArg(final ArgResults? args) { + OptionResolutionImpl>? _resolveNamedArg(final ArgResults? args) { final argOptName = argName; if (argOptName == null || args == null || !args.wasParsed(argOptName)) { return null; } final multiParser = valueParser as MultiParser; - return OptionResolution( + return OptionResolutionImpl( value: args .multiOption(argOptName) .map(multiParser.elementParser.parse) From 8c2a27f667498ee612504d573e96fc3ded6db9fa Mon Sep 17 00:00:00 2001 From: Christer Date: Sun, 9 Nov 2025 18:32:25 +0100 Subject: [PATCH 3/4] fix(config): Corrected typos in doc comments --- packages/config/lib/src/config/option_resolution.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config/lib/src/config/option_resolution.dart b/packages/config/lib/src/config/option_resolution.dart index 733761b..92389f3 100644 --- a/packages/config/lib/src/config/option_resolution.dart +++ b/packages/config/lib/src/config/option_resolution.dart @@ -8,13 +8,13 @@ abstract class OptionResolution { /// or if there was an error. V? get value; - /// The error message if the option was not succesfully resolved. + /// The error message if the option was not successfully resolved. String? get error; /// The source type of the option's value. ValueSourceType get source; - /// Whether the option was not succesfully resolved. + /// Whether the option was not successfully resolved. bool get hasError => error != null; /// Whether the option has a proper value (without errors). From 44913d5250d0b2a3905c80d501d1154a045625fc Mon Sep 17 00:00:00 2001 From: Christer Date: Sun, 9 Nov 2025 18:40:45 +0100 Subject: [PATCH 4/4] refactor(config): Explicitly marked internal methods @internal --- packages/config/lib/src/config/options.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/config/lib/src/config/options.dart b/packages/config/lib/src/config/options.dart index 662fb14..a39ab9c 100644 --- a/packages/config/lib/src/config/options.dart +++ b/packages/config/lib/src/config/options.dart @@ -326,6 +326,7 @@ abstract class ConfigOptionBase implements OptionDefinition { /// Returns the result with the resolved value or error. /// /// This method is intended for internal use. + @internal OptionResolutionImpl resolveValue( final Configuration cfg, { final ArgResults? args, @@ -481,6 +482,7 @@ abstract class ConfigOptionBase implements OptionDefinition { /// Returns an error message if the value is invalid, or null if valid. /// /// This method is intended for internal use. + @internal String? validateOptionValue(final V? value) { if (value == null && mandatory) { return '${qualifiedString()} is mandatory';