diff --git a/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json b/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json index ac6528a099..9fea64ad43 100644 --- a/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json +++ b/pkgs/code_assets/doc/schema/shared/shared_definitions.schema.json @@ -239,19 +239,12 @@ "properties": { "type": { "type": "string", - "anyOf": [ - { - "enum": [ - "dynamic_loading_bundle", - "dynamic_loading_executable", - "dynamic_loading_process", - "dynamic_loading_system", - "static" - ] - }, - { - "type": "string" - } + "enum": [ + "dynamic_loading_bundle", + "dynamic_loading_executable", + "dynamic_loading_process", + "dynamic_loading_system", + "static" ] } }, @@ -278,18 +271,11 @@ }, "LinkModePreference": { "type": "string", - "anyOf": [ - { - "enum": [ - "dynamic", - "prefer_dynamic", - "prefer_static", - "static" - ] - }, - { - "type": "string" - } + "enum": [ + "dynamic", + "prefer_dynamic", + "prefer_static", + "static" ] }, "MacOSCodeConfig": { diff --git a/pkgs/code_assets/lib/src/code_assets/link_mode.dart b/pkgs/code_assets/lib/src/code_assets/link_mode.dart index bd5ffa8daf..bafa2ede4e 100644 --- a/pkgs/code_assets/lib/src/code_assets/link_mode.dart +++ b/pkgs/code_assets/lib/src/code_assets/link_mode.dart @@ -18,7 +18,7 @@ import 'syntax.g.dart'; /// * [DynamicLoadingSystem] /// /// See the documentation on the above classes. -abstract final class LinkMode { +sealed class LinkMode { const LinkMode._(); /// Constructs a [LinkMode] from the given [json]. @@ -46,7 +46,6 @@ extension LinkModeSyntaxExtension on LinkMode { final DynamicLoadingSystem system => DynamicLoadingSystemLinkModeSyntax( uri: system.uri, ), - _ => throw UnimplementedError('The link mode "$this" is not known'), }; /// Converts a [LinkModeSyntax] to its corresponding [LinkMode] @@ -59,7 +58,7 @@ extension LinkModeSyntaxExtension on LinkMode { _ when linkMode.isDynamicLoadingSystemLinkMode => DynamicLoadingSystem( linkMode.asDynamicLoadingSystemLinkMode.uri, ), - _ => throw FormatException('The link mode "${linkMode.type}" is not known'), + _ => throw FormatException('Unknown LinkMode type: ${linkMode.type}'), }; } @@ -72,7 +71,7 @@ extension LinkModeSyntaxExtension on LinkMode { /// Note: Dynamic loading is not equal to dynamic linking. Dynamic linking /// would have to run the linker at compile-time, which is currently not /// supported in the Dart and Flutter SDK. -abstract final class DynamicLoading extends LinkMode { +sealed class DynamicLoading extends LinkMode { DynamicLoading._() : super._(); } diff --git a/pkgs/code_assets/lib/src/code_assets/link_mode_preference.dart b/pkgs/code_assets/lib/src/code_assets/link_mode_preference.dart index f92ca4e4f7..137f3cb396 100644 --- a/pkgs/code_assets/lib/src/code_assets/link_mode_preference.dart +++ b/pkgs/code_assets/lib/src/code_assets/link_mode_preference.dart @@ -50,6 +50,13 @@ final class LinkModePreference { /// All possible values for [LinkModePreference]. static const values = [dynamic, static, preferDynamic, preferStatic]; + @override + bool operator ==(Object other) => + other is LinkModePreference && other.name == name; + + @override + int get hashCode => name.hashCode; + @override String toString() => name; } @@ -57,14 +64,14 @@ final class LinkModePreference { /// Extension methods for [LinkModePreference] to convert to and from the /// syntax model. extension LinkModePreferenceSyntaxExtension on LinkModePreference { - static const _toSyntax = { + static final _toSyntax = { LinkModePreference.dynamic: LinkModePreferenceSyntax.dynamic, LinkModePreference.preferDynamic: LinkModePreferenceSyntax.preferDynamic, LinkModePreference.preferStatic: LinkModePreferenceSyntax.preferStatic, LinkModePreference.static: LinkModePreferenceSyntax.static, }; - static const _fromSyntax = { + static final _fromSyntax = { LinkModePreferenceSyntax.dynamic: LinkModePreference.dynamic, LinkModePreferenceSyntax.preferDynamic: LinkModePreference.preferDynamic, LinkModePreferenceSyntax.preferStatic: LinkModePreference.preferStatic, diff --git a/pkgs/code_assets/lib/src/code_assets/syntax.g.dart b/pkgs/code_assets/lib/src/code_assets/syntax.g.dart index 51067a4fa9..70c818aff1 100644 --- a/pkgs/code_assets/lib/src/code_assets/syntax.g.dart +++ b/pkgs/code_assets/lib/src/code_assets/syntax.g.dart @@ -68,7 +68,10 @@ class ArchitectureSyntax { ArchitectureSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory ArchitectureSyntax.fromJson(String name) { + factory ArchitectureSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -277,7 +280,10 @@ class CodeConfigSyntax extends JsonObjectSyntax { LinkModePreferenceSyntax get linkModePreference { final jsonValue = _reader.get('link_mode_preference'); - return LinkModePreferenceSyntax.fromJson(jsonValue); + return LinkModePreferenceSyntax.fromJson( + jsonValue, + path: [...path, 'link_mode_preference'], + ); } set _linkModePreference(LinkModePreferenceSyntax value) { @@ -308,7 +314,7 @@ class CodeConfigSyntax extends JsonObjectSyntax { SanitizerSyntax? get sanitizer { final jsonValue = _reader.get('sanitizer'); if (jsonValue == null) return null; - return SanitizerSyntax.fromJson(jsonValue); + return SanitizerSyntax.fromJson(jsonValue, path: [...path, 'sanitizer']); } set _sanitizer(SanitizerSyntax? value) { @@ -319,7 +325,10 @@ class CodeConfigSyntax extends JsonObjectSyntax { ArchitectureSyntax get targetArchitecture { final jsonValue = _reader.get('target_architecture'); - return ArchitectureSyntax.fromJson(jsonValue); + return ArchitectureSyntax.fromJson( + jsonValue, + path: [...path, 'target_architecture'], + ); } set _targetArchitecture(ArchitectureSyntax value) { @@ -331,7 +340,7 @@ class CodeConfigSyntax extends JsonObjectSyntax { OSSyntax get targetOs { final jsonValue = _reader.get('target_os'); - return OSSyntax.fromJson(jsonValue); + return OSSyntax.fromJson(jsonValue, path: [...path, 'target_os']); } set _targetOs(OSSyntax value) { @@ -671,7 +680,17 @@ class LinkModeSyntax extends JsonObjectSyntax { if (result.isStaticLinkMode) { return result.asStaticLinkMode; } - return result; + _throwFormatException( + result.type, + [...path, 'type'], + expectedValues: { + 'dynamic_loading_bundle', + 'dynamic_loading_executable', + 'dynamic_loading_process', + 'dynamic_loading_system', + 'static', + }, + ); } LinkModeSyntax._fromJson(super.json, {super.path = const []}) @@ -721,15 +740,15 @@ class LinkModePreferenceSyntax { for (final value in values) value.name: value, }; - LinkModePreferenceSyntax.unknown(this.name) - : assert(!_byName.keys.contains(name)); - - factory LinkModePreferenceSyntax.fromJson(String name) { + factory LinkModePreferenceSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; } - return LinkModePreferenceSyntax.unknown(name); + _throwFormatException(name, path, expectedValues: _byName.keys.toSet()); } bool get isKnown => _byName[name] != null; @@ -916,7 +935,7 @@ class OSSyntax { OSSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory OSSyntax.fromJson(String name) { + factory OSSyntax.fromJson(String name, {List path = const []}) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -949,7 +968,10 @@ class SanitizerSyntax { SanitizerSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory SanitizerSyntax.fromJson(String name) { + factory SanitizerSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -1057,14 +1079,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -1101,7 +1123,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -1114,7 +1136,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -1182,7 +1204,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -1212,7 +1234,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -1229,7 +1251,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -1239,7 +1266,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -1248,7 +1280,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -1261,7 +1298,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -1276,7 +1313,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -1326,30 +1363,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -1414,6 +1452,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/code_assets/lib/src/code_assets/validation.dart b/pkgs/code_assets/lib/src/code_assets/validation.dart index 0b328cf9b7..067d22bdbc 100644 --- a/pkgs/code_assets/lib/src/code_assets/validation.dart +++ b/pkgs/code_assets/lib/src/code_assets/validation.dart @@ -262,7 +262,6 @@ bool _mustHaveFile(LinkMode linkMode) => switch (linkMode) { DynamicLoadingSystem _ => false, DynamicLoadingBundled _ => true, StaticLinking _ => true, - _ => throw UnsupportedError('Unknown link mode: $linkMode.'), }; void _validateNoDuplicateDylibNames( diff --git a/pkgs/code_assets/test/code_assets/asset_test.dart b/pkgs/code_assets/test/code_assets/asset_test.dart index 4cdbba189d..7e2830facf 100644 --- a/pkgs/code_assets/test/code_assets/asset_test.dart +++ b/pkgs/code_assets/test/code_assets/asset_test.dart @@ -113,7 +113,11 @@ void main() { predicate( (e) => e is FormatException && - e.message.contains('The link mode "wrong" is not known'), + e.message.contains( + "Unexpected value 'wrong' (String) for 'type'.", + ) && + e.message.contains("'dynamic_loading_bundle'") && + e.message.contains("'static'"), ), ), ); diff --git a/pkgs/code_assets/test/code_assets/link_mode_test.dart b/pkgs/code_assets/test/code_assets/link_mode_test.dart index 0977b56c84..6ea1d60a3b 100644 --- a/pkgs/code_assets/test/code_assets/link_mode_test.dart +++ b/pkgs/code_assets/test/code_assets/link_mode_test.dart @@ -3,10 +3,60 @@ // BSD-style license that can be found in the LICENSE file. import 'package:code_assets/code_assets.dart'; +import 'package:code_assets/src/code_assets/syntax.g.dart'; import 'package:test/test.dart'; void main() { test('LinkMode toString', () async { StaticLinking().toString(); }); + + test('Unknown LinkMode throws FormatException', () async { + expect( + () => LinkMode.fromJson({ + 'type': 'my_custom_link_mode', + 'extra_data': 'some_value', + }), + throwsA(isA()), + ); + }); + + test('Unknown LinkModePreference throws FormatException', () async { + expect( + () => LinkModePreference.fromString('custom_pref'), + throwsA( + predicate( + (e) => + e is FormatException && + e.message.contains( + "Unexpected value 'custom_pref' (String) for ''." + " Expected one of 'dynamic', 'prefer_dynamic'," + " 'prefer_static', 'static'.", + ), + ), + ), + ); + }); + + test( + 'Unknown LinkModePreferenceSyntax throws FormatException with JSON path', + () async { + expect( + () => LinkModePreferenceSyntax.fromJson( + 'custom_pref', + path: ['code', 'link_mode_preference'], + ), + throwsA( + predicate( + (e) => + e is FormatException && + e.message.contains( + "Unexpected value 'custom_pref' (String) for" + " 'code.link_mode_preference'.", + ), + ), + ), + ); + }, + ); } diff --git a/pkgs/data_assets/lib/src/data_assets/syntax.g.dart b/pkgs/data_assets/lib/src/data_assets/syntax.g.dart index 77d8fafe21..c80d8f7eb3 100644 --- a/pkgs/data_assets/lib/src/data_assets/syntax.g.dart +++ b/pkgs/data_assets/lib/src/data_assets/syntax.g.dart @@ -181,14 +181,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -225,7 +225,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -238,7 +238,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -306,7 +306,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -336,7 +336,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -353,7 +353,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -363,7 +368,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -372,7 +382,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -385,7 +400,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -400,7 +415,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -450,30 +465,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -538,6 +554,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/hooks/lib/src/hooks/syntax.g.dart b/pkgs/hooks/lib/src/hooks/syntax.g.dart index 5869f8376b..ee67c3030c 100644 --- a/pkgs/hooks/lib/src/hooks/syntax.g.dart +++ b/pkgs/hooks/lib/src/hooks/syntax.g.dart @@ -359,7 +359,7 @@ class FailureSyntax extends JsonObjectSyntax { FailureTypeSyntax get type { final jsonValue = _reader.get('type'); - return FailureTypeSyntax.fromJson(jsonValue); + return FailureTypeSyntax.fromJson(jsonValue, path: [...path, 'type']); } set _type(FailureTypeSyntax value) { @@ -394,7 +394,10 @@ class FailureTypeSyntax { FailureTypeSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory FailureTypeSyntax.fromJson(String name) { + factory FailureTypeSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -666,7 +669,7 @@ class HookOutputSyntax extends JsonObjectSyntax { OutputStatusSyntax? get status { final jsonValue = _reader.get('status'); if (jsonValue == null) return null; - return OutputStatusSyntax.fromJson(jsonValue); + return OutputStatusSyntax.fromJson(jsonValue, path: [...path, 'status']); } set status(OutputStatusSyntax? value) { @@ -959,7 +962,10 @@ class OutputStatusSyntax { OutputStatusSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory OutputStatusSyntax.fromJson(String name) { + factory OutputStatusSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -1098,14 +1104,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -1142,7 +1148,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -1155,7 +1161,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -1223,7 +1229,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -1253,7 +1259,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -1270,7 +1276,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -1280,7 +1291,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -1289,7 +1305,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -1302,7 +1323,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -1317,7 +1338,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -1367,30 +1388,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -1455,6 +1477,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/hooks_runner/lib/src/hooks_runner/syntax.g.dart b/pkgs/hooks_runner/lib/src/hooks_runner/syntax.g.dart index 9c77aa3363..480cd7f5ab 100644 --- a/pkgs/hooks_runner/lib/src/hooks_runner/syntax.g.dart +++ b/pkgs/hooks_runner/lib/src/hooks_runner/syntax.g.dart @@ -212,14 +212,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -256,7 +256,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -269,7 +269,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -337,7 +337,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -367,7 +367,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -384,7 +384,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -394,7 +399,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -403,7 +413,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -416,7 +431,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -431,7 +446,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -481,30 +496,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -569,6 +585,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/json_syntax_generator/lib/src/generator/enum_class_generator.dart b/pkgs/json_syntax_generator/lib/src/generator/enum_class_generator.dart index 5ece46af49..5b5823084d 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/enum_class_generator.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/enum_class_generator.dart @@ -23,6 +23,16 @@ class EnumGenerator { ); } + final unknownConstructor = classInfo.isOpen + ? ''' +$className.unknown(this.name) : assert(!_byName.keys.contains(name));''' + : ''; + final unknownReturn = classInfo.isOpen + ? ''' +return $className.unknown(name);''' + : ''' +_throwFormatException(name, path, expectedValues: _byName.keys.toSet());'''; + buffer.writeln(''' class $className { final String name; @@ -39,14 +49,17 @@ class $className { for (final value in values) value.name: value, }; - $className.unknown(this.name) : assert(!_byName.keys.contains(name)); + $unknownConstructor - factory $className.fromJson(String name) { + factory $className.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if(knownValue != null) { return knownValue; } - return $className.unknown(name); + $unknownReturn } bool get isKnown => _byName[name] != null; diff --git a/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart b/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart index 2df882144f..b76752bba4 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart @@ -38,14 +38,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -82,7 +82,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -95,7 +95,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -163,7 +163,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -193,7 +193,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -210,7 +210,7 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString(value, [parentKey, key], expectedType: T, pattern: valuePattern), ); } } @@ -220,7 +220,7 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException(value, [key], expectedType: String, pattern: pattern); } return value; } @@ -229,7 +229,7 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException(value, [key], expectedType: String, pattern: pattern); } return value; } @@ -242,7 +242,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -257,7 +257,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -307,30 +307,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -395,6 +396,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/json_syntax_generator/lib/src/generator/normal_class_generator.dart b/pkgs/json_syntax_generator/lib/src/generator/normal_class_generator.dart index ccb499a26d..a8be083dcc 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/normal_class_generator.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/normal_class_generator.dart @@ -66,15 +66,24 @@ static const ${tagProperty}Value = '$tagValue'; final className = classInfo.className; final factorySubclassReturns = []; + final taggedUnionValues = []; for (final subclass in classInfo.subclasses) { if (subclass.taggedUnionValue != null) { + taggedUnionValues.add("'${subclass.taggedUnionValue}'"); factorySubclassReturns.add(''' if (result.is${subclass.name}) { return result.as${subclass.name}; }'''); } } + taggedUnionValues.sort(); final factorySubclassReturnsString = factorySubclassReturns.join('\n'); + final expectedValuesString = taggedUnionValues.join(', '); + final unknownReturn = classInfo.isOpenTaggedUnion + ? 'return result;' + : '_throwFormatException(result.${classInfo.taggedUnionProperty}, ' + "[...path, '${classInfo.taggedUnionProperty}'], " + 'expectedValues: {$expectedValuesString});'; return ''' factory $className.fromJson( @@ -83,7 +92,7 @@ static const ${tagProperty}Value = '$tagValue'; }) { final result = $className._fromJson(json, path: path); $factorySubclassReturnsString - return result; + $unknownReturn } '''; } diff --git a/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart b/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart index 15d93ab5c1..9ee77a8448 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart @@ -98,7 +98,7 @@ class PropertyGenerator { buffer.writeln(''' $dartType get $fieldName { final jsonValue = _reader.get<$dartStringType>('$jsonKey'); $earlyReturn - return $classType.fromJson(jsonValue); + return $classType.fromJson(jsonValue, path: [...path, '$jsonKey']); } '''); if (!property.isOverride) { diff --git a/pkgs/json_syntax_generator/lib/src/model/class_info.dart b/pkgs/json_syntax_generator/lib/src/model/class_info.dart index 41f9e5588f..1eb6ed9b5d 100644 --- a/pkgs/json_syntax_generator/lib/src/model/class_info.dart +++ b/pkgs/json_syntax_generator/lib/src/model/class_info.dart @@ -47,6 +47,9 @@ class NormalClassInfo extends ClassInfo { /// Only set in the parent class. final bool visibleTaggedUnion; + /// If the tagged union allows unknown tag values. + final bool isOpenTaggedUnion; + bool get isTaggedUnion => taggedUnionProperty != null || taggedUnionValue != null; @@ -60,6 +63,7 @@ class NormalClassInfo extends ClassInfo { this.taggedUnionValue, this.extraValidation = const [], this.visibleTaggedUnion = false, + this.isOpenTaggedUnion = true, }) : super() { superclass?.subclasses.add(this); if (taggedUnionValue != null) { diff --git a/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart b/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart index 7e9c8c6398..4c4d298bdc 100644 --- a/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart +++ b/pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart @@ -181,6 +181,9 @@ class SchemaAnalyzer { ? schemas.generateSubClassesKey! : null, visibleTaggedUnion: publicUnionTagValues.contains(typeName), + isOpenTaggedUnion: + !schemas.generateSubClasses || + schemas.property(schemas.generateSubClassesKey!).generateOpenEnum, extraValidation: extraValidation, ); _classes[typeName] = classInfo; @@ -796,11 +799,15 @@ extension type JsonSchemas._(List _schemas) { } extension on JsonSchemas { - bool get generateEnum => type == SchemaType.string && anyOfs.isNotEmpty; + bool get generateEnum => + type == SchemaType.string && + (anyOfs.isNotEmpty || enumOrTaggedUnionValues.isNotEmpty); /// A class with opaque members and an `unknown` option. bool get generateOpenEnum => - generateEnum && anyOfs.single.any((e) => e.type != null); + generateEnum && + anyOfs.isNotEmpty && + anyOfs.single.any((e) => e.type != null); /// Generate getters/setters as `Map`. bool get generateMapOf => @@ -815,7 +822,8 @@ extension on JsonSchemas { if (type != SchemaType.object) return null; for (final p in propertyKeys) { final propertySchemas = property(p); - if (propertySchemas.anyOfs.isNotEmpty) { + if (propertySchemas.anyOfs.isNotEmpty || + propertySchemas.enumOrTaggedUnionValues.isNotEmpty) { if (propertySchemas.className != null) { // This is an explicit enum field, don't make the surrounding class a // tagged union. @@ -904,11 +912,13 @@ extension on JsonSchemas { } List get enumOrTaggedUnionValues => [ - for (final schema in _schemas) + for (final schema in _schemas) ...[ for (final s in schema.anyOf) ...[ if (s.constValue is String) s.constValue as String, ...s.enumValues?.whereType() ?? [], ], + ...schema.enumValues?.whereType() ?? [], + ], ]..sort(); } diff --git a/pkgs/pub_formats/lib/src/package_config_syntax.g.dart b/pkgs/pub_formats/lib/src/package_config_syntax.g.dart index 1f0ba6abf6..b1dd6e3295 100644 --- a/pkgs/pub_formats/lib/src/package_config_syntax.g.dart +++ b/pkgs/pub_formats/lib/src/package_config_syntax.g.dart @@ -239,14 +239,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -283,7 +283,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -296,7 +296,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -364,7 +364,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -394,7 +394,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -411,7 +411,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -421,7 +426,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -430,7 +440,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -443,7 +458,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -458,7 +473,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -508,30 +523,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -596,6 +612,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/pub_formats/lib/src/package_graph_syntax.g.dart b/pkgs/pub_formats/lib/src/package_graph_syntax.g.dart index 8a96c34fbd..0745064e56 100644 --- a/pkgs/pub_formats/lib/src/package_graph_syntax.g.dart +++ b/pkgs/pub_formats/lib/src/package_graph_syntax.g.dart @@ -184,14 +184,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -228,7 +228,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -241,7 +241,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -309,7 +309,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -339,7 +339,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -356,7 +356,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -366,7 +371,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -375,7 +385,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -388,7 +403,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -403,7 +418,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -453,30 +468,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -541,6 +557,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/pub_formats/lib/src/pubspec_lock_syntax.g.dart b/pkgs/pub_formats/lib/src/pubspec_lock_syntax.g.dart index 49b58172cc..5679f13cfa 100644 --- a/pkgs/pub_formats/lib/src/pubspec_lock_syntax.g.dart +++ b/pkgs/pub_formats/lib/src/pubspec_lock_syntax.g.dart @@ -28,7 +28,10 @@ class DependencyTypeSyntax { DependencyTypeSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory DependencyTypeSyntax.fromJson(String name) { + factory DependencyTypeSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -234,7 +237,10 @@ class PackageSyntax extends JsonObjectSyntax { DependencyTypeSyntax get dependency { final jsonValue = _reader.get('dependency'); - return DependencyTypeSyntax.fromJson(jsonValue); + return DependencyTypeSyntax.fromJson( + jsonValue, + path: [...path, 'dependency'], + ); } set _dependency(DependencyTypeSyntax value) { @@ -265,7 +271,7 @@ class PackageSyntax extends JsonObjectSyntax { PackageSourceSyntax get source { final jsonValue = _reader.get('source'); - return PackageSourceSyntax.fromJson(jsonValue); + return PackageSourceSyntax.fromJson(jsonValue, path: [...path, 'source']); } set _source(PackageSourceSyntax value) { @@ -339,7 +345,10 @@ class PackageSourceSyntax { PackageSourceSyntax.unknown(this.name) : assert(!_byName.keys.contains(name)); - factory PackageSourceSyntax.fromJson(String name) { + factory PackageSourceSyntax.fromJson( + String name, { + List path = const [], + }) { final knownValue = _byName[name]; if (knownValue != null) { return knownValue; @@ -546,14 +555,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -590,7 +599,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -603,7 +612,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -671,7 +680,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -701,7 +710,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -718,7 +727,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -728,7 +742,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -737,7 +756,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -750,7 +774,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -765,7 +789,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -815,30 +839,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -903,6 +928,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/pub_formats/lib/src/pubspec_syntax.g.dart b/pkgs/pub_formats/lib/src/pubspec_syntax.g.dart index ce001ebbc8..17caab0f8c 100644 --- a/pkgs/pub_formats/lib/src/pubspec_syntax.g.dart +++ b/pkgs/pub_formats/lib/src/pubspec_syntax.g.dart @@ -737,14 +737,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -781,7 +781,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -794,7 +794,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -862,7 +862,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -892,7 +892,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -909,7 +909,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -919,7 +924,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -928,7 +938,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -941,7 +956,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -956,7 +971,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -1006,30 +1021,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -1094,6 +1110,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern, diff --git a/pkgs/record_use/lib/src/syntax.g.dart b/pkgs/record_use/lib/src/syntax.g.dart index 591d5f1a3c..8ec5a47695 100644 --- a/pkgs/record_use/lib/src/syntax.g.dart +++ b/pkgs/record_use/lib/src/syntax.g.dart @@ -2332,14 +2332,14 @@ class _JsonReader { T get(String key) { final value = json[key]; if (value is T) return value; - throwFormatException(value, T, [key]); + throwFormatException(value, [key], expectedType: T); } List validate(String key) { final value = json[key]; if (value is T) return []; return [ - errorString(value, T, [key]), + errorString(value, [key], expectedType: T), ]; } @@ -2376,7 +2376,7 @@ class _JsonReader { List _castList(List list, String key) { for (final (index, value) in list.indexed) { if (value is! T) { - throwFormatException(value, T, [key, index]); + throwFormatException(value, [key, index], expectedType: T); } } return list.cast(); @@ -2389,7 +2389,7 @@ class _JsonReader { final result = []; for (final (index, value) in list.indexed) { if (value is! T) { - result.add(errorString(value, T, [key, index])); + result.add(errorString(value, [key, index], expectedType: T)); } } return result; @@ -2457,7 +2457,7 @@ class _JsonReader { ) { for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - throwFormatException(value, T, [parentKey, key]); + throwFormatException(value, [parentKey, key], expectedType: T); } } return map_.cast(); @@ -2487,7 +2487,7 @@ class _JsonReader { final result = []; for (final MapEntry(:key, :value) in map_.entries) { if (value is! T) { - result.add(errorString(value, T, [parentKey, key])); + result.add(errorString(value, [parentKey, key], expectedType: T)); } } return result; @@ -2504,7 +2504,12 @@ class _JsonReader { valuePattern != null && !valuePattern.hasMatch(value)) { result.add( - errorString(value, T, [parentKey, key], pattern: valuePattern), + errorString( + value, + [parentKey, key], + expectedType: T, + pattern: valuePattern, + ), ); } } @@ -2514,7 +2519,12 @@ class _JsonReader { String string(String key, RegExp? pattern) { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -2523,7 +2533,12 @@ class _JsonReader { final value = get(key); if (value == null) return null; if (pattern != null && !pattern.hasMatch(value)) { - throwFormatException(value, String, [key], pattern: pattern); + throwFormatException( + value, + [key], + expectedType: String, + pattern: pattern, + ); } return value; } @@ -2536,7 +2551,7 @@ class _JsonReader { final value = get(key); if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -2551,7 +2566,7 @@ class _JsonReader { if (value == null) return []; if (pattern != null && !pattern.hasMatch(value)) { return [ - errorString(value, String, [key], pattern: pattern), + errorString(value, [key], expectedType: String, pattern: pattern), ]; } return []; @@ -2601,30 +2616,31 @@ class _JsonReader { Never throwFormatException( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - throw FormatException( - errorString(value, expectedType, pathExtension, pattern: pattern), - ); - } + Set? expectedValues, + }) => _throwFormatException( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String errorString( Object? value, - Type expectedType, List pathExtension, { + Type? expectedType, RegExp? pattern, - }) { - final pathString = _jsonPathToString(pathExtension); - if (value == null) { - return "No value was provided for '$pathString'." - ' Expected a $expectedType.'; - } - final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; - return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." - ' Expected a $expectedType$satisfying.'; - } + Set? expectedValues, + }) => _errorString( + value, + [...path, ...pathExtension], + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ); String keyErrorString( String key, { @@ -2689,6 +2705,47 @@ void _checkArgumentMapKeys(Map? map, {RegExp? keyPattern}) { } } +Never _throwFormatException( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + throw FormatException( + _errorString( + value, + path, + expectedType: expectedType, + pattern: pattern, + expectedValues: expectedValues, + ), + ); +} + +String _errorString( + Object? value, + List path, { + Type? expectedType, + RegExp? pattern, + Set? expectedValues, +}) { + final pathString = path.join('.'); + final String expected; + if (expectedValues != null) { + expected = "one of ${expectedValues.map((e) => "'$e'").join(', ')}"; + } else { + final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}'; + expected = 'a $expectedType$satisfying'; + } + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected $expected.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected $expected.'; +} + void _checkArgumentMapStringElements( Map? map, { RegExp? valuePattern,