Skip to content

Commit ed5c5f2

Browse files
fix(config): Created a public OptionResolution interface (#91)
The class `OptionResolution` was internal, yet used in a public and overridable method `OptionGroup.validateValues`. This PR breaks out a public interface now called `OptionResolution` and exported from the package. The internal implementation has been moved to `OptionResolutionImpl`. Closes [config] `OptionResolution` is not exported [#72](#72) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added documentation comments for option value source types. * **Refactor** * Reworked option-resolution internals: introduced an abstract resolution interface plus a concrete implementation and updated exported resolution types for consistent, immutable value/error/source handling; no user-visible behavioral changes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 4c78b60 commit ed5c5f2

File tree

6 files changed

+129
-81
lines changed

6 files changed

+129
-81
lines changed

packages/config/lib/src/config/config.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export 'exceptions.dart';
88
export 'file_system_options.dart';
99
export 'multi_config_source.dart';
1010
export 'option_groups.dart';
11+
export 'option_resolution.dart';
1112
export 'option_types.dart';
1213
export 'options.dart';
1314
export 'output_formatting.dart' show formatConfigError;

packages/config/lib/src/config/configuration.dart

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:collection/collection.dart';
44

55
import 'configuration_broker.dart';
66
import 'exceptions.dart';
7-
import 'option_resolution.dart';
7+
import 'option_resolution_impl.dart';
88
import 'options.dart';
99
import 'output_formatting.dart';
1010
import 'source_type.dart';
@@ -43,7 +43,7 @@ import 'source_type.dart';
4343
/// ```
4444
class Configuration<O extends OptionDefinition> {
4545
final List<O> _options;
46-
final Map<O, OptionResolution> _config;
46+
final Map<O, OptionResolutionImpl> _config;
4747
final List<String> _errors;
4848

4949
/// Creates a configuration with the provided option values.
@@ -86,7 +86,7 @@ class Configuration<O extends OptionDefinition> {
8686
final Map<O, Object?>? presetValues,
8787
final bool ignoreUnexpectedPositionalArgs = false,
8888
}) : _options = List<O>.from(options),
89-
_config = <O, OptionResolution>{},
89+
_config = <O, OptionResolutionImpl>{},
9090
_errors = <String>[] {
9191
if (argResults == null && args != null) {
9292
argResults = _prepareArgResults(args);
@@ -118,7 +118,7 @@ class Configuration<O extends OptionDefinition> {
118118
final Map<O, Object?>? presetValues,
119119
final bool ignoreUnexpectedPositionalArgs = false,
120120
}) : _options = List<O>.from(options),
121-
_config = <O, OptionResolution>{},
121+
_config = <O, OptionResolutionImpl>{},
122122
_errors = <String>[] {
123123
if (argResults == null && args != null) {
124124
argResults = _prepareArgResults(args);
@@ -148,7 +148,8 @@ class Configuration<O extends OptionDefinition> {
148148
} on FormatException catch (e) {
149149
_errors.add(e.message);
150150
for (final o in _options) {
151-
_config[o] = const OptionResolution.error('Previous ArgParser error');
151+
_config[o] =
152+
const OptionResolutionImpl.error('Previous ArgParser error');
152153
}
153154
return null;
154155
}
@@ -270,7 +271,9 @@ class Configuration<O extends OptionDefinition> {
270271
return resolution.source;
271272
}
272273

273-
OptionResolution _getOptionResolution<V>(final OptionDefinition<V> option) {
274+
OptionResolutionImpl _getOptionResolution<V>(
275+
final OptionDefinition<V> option,
276+
) {
274277
if (!_options.contains(option)) {
275278
throw ArgumentError(
276279
'${option.qualifiedString()} is not part of this configuration');
@@ -302,10 +305,10 @@ class Configuration<O extends OptionDefinition> {
302305
final orderedOpts = _options.sorted((final a, final b) =>
303306
(a.option.argPos ?? -1).compareTo(b.option.argPos ?? -1));
304307

305-
final optionGroups = <OptionGroup, Map<O, OptionResolution>>{};
308+
final optionGroups = <OptionGroup, Map<O, OptionResolutionImpl>>{};
306309

307310
for (final opt in orderedOpts) {
308-
OptionResolution resolution;
311+
OptionResolutionImpl resolution;
309312
try {
310313
if (presetValues != null && presetValues.containsKey(opt)) {
311314
resolution = _resolvePresetValue(opt, presetValues[opt]);
@@ -336,7 +339,7 @@ class Configuration<O extends OptionDefinition> {
336339
// Represents an option resolution that depends on another option
337340
// whose resolution failed, so this resolution fails in turn.
338341
// Not adding to _errors to avoid double reporting.
339-
resolution = OptionResolution.error(e.message);
342+
resolution = OptionResolutionImpl.error(e.message);
340343
}
341344

342345
_config[opt] = resolution;
@@ -351,21 +354,21 @@ class Configuration<O extends OptionDefinition> {
351354
}
352355
}
353356

354-
OptionResolution _resolvePresetValue(
357+
OptionResolutionImpl _resolvePresetValue(
355358
final O option,
356359
final Object? value,
357360
) {
358361
final resolution = value == null
359-
? const OptionResolution.noValue()
360-
: OptionResolution(value: value, source: ValueSourceType.preset);
362+
? const OptionResolutionImpl.noValue()
363+
: OptionResolutionImpl(value: value, source: ValueSourceType.preset);
361364

362365
final error = option.option.validateOptionValue(value);
363366
if (error != null) return resolution.copyWithError(error);
364367
return resolution;
365368
}
366369

367370
void _validateGroups(
368-
final Map<OptionGroup, Map<O, OptionResolution>> optionGroups,
371+
final Map<OptionGroup, Map<O, OptionResolutionImpl>> optionGroups,
369372
) {
370373
optionGroups.forEach((final group, final optionResolutions) {
371374
final error = group.validateValues(optionResolutions);

packages/config/lib/src/config/option_resolution.dart

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,24 @@
11
import 'source_type.dart';
22

3-
final class OptionResolution<V> {
4-
final String? stringValue;
5-
final V? value;
6-
final String? error;
7-
final ValueSourceType source;
8-
9-
const OptionResolution._({
10-
required this.source,
11-
this.stringValue,
12-
this.value,
13-
this.error,
14-
});
15-
16-
const OptionResolution({
17-
required this.source,
18-
this.stringValue,
19-
this.value,
20-
}) : error = null;
21-
22-
const OptionResolution.noValue()
23-
: source = ValueSourceType.noValue,
24-
stringValue = null,
25-
value = null,
26-
error = null;
27-
28-
const OptionResolution.error(this.error)
29-
: source = ValueSourceType.noValue,
30-
stringValue = null,
31-
value = null;
32-
33-
OptionResolution<V> copyWithValue(final V newValue) => OptionResolution._(
34-
source: source,
35-
stringValue: stringValue,
36-
value: newValue,
37-
error: error,
38-
);
39-
40-
OptionResolution<V> copyWithError(final String error) => OptionResolution._(
41-
source: source,
42-
stringValue: stringValue,
43-
value: value,
44-
error: error,
45-
);
3+
/// Describes the resolution of a configuration option.
4+
abstract class OptionResolution<V> {
5+
const OptionResolution();
6+
7+
/// The resolved value of the option, or null if there was no value
8+
/// or if there was an error.
9+
V? get value;
10+
11+
/// The error message if the option was not successfully resolved.
12+
String? get error;
13+
14+
/// The source type of the option's value.
15+
ValueSourceType get source;
16+
17+
/// Whether the option was not successfully resolved.
18+
bool get hasError => error != null;
4619

4720
/// Whether the option has a proper value (without errors).
48-
bool get hasValue => source != ValueSourceType.noValue && error == null;
21+
bool get hasValue => source != ValueSourceType.noValue && !hasError;
4922

5023
/// Whether the option has a value that was specified explicitly (not default).
5124
bool get isSpecified => hasValue && source != ValueSourceType.defaultValue;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'option_resolution.dart';
2+
import 'source_type.dart';
3+
4+
final class OptionResolutionImpl<V> extends OptionResolution<V> {
5+
final String? stringValue;
6+
7+
@override
8+
final V? value;
9+
10+
@override
11+
final String? error;
12+
13+
@override
14+
final ValueSourceType source;
15+
16+
const OptionResolutionImpl._({
17+
required this.source,
18+
this.stringValue,
19+
this.value,
20+
this.error,
21+
});
22+
23+
const OptionResolutionImpl({
24+
required this.source,
25+
this.stringValue,
26+
this.value,
27+
}) : error = null;
28+
29+
const OptionResolutionImpl.noValue()
30+
: source = ValueSourceType.noValue,
31+
stringValue = null,
32+
value = null,
33+
error = null;
34+
35+
const OptionResolutionImpl.error(this.error)
36+
: source = ValueSourceType.noValue,
37+
stringValue = null,
38+
value = null;
39+
40+
OptionResolutionImpl<V> copyWithValue(final V newValue) =>
41+
OptionResolutionImpl._(
42+
source: source,
43+
stringValue: stringValue,
44+
value: newValue,
45+
error: error,
46+
);
47+
48+
OptionResolutionImpl<V> copyWithError(final String error) =>
49+
OptionResolutionImpl._(
50+
source: source,
51+
stringValue: stringValue,
52+
value: value,
53+
error: error,
54+
);
55+
}

0 commit comments

Comments
 (0)