From fb95354c853c0b032aa66e8c914371dcb1653908 Mon Sep 17 00:00:00 2001 From: Daco Harkes <dacoharkes@google.com> Date: Thu, 20 Mar 2025 11:57:12 +0100 Subject: [PATCH 1/2] [native_assets_cli] Syntax validation --- .../lib/src/generator/helper_library.dart | 110 ++++++- .../src/generator/normal_class_generator.dart | 14 +- .../lib/src/generator/property_generator.dart | 80 +++++ .../lib/src/model/property_info.dart | 10 + .../lib/src/code_assets/syntax.g.dart | 303 +++++++++++++++++- .../lib/src/code_assets/validation.dart | 70 +++- .../lib/src/data_assets/syntax.g.dart | 130 +++++++- .../lib/src/data_assets/validation.dart | 47 ++- pkgs/native_assets_cli/lib/src/extension.dart | 10 +- .../lib/src/hook/syntax.g.dart | 252 ++++++++++++++- .../native_assets_cli/lib/src/validation.dart | 29 +- 11 files changed, 994 insertions(+), 61 deletions(-) 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 137278d643..91857c6b70 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart @@ -22,22 +22,46 @@ class JsonReader { T get<T extends Object?>(String key) { final value = json[key]; if (value is T) return value; - final pathString = _jsonPathToString([key]); - if (value == null) { - throw FormatException("No value was provided for '$pathString'."); - } throwFormatException(value, T, [key]); } + List<String> validate<T extends Object?>(String key) { + final value = json[key]; + if (value is T) return []; + return [ + errorString(value, T, [key]), + ]; + } + List<T> list<T extends Object?>(String key) => _castList<T>(get<List<Object?>>(key), key); + List<String> validateList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + return _validateListElements(get<List<Object?>>(key), key); + } + List<T>? optionalList<T extends Object?>(String key) => switch (get<List<Object?>?>(key)?.cast<T>()) { null => null, final l => _castList<T>(l, key), }; + List<String> validateOptionalList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>?>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + final list = get<List<Object?>?>(key); + if (list == null) { + return []; + } + return _validateListElements(list, key); + } + /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { var index = 0; @@ -50,6 +74,21 @@ class JsonReader { return list.cast(); } + List<String> _validateListElements<T extends Object?>( + List<Object?> list, + String key, + ) { + var index = 0; + final result = <String>[]; + for (final value in list) { + if (value is! T) { + result.add(errorString(value, T, [key, index])); + } + index++; + } + return result; + } + List<T>? optionalListParsed<T extends Object?>( String key, T Function(Object?) elementParser, @@ -62,12 +101,32 @@ class JsonReader { Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); + List<String> validateMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return _validateMapElements<T>(get<Map<String, Object?>>(key), key); + } + Map<String, T>? optionalMap<T extends Object?>(String key) => switch (get<Map<String, Object?>?>(key)) { null => null, final m => _castMap<T>(m, key), }; + List<String> validateOptionalMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>?>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final map = get<Map<String, Object?>?>(key); + if (map == null) { + return []; + } + return _validateMapElements<T>(map, key); + } + /// [Map.cast] but with [FormatException]s. Map<String, T> _castMap<T extends Object?>( Map<String, Object?> map_, @@ -81,18 +140,40 @@ class JsonReader { return map_.cast(); } + List<String> _validateMapElements<T extends Object?>( + Map<String, Object?> map_, + String parentKey, + ) { + final result = <String>[]; + for (final MapEntry(:key, :value) in map_.entries) { + if (value is! T) { + result.add(errorString(value, T, [parentKey, key])); + } + } + return result; + } + List<String>? optionalStringList(String key) => optionalList<String>(key); + List<String> validateOptionalStringList(String key) => + validateOptionalList<String>(key); + List<String> stringList(String key) => list<String>(key); + List<String> validateStringList(String key) => validateList<String>(key); + Uri path$(String key) => _fileSystemPathToUri(get<String>(key)); + List<String> validatePath(String key) => validate<String>(key); + Uri? optionalPath(String key) { final value = get<String?>(key); if (value == null) return null; return _fileSystemPathToUri(value); } + List<String> validateOptionalPath(String key) => validate<String?>(key); + List<Uri>? optionalPathList(String key) { final strings = optionalStringList(key); if (strings == null) { @@ -101,6 +182,9 @@ class JsonReader { return [for (final string in strings) _fileSystemPathToUri(string)]; } + List<String> validateOptionalPathList(String key) => + validateOptionalStringList(key); + static Uri _fileSystemPathToUri(String path) { if (path.endsWith(Platform.pathSeparator)) { return Uri.directory(path); @@ -115,12 +199,22 @@ class JsonReader { Object? value, Type expectedType, List<Object> pathExtension, + ) { + throw FormatException(errorString(value, expectedType, pathExtension)); + } + + String errorString( + Object? value, + Type expectedType, + List<Object> pathExtension, ) { final pathString = _jsonPathToString(pathExtension); - throw FormatException( - "Unexpected value '$value' (${value.runtimeType}) for '$pathString'. " - 'Expected a $expectedType.', - ); + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected a $expectedType.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected a $expectedType.'; } } 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 c338997378..a4c17eb738 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 @@ -24,6 +24,7 @@ class ClassGenerator { final constructorSetterCalls = <String>[]; final accessors = <String>[]; final superParams = <String>[]; + final validateCalls = <String>[]; final propertyNames = { @@ -62,6 +63,7 @@ class ClassGenerator { } if (thisClassProperty != null) { accessors.add(PropertyGenerator(thisClassProperty).generate()); + validateCalls.add('...${property.validateName}()'); } } @@ -95,6 +97,12 @@ class $className extends $superclassName { buffer.writeln(''' ${accessors.join('\n')} + @override + List<String> validate() => [ + ...super.validate(), + ${validateCalls.join(',\n')} + ]; + @override String toString() => '$className(\$json)'; } @@ -121,6 +129,10 @@ class $className { ${accessors.join('\n')} + List<String> validate() => [ + ${validateCalls.join(',\n')} + ]; + @override String toString() => '$className(\$json)'; } @@ -132,7 +144,7 @@ class $className { extension ${className}Extension on $superclassName { bool get is$className => type == '$identifyingSubtype'; - $className get as$className => $className.fromJson(json); + $className get as$className => $className.fromJson(json, path: path); } '''); } 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 5e6e30803b..093838d88f 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart @@ -85,6 +85,7 @@ class PropertyGenerator { final classInfo = dartType.classInfo; final classType = classInfo.name; final fieldName = property.name; + final validateName = property.validateName; final required = property.isRequired; switch (classInfo) { @@ -106,6 +107,8 @@ set $setterName($dartType value) { $setter $sortOnKey } + +List<String> $validateName() => _reader.validate<$dartStringType>('$jsonKey'); '''); } @@ -127,6 +130,27 @@ set $setterName($dartType value) { $sortOnKey } '''); + if (required) { + buffer.writeln(''' +List<String> $validateName() { + final mapErrors = _reader.validate<Map<String, Object?>>('$jsonKey'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return $fieldName.validate(); +} +'''); + } else { + buffer.writeln(''' +List<String> $validateName() { + final mapErrors = _reader.validate<Map<String, Object?>?>('$jsonKey'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return $fieldName?.validate() ?? []; +} +'''); + } } } } @@ -140,6 +164,7 @@ set $setterName($dartType value) { String sortOnKey, ) { final fieldName = property.name; + final validateName = property.validateName; buffer.writeln(''' $dartType get $fieldName => _reader.get<$dartType>('$jsonKey'); @@ -148,6 +173,8 @@ set $setterName($dartType value) { json.setOrRemove('$jsonKey', value); $sortOnKey } + +List<String> $validateName() => _reader.validate<$dartType>('$jsonKey'); '''); } @@ -163,6 +190,7 @@ set $setterName($dartType value) { throw UnimplementedError('Expected an optional property.'); } final fieldName = property.name; + final validateName = property.validateName; final valueType = dartType.valueType; switch (valueType) { @@ -175,6 +203,9 @@ set $setterName($dartType value) { json.setOrRemove('$jsonKey', value); $sortOnKey } + +List<String> $validateName() => + _reader.validateOptionalMap<${dartType.valueType}>('$jsonKey'); '''); case ListDartType(): final itemType = valueType.itemType; @@ -213,6 +244,26 @@ set $setterName($dartType value) { } $sortOnKey } + +List<String> $validateName() { + final mapErrors = _reader.validateOptionalMap('$jsonKey'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final jsonValue = _reader.optionalMap( + '$jsonKey', + ); + if (jsonValue == null) { + return []; + } + final result = <String>[]; + for (final list in $fieldName!.values) { + for (final element in list) { + result.addAll(element.validate()); + } + } + return result; +} '''); case SimpleDartType(): switch (valueType.typeName) { @@ -225,6 +276,8 @@ set $setterName($dartType value) { json.setOrRemove('$jsonKey', value); $sortOnKey } + +List<String> $validateName() => _reader.validateOptionalMap('$jsonKey'); '''); } else { throw UnimplementedError(valueType.toString()); @@ -246,6 +299,7 @@ set $setterName($dartType value) { String sortOnKey, ) { final fieldName = property.name; + final validateName = property.validateName; final itemType = dartType.itemType; final typeName = itemType.toString(); final required = property.isRequired; @@ -279,11 +333,27 @@ set $setterName($dartType value) { } $sortOnKey } + +List<String> $validateName() { + final listErrors = _reader.validateOptionalList<Map<String, Object?>>( + '$jsonKey', + ); + if (listErrors.isNotEmpty) { + return listErrors; + } + final elements = $fieldName; + if (elements == null) { + return []; + } + return [for (final element in elements) ...element.validate()]; +} '''); case SimpleDartType(): switch (itemType.typeName) { case 'String': final jsonRead = required ? 'stringList' : 'optionalStringList'; + final jsonValidate = + required ? 'validateStringList' : 'validateOptionalStringList'; final setter = setOrRemove(dartType, jsonKey); buffer.writeln(''' $dartType get $fieldName => _reader.$jsonRead('$jsonKey'); @@ -292,6 +362,8 @@ set $setterName($dartType value) { $setter $sortOnKey } + +List<String> $validateName() => _reader.$jsonValidate('$jsonKey'); '''); default: @@ -299,6 +371,8 @@ set $setterName($dartType value) { } case UriDartType(): final jsonRead = required ? 'pathList' : 'optionalPathList'; + final jsonValidate = + required ? 'validatePathList' : 'validateOptionalPathList'; final setter = setOrRemove(dartType, jsonKey, '.toJson()'); buffer.writeln(''' $dartType get $fieldName => _reader.$jsonRead('$jsonKey'); @@ -307,6 +381,8 @@ set $setterName($dartType value) { $setter $sortOnKey } + +List<String> $validateName() => _reader.$jsonValidate('$jsonKey'); '''); default: throw UnimplementedError(itemType.toString()); @@ -322,8 +398,10 @@ set $setterName($dartType value) { String sortOnKey, ) { final fieldName = property.name; + final validateName = property.validateName; final required = property.isRequired; final jsonRead = required ? r'path$' : 'optionalPath'; + final jsonValidate = required ? r'validatePath' : 'validateOptionalPath'; final setter = setOrRemove(dartType, jsonKey, '.toFilePath()'); buffer.writeln(''' $dartType get $fieldName => _reader.$jsonRead('$jsonKey'); @@ -332,6 +410,8 @@ set $setterName($dartType value) { $setter $sortOnKey } + +List<String> $validateName() => _reader.$jsonValidate('$jsonKey'); '''); } diff --git a/pkgs/json_syntax_generator/lib/src/model/property_info.dart b/pkgs/json_syntax_generator/lib/src/model/property_info.dart index ac13cc174d..f0b05fb8b3 100644 --- a/pkgs/json_syntax_generator/lib/src/model/property_info.dart +++ b/pkgs/json_syntax_generator/lib/src/model/property_info.dart @@ -9,6 +9,9 @@ class PropertyInfo { /// The Dart getter and setter name. final String name; + /// The Dart validate method name. + String get validateName => '_validate${_ucFirst(name)}'; + /// The key in the json object for this property. final String jsonKey; @@ -53,3 +56,10 @@ PropertyInfo( isRequired: $isRequired )'''; } + +String _ucFirst(String str) { + if (str.isEmpty) { + return ''; + } + return str[0].toUpperCase() + str.substring(1); +} diff --git a/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart b/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart index a719c66e6b..ff06c586ff 100644 --- a/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart @@ -28,6 +28,11 @@ class AndroidCodeConfig { json.setOrRemove('target_ndk_api', value); } + List<String> _validateTargetNdkApi() => + _reader.validate<int?>('target_ndk_api'); + + List<String> validate() => [..._validateTargetNdkApi()]; + @override String toString() => 'AndroidCodeConfig($json)'; } @@ -98,6 +103,10 @@ class Asset { json.setOrRemove('type', value); } + List<String> _validateType() => _reader.validate<String?>('type'); + + List<String> validate() => [..._validateType()]; + @override String toString() => 'Asset($json)'; } @@ -147,18 +156,25 @@ class NativeCodeAsset extends Asset { json.setOrRemove('architecture', value?.name); } + List<String> _validateArchitecture() => + _reader.validate<String?>('architecture'); + Uri? get file => _reader.optionalPath('file'); set _file(Uri? value) { json.setOrRemove('file', value?.toFilePath()); } + List<String> _validateFile() => _reader.validateOptionalPath('file'); + String get id => _reader.get<String>('id'); set _id(String value) { json.setOrRemove('id', value); } + List<String> _validateId() => _reader.validate<String>('id'); + LinkMode get linkMode { final jsonValue = _reader.map$('link_mode'); return LinkMode.fromJson(jsonValue, path: [...path, 'link_mode']); @@ -168,6 +184,14 @@ class NativeCodeAsset extends Asset { json['link_mode'] = value.json; } + List<String> _validateLinkMode() { + final mapErrors = _reader.validate<Map<String, Object?>>('link_mode'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return linkMode.validate(); + } + OS get os { final jsonValue = _reader.get<String>('os'); return OS.fromJson(jsonValue); @@ -177,6 +201,18 @@ class NativeCodeAsset extends Asset { json['os'] = value.name; } + List<String> _validateOs() => _reader.validate<String>('os'); + + @override + List<String> validate() => [ + ...super.validate(), + ..._validateArchitecture(), + ..._validateFile(), + ..._validateId(), + ..._validateLinkMode(), + ..._validateOs(), + ]; + @override String toString() => 'NativeCodeAsset($json)'; } @@ -184,7 +220,8 @@ class NativeCodeAsset extends Asset { extension NativeCodeAssetExtension on Asset { bool get isNativeCodeAsset => type == 'native_code'; - NativeCodeAsset get asNativeCodeAsset => NativeCodeAsset.fromJson(json); + NativeCodeAsset get asNativeCodeAsset => + NativeCodeAsset.fromJson(json, path: path); } class CCompilerConfig { @@ -220,18 +257,25 @@ class CCompilerConfig { json['ar'] = value.toFilePath(); } + List<String> _validateAr() => _reader.validatePath('ar'); + Uri get cc => _reader.path$('cc'); set _cc(Uri value) { json['cc'] = value.toFilePath(); } + List<String> _validateCc() => _reader.validatePath('cc'); + Uri? get envScript => _reader.optionalPath('env_script'); set _envScript(Uri? value) { json.setOrRemove('env_script', value?.toFilePath()); } + List<String> _validateEnvScript() => + _reader.validateOptionalPath('env_script'); + List<String>? get envScriptArguments => _reader.optionalStringList('env_script_arguments'); @@ -239,12 +283,17 @@ class CCompilerConfig { json.setOrRemove('env_script_arguments', value); } + List<String> _validateEnvScriptArguments() => + _reader.validateOptionalStringList('env_script_arguments'); + Uri get ld => _reader.path$('ld'); set _ld(Uri value) { json['ld'] = value.toFilePath(); } + List<String> _validateLd() => _reader.validatePath('ld'); + Windows? get windows { final jsonValue = _reader.optionalMap('windows'); if (jsonValue == null) return null; @@ -255,6 +304,23 @@ class CCompilerConfig { json.setOrRemove('windows', value?.json); } + List<String> _validateWindows() { + final mapErrors = _reader.validate<Map<String, Object?>?>('windows'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return windows?.validate() ?? []; + } + + List<String> validate() => [ + ..._validateAr(), + ..._validateCc(), + ..._validateEnvScript(), + ..._validateEnvScriptArguments(), + ..._validateLd(), + ..._validateWindows(), + ]; + @override String toString() => 'CCompilerConfig($json)'; } @@ -288,6 +354,18 @@ class Windows { json.setOrRemove('developer_command_prompt', value?.json); } + List<String> _validateDeveloperCommandPrompt() { + final mapErrors = _reader.validate<Map<String, Object?>?>( + 'developer_command_prompt', + ); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return developerCommandPrompt?.validate() ?? []; + } + + List<String> validate() => [..._validateDeveloperCommandPrompt()]; + @override String toString() => 'Windows($json)'; } @@ -315,12 +393,18 @@ class DeveloperCommandPrompt { json['arguments'] = value; } + List<String> _validateArguments() => _reader.validateStringList('arguments'); + Uri get script => _reader.path$('script'); set _script(Uri value) { json['script'] = value.toFilePath(); } + List<String> _validateScript() => _reader.validatePath('script'); + + List<String> validate() => [..._validateArguments(), ..._validateScript()]; + @override String toString() => 'DeveloperCommandPrompt($json)'; } @@ -364,6 +448,14 @@ class CodeConfig { json.setOrRemove('android', value?.json); } + List<String> _validateAndroid() { + final mapErrors = _reader.validate<Map<String, Object?>?>('android'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return android?.validate() ?? []; + } + CCompilerConfig? get cCompiler { final jsonValue = _reader.optionalMap('c_compiler'); if (jsonValue == null) return null; @@ -374,6 +466,14 @@ class CodeConfig { json.setOrRemove('c_compiler', value?.json); } + List<String> _validateCCompiler() { + final mapErrors = _reader.validate<Map<String, Object?>?>('c_compiler'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return cCompiler?.validate() ?? []; + } + IOSCodeConfig? get iOS { final jsonValue = _reader.optionalMap('ios'); if (jsonValue == null) return null; @@ -384,6 +484,14 @@ class CodeConfig { json.setOrRemove('ios', value?.json); } + List<String> _validateIOS() { + final mapErrors = _reader.validate<Map<String, Object?>?>('ios'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return iOS?.validate() ?? []; + } + LinkModePreference get linkModePreference { final jsonValue = _reader.get<String>('link_mode_preference'); return LinkModePreference.fromJson(jsonValue); @@ -393,6 +501,9 @@ class CodeConfig { json['link_mode_preference'] = value.name; } + List<String> _validateLinkModePreference() => + _reader.validate<String>('link_mode_preference'); + MacOSCodeConfig? get macOS { final jsonValue = _reader.optionalMap('macos'); if (jsonValue == null) return null; @@ -403,6 +514,14 @@ class CodeConfig { json.setOrRemove('macos', value?.json); } + List<String> _validateMacOS() { + final mapErrors = _reader.validate<Map<String, Object?>?>('macos'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return macOS?.validate() ?? []; + } + Architecture get targetArchitecture { final jsonValue = _reader.get<String>('target_architecture'); return Architecture.fromJson(jsonValue); @@ -412,6 +531,9 @@ class CodeConfig { json['target_architecture'] = value.name; } + List<String> _validateTargetArchitecture() => + _reader.validate<String>('target_architecture'); + OS get targetOs { final jsonValue = _reader.get<String>('target_os'); return OS.fromJson(jsonValue); @@ -421,6 +543,18 @@ class CodeConfig { json['target_os'] = value.name; } + List<String> _validateTargetOs() => _reader.validate<String>('target_os'); + + List<String> validate() => [ + ..._validateAndroid(), + ..._validateCCompiler(), + ..._validateIOS(), + ..._validateLinkModePreference(), + ..._validateMacOS(), + ..._validateTargetArchitecture(), + ..._validateTargetOs(), + ]; + @override String toString() => 'CodeConfig($json)'; } @@ -450,6 +584,16 @@ class Config { json.sortOnKey(); } + List<String> _validateCode() { + final mapErrors = _reader.validate<Map<String, Object?>?>('code'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return code?.validate() ?? []; + } + + List<String> validate() => [..._validateCode()]; + @override String toString() => 'Config($json)'; } @@ -477,12 +621,22 @@ class IOSCodeConfig { json.setOrRemove('target_sdk', value); } + List<String> _validateTargetSdk() => _reader.validate<String?>('target_sdk'); + int? get targetVersion => _reader.get<int?>('target_version'); set _targetVersion(int? value) { json.setOrRemove('target_version', value); } + List<String> _validateTargetVersion() => + _reader.validate<int?>('target_version'); + + List<String> validate() => [ + ..._validateTargetSdk(), + ..._validateTargetVersion(), + ]; + @override String toString() => 'IOSCodeConfig($json)'; } @@ -507,6 +661,10 @@ class LinkMode { json.setOrRemove('type', value); } + List<String> _validateType() => _reader.validate<String>('type'); + + List<String> validate() => [..._validateType()]; + @override String toString() => 'LinkMode($json)'; } @@ -517,6 +675,9 @@ class DynamicLoadingBundleLinkMode extends LinkMode { DynamicLoadingBundleLinkMode() : super(type: 'dynamic_loading_bundle'); + @override + List<String> validate() => [...super.validate()]; + @override String toString() => 'DynamicLoadingBundleLinkMode($json)'; } @@ -525,7 +686,7 @@ extension DynamicLoadingBundleLinkModeExtension on LinkMode { bool get isDynamicLoadingBundleLinkMode => type == 'dynamic_loading_bundle'; DynamicLoadingBundleLinkMode get asDynamicLoadingBundleLinkMode => - DynamicLoadingBundleLinkMode.fromJson(json); + DynamicLoadingBundleLinkMode.fromJson(json, path: path); } class DynamicLoadingExecutableLinkMode extends LinkMode { @@ -535,6 +696,9 @@ class DynamicLoadingExecutableLinkMode extends LinkMode { DynamicLoadingExecutableLinkMode() : super(type: 'dynamic_loading_executable'); + @override + List<String> validate() => [...super.validate()]; + @override String toString() => 'DynamicLoadingExecutableLinkMode($json)'; } @@ -544,7 +708,7 @@ extension DynamicLoadingExecutableLinkModeExtension on LinkMode { type == 'dynamic_loading_executable'; DynamicLoadingExecutableLinkMode get asDynamicLoadingExecutableLinkMode => - DynamicLoadingExecutableLinkMode.fromJson(json); + DynamicLoadingExecutableLinkMode.fromJson(json, path: path); } class DynamicLoadingProcessLinkMode extends LinkMode { @@ -553,6 +717,9 @@ class DynamicLoadingProcessLinkMode extends LinkMode { DynamicLoadingProcessLinkMode() : super(type: 'dynamic_loading_process'); + @override + List<String> validate() => [...super.validate()]; + @override String toString() => 'DynamicLoadingProcessLinkMode($json)'; } @@ -561,7 +728,7 @@ extension DynamicLoadingProcessLinkModeExtension on LinkMode { bool get isDynamicLoadingProcessLinkMode => type == 'dynamic_loading_process'; DynamicLoadingProcessLinkMode get asDynamicLoadingProcessLinkMode => - DynamicLoadingProcessLinkMode.fromJson(json); + DynamicLoadingProcessLinkMode.fromJson(json, path: path); } class DynamicLoadingSystemLinkMode extends LinkMode { @@ -587,6 +754,11 @@ class DynamicLoadingSystemLinkMode extends LinkMode { json['uri'] = value.toFilePath(); } + List<String> _validateUri() => _reader.validatePath('uri'); + + @override + List<String> validate() => [...super.validate(), ..._validateUri()]; + @override String toString() => 'DynamicLoadingSystemLinkMode($json)'; } @@ -595,7 +767,7 @@ extension DynamicLoadingSystemLinkModeExtension on LinkMode { bool get isDynamicLoadingSystemLinkMode => type == 'dynamic_loading_system'; DynamicLoadingSystemLinkMode get asDynamicLoadingSystemLinkMode => - DynamicLoadingSystemLinkMode.fromJson(json); + DynamicLoadingSystemLinkMode.fromJson(json, path: path); } class StaticLinkMode extends LinkMode { @@ -603,6 +775,9 @@ class StaticLinkMode extends LinkMode { StaticLinkMode() : super(type: 'static'); + @override + List<String> validate() => [...super.validate()]; + @override String toString() => 'StaticLinkMode($json)'; } @@ -610,7 +785,8 @@ class StaticLinkMode extends LinkMode { extension StaticLinkModeExtension on LinkMode { bool get isStaticLinkMode => type == 'static'; - StaticLinkMode get asStaticLinkMode => StaticLinkMode.fromJson(json); + StaticLinkMode get asStaticLinkMode => + StaticLinkMode.fromJson(json, path: path); } class LinkModePreference { @@ -673,6 +849,11 @@ class MacOSCodeConfig { json.setOrRemove('target_version', value); } + List<String> _validateTargetVersion() => + _reader.validate<int?>('target_version'); + + List<String> validate() => [..._validateTargetVersion()]; + @override String toString() => 'MacOSCodeConfig($json)'; } @@ -730,22 +911,46 @@ class JsonReader { T get<T extends Object?>(String key) { final value = json[key]; if (value is T) return value; - final pathString = _jsonPathToString([key]); - if (value == null) { - throw FormatException("No value was provided for '$pathString'."); - } throwFormatException(value, T, [key]); } + List<String> validate<T extends Object?>(String key) { + final value = json[key]; + if (value is T) return []; + return [ + errorString(value, T, [key]), + ]; + } + List<T> list<T extends Object?>(String key) => _castList<T>(get<List<Object?>>(key), key); + List<String> validateList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + return _validateListElements(get<List<Object?>>(key), key); + } + List<T>? optionalList<T extends Object?>(String key) => switch (get<List<Object?>?>(key)?.cast<T>()) { null => null, final l => _castList<T>(l, key), }; + List<String> validateOptionalList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>?>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + final list = get<List<Object?>?>(key); + if (list == null) { + return []; + } + return _validateListElements(list, key); + } + /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { var index = 0; @@ -758,6 +963,21 @@ class JsonReader { return list.cast(); } + List<String> _validateListElements<T extends Object?>( + List<Object?> list, + String key, + ) { + var index = 0; + final result = <String>[]; + for (final value in list) { + if (value is! T) { + result.add(errorString(value, T, [key, index])); + } + index++; + } + return result; + } + List<T>? optionalListParsed<T extends Object?>( String key, T Function(Object?) elementParser, @@ -770,12 +990,32 @@ class JsonReader { Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); + List<String> validateMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return _validateMapElements<T>(get<Map<String, Object?>>(key), key); + } + Map<String, T>? optionalMap<T extends Object?>(String key) => switch (get<Map<String, Object?>?>(key)) { null => null, final m => _castMap<T>(m, key), }; + List<String> validateOptionalMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>?>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final map = get<Map<String, Object?>?>(key); + if (map == null) { + return []; + } + return _validateMapElements<T>(map, key); + } + /// [Map.cast] but with [FormatException]s. Map<String, T> _castMap<T extends Object?>( Map<String, Object?> map_, @@ -789,18 +1029,40 @@ class JsonReader { return map_.cast(); } + List<String> _validateMapElements<T extends Object?>( + Map<String, Object?> map_, + String parentKey, + ) { + final result = <String>[]; + for (final MapEntry(:key, :value) in map_.entries) { + if (value is! T) { + result.add(errorString(value, T, [parentKey, key])); + } + } + return result; + } + List<String>? optionalStringList(String key) => optionalList<String>(key); + List<String> validateOptionalStringList(String key) => + validateOptionalList<String>(key); + List<String> stringList(String key) => list<String>(key); + List<String> validateStringList(String key) => validateList<String>(key); + Uri path$(String key) => _fileSystemPathToUri(get<String>(key)); + List<String> validatePath(String key) => validate<String>(key); + Uri? optionalPath(String key) { final value = get<String?>(key); if (value == null) return null; return _fileSystemPathToUri(value); } + List<String> validateOptionalPath(String key) => validate<String?>(key); + List<Uri>? optionalPathList(String key) { final strings = optionalStringList(key); if (strings == null) { @@ -809,6 +1071,9 @@ class JsonReader { return [for (final string in strings) _fileSystemPathToUri(string)]; } + List<String> validateOptionalPathList(String key) => + validateOptionalStringList(key); + static Uri _fileSystemPathToUri(String path) { if (path.endsWith(Platform.pathSeparator)) { return Uri.directory(path); @@ -823,12 +1088,22 @@ class JsonReader { Object? value, Type expectedType, List<Object> pathExtension, + ) { + throw FormatException(errorString(value, expectedType, pathExtension)); + } + + String errorString( + Object? value, + Type expectedType, + List<Object> pathExtension, ) { final pathString = _jsonPathToString(pathExtension); - throw FormatException( - "Unexpected value '$value' (${value.runtimeType}) for '$pathString'. " - 'Expected a $expectedType.', - ); + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected a $expectedType.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected a $expectedType.'; } } diff --git a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart index 35aa793276..d68dcea6a5 100644 --- a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart +++ b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart @@ -7,16 +7,23 @@ import 'dart:io'; import '../../code_assets_builder.dart'; import 'config.dart'; import 'link_mode.dart'; +import 'syntax.g.dart' as syntax; Future<ValidationErrors> validateCodeAssetBuildInput(BuildInput input) async => - _validateCodeConfig('BuildInput.config.code', input.config.code); + _validateConfig('BuildInput.config.code', input.config); Future<ValidationErrors> validateCodeAssetLinkInput(LinkInput input) async => [ - ..._validateCodeConfig('LinkInput.config.code', input.config.code), + ..._validateConfig('LinkInput.config.code', input.config), ...await _validateCodeAssetLinkInput(input.assets.encodedAssets), ]; -ValidationErrors _validateCodeConfig(String inputName, CodeConfig code) { +ValidationErrors _validateConfig(String inputName, HookConfig config) { + final syntaxErrors = _validateConfigSyntax(config); + if (syntaxErrors.isNotEmpty) { + return syntaxErrors; + } + + final code = config.code; final errors = <String>[]; final targetOS = code.targetOS; switch (targetOS) { @@ -71,13 +78,30 @@ ValidationErrors _validateCodeConfig(String inputName, CodeConfig code) { return errors; } +List<String> _validateConfigSyntax(HookConfig config) { + final syntaxNode = syntax.Config.fromJson(config.json, path: config.path); + final syntaxErrors = syntaxNode.validate(); + if (syntaxErrors.isEmpty) { + return []; + } + return [...syntaxErrors, _semanticValidationSkippedMessage(syntaxNode.path)]; +} + Future<ValidationErrors> _validateCodeAssetLinkInput( List<EncodedAsset> encodedAssets, -) async => [ - for (final asset in encodedAssets) - if (asset.type == CodeAsset.type) - ..._validateCodeAssetFile(CodeAsset.fromEncoded(asset)), -]; +) async { + final errors = <String>[]; + for (final asset in encodedAssets) { + if (asset.type != CodeAsset.type) continue; + final syntaxErrors = _validateCodeAssetSyntax(asset); + if (syntaxErrors.isNotEmpty) { + errors.addAll(syntaxErrors); + continue; + } + errors.addAll(_validateCodeAssetFile(CodeAsset.fromEncoded(asset))); + } + return errors; +} Future<ValidationErrors> validateCodeAssetBuildOutput( BuildInput input, @@ -141,6 +165,11 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( for (final asset in encodedAssets) { if (asset.type != CodeAsset.type) continue; + final syntaxErrors = _validateCodeAssetSyntax(asset); + if (syntaxErrors.isNotEmpty) { + errors.addAll(syntaxErrors); + continue; + } _validateCodeAsset( input, codeConfig, @@ -158,6 +187,11 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( for (final asset in encodedAssetsForLinking) { if (asset.type != CodeAsset.type) continue; + final syntaxErrors = _validateCodeAssetSyntax(asset); + if (syntaxErrors.isNotEmpty) { + errors.addAll(syntaxErrors); + continue; + } _validateCodeAsset( input, codeConfig, @@ -172,6 +206,26 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( return errors; } +List<String> _validateCodeAssetSyntax(EncodedAsset encodedAsset) { + final syntaxNode = syntax.Asset.fromJson( + encodedAsset.toJson(), + path: encodedAsset.jsonPath ?? [], + ); + if (!syntaxNode.isNativeCodeAsset) { + return []; + } + final syntaxErrors = syntaxNode.asNativeCodeAsset.validate(); + if (syntaxErrors.isEmpty) { + return []; + } + return [...syntaxErrors, _semanticValidationSkippedMessage(syntaxNode.path)]; +} + +String _semanticValidationSkippedMessage(List<Object> jsonPath) { + final pathString = jsonPath.join('.'); + return "Syntax errors in '$pathString'. Semantic validation skipped."; +} + void _validateCodeAsset( HookInput input, CodeConfig codeConfig, diff --git a/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart b/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart index d88a736b2d..34c1649c17 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart @@ -28,6 +28,10 @@ class Asset { json.setOrRemove('type', value); } + List<String> _validateType() => _reader.validate<String?>('type'); + + List<String> validate() => [..._validateType()]; + @override String toString() => 'Asset($json)'; } @@ -62,18 +66,32 @@ class DataAsset extends Asset { json['file'] = value.toFilePath(); } + List<String> _validateFile() => _reader.validatePath('file'); + String get name => _reader.get<String>('name'); set _name(String value) { json.setOrRemove('name', value); } + List<String> _validateName() => _reader.validate<String>('name'); + String get package => _reader.get<String>('package'); set _package(String value) { json.setOrRemove('package', value); } + List<String> _validatePackage() => _reader.validate<String>('package'); + + @override + List<String> validate() => [ + ...super.validate(), + ..._validateFile(), + ..._validateName(), + ..._validatePackage(), + ]; + @override String toString() => 'DataAsset($json)'; } @@ -81,7 +99,7 @@ class DataAsset extends Asset { extension DataAssetExtension on Asset { bool get isDataAsset => type == 'data'; - DataAsset get asDataAsset => DataAsset.fromJson(json); + DataAsset get asDataAsset => DataAsset.fromJson(json, path: path); } class JsonReader { @@ -100,22 +118,46 @@ class JsonReader { T get<T extends Object?>(String key) { final value = json[key]; if (value is T) return value; - final pathString = _jsonPathToString([key]); - if (value == null) { - throw FormatException("No value was provided for '$pathString'."); - } throwFormatException(value, T, [key]); } + List<String> validate<T extends Object?>(String key) { + final value = json[key]; + if (value is T) return []; + return [ + errorString(value, T, [key]), + ]; + } + List<T> list<T extends Object?>(String key) => _castList<T>(get<List<Object?>>(key), key); + List<String> validateList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + return _validateListElements(get<List<Object?>>(key), key); + } + List<T>? optionalList<T extends Object?>(String key) => switch (get<List<Object?>?>(key)?.cast<T>()) { null => null, final l => _castList<T>(l, key), }; + List<String> validateOptionalList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>?>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + final list = get<List<Object?>?>(key); + if (list == null) { + return []; + } + return _validateListElements(list, key); + } + /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { var index = 0; @@ -128,6 +170,21 @@ class JsonReader { return list.cast(); } + List<String> _validateListElements<T extends Object?>( + List<Object?> list, + String key, + ) { + var index = 0; + final result = <String>[]; + for (final value in list) { + if (value is! T) { + result.add(errorString(value, T, [key, index])); + } + index++; + } + return result; + } + List<T>? optionalListParsed<T extends Object?>( String key, T Function(Object?) elementParser, @@ -140,12 +197,32 @@ class JsonReader { Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); + List<String> validateMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return _validateMapElements<T>(get<Map<String, Object?>>(key), key); + } + Map<String, T>? optionalMap<T extends Object?>(String key) => switch (get<Map<String, Object?>?>(key)) { null => null, final m => _castMap<T>(m, key), }; + List<String> validateOptionalMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>?>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final map = get<Map<String, Object?>?>(key); + if (map == null) { + return []; + } + return _validateMapElements<T>(map, key); + } + /// [Map.cast] but with [FormatException]s. Map<String, T> _castMap<T extends Object?>( Map<String, Object?> map_, @@ -159,18 +236,40 @@ class JsonReader { return map_.cast(); } + List<String> _validateMapElements<T extends Object?>( + Map<String, Object?> map_, + String parentKey, + ) { + final result = <String>[]; + for (final MapEntry(:key, :value) in map_.entries) { + if (value is! T) { + result.add(errorString(value, T, [parentKey, key])); + } + } + return result; + } + List<String>? optionalStringList(String key) => optionalList<String>(key); + List<String> validateOptionalStringList(String key) => + validateOptionalList<String>(key); + List<String> stringList(String key) => list<String>(key); + List<String> validateStringList(String key) => validateList<String>(key); + Uri path$(String key) => _fileSystemPathToUri(get<String>(key)); + List<String> validatePath(String key) => validate<String>(key); + Uri? optionalPath(String key) { final value = get<String?>(key); if (value == null) return null; return _fileSystemPathToUri(value); } + List<String> validateOptionalPath(String key) => validate<String?>(key); + List<Uri>? optionalPathList(String key) { final strings = optionalStringList(key); if (strings == null) { @@ -179,6 +278,9 @@ class JsonReader { return [for (final string in strings) _fileSystemPathToUri(string)]; } + List<String> validateOptionalPathList(String key) => + validateOptionalStringList(key); + static Uri _fileSystemPathToUri(String path) { if (path.endsWith(Platform.pathSeparator)) { return Uri.directory(path); @@ -193,12 +295,22 @@ class JsonReader { Object? value, Type expectedType, List<Object> pathExtension, + ) { + throw FormatException(errorString(value, expectedType, pathExtension)); + } + + String errorString( + Object? value, + Type expectedType, + List<Object> pathExtension, ) { final pathString = _jsonPathToString(pathExtension); - throw FormatException( - "Unexpected value '$value' (${value.runtimeType}) for '$pathString'. " - 'Expected a $expectedType.', - ); + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected a $expectedType.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected a $expectedType.'; } } diff --git a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart index 234310bd1e..dc6801a55e 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart @@ -5,18 +5,28 @@ import 'dart:io'; import '../../data_assets_builder.dart'; +import 'syntax.g.dart' as syntax; Future<ValidationErrors> validateDataAssetBuildInput(BuildInput input) async => const []; Future<ValidationErrors> validateDataAssetLinkInput(LinkInput input) async { - final errors = <String>[ - for (final asset in input.assets.data) - ..._validateFile( - 'LinkInput.assets.data asset "${asset.id}" file', - asset.file, + final errors = <String>[]; + for (final asset in input.assets.encodedAssets) { + final syntaxErrors = _validateDataAssetSyntax(asset); + if (asset.type != DataAsset.type) continue; + if (syntaxErrors.isNotEmpty) { + errors.addAll(syntaxErrors); + continue; + } + final dataAsset = DataAsset.fromEncoded(asset); + errors.addAll( + _validateFile( + 'LinkInput.assets.data asset "${dataAsset.id}" file', + dataAsset.file, ), - ]; + ); + } return errors; } @@ -48,6 +58,11 @@ Future<ValidationErrors> _validateDataAssetBuildOrLinkOutput( for (final asset in encodedAssets) { if (asset.type != DataAsset.type) continue; + final syntaxErrors = _validateDataAssetSyntax(asset); + if (syntaxErrors.isNotEmpty) { + errors.addAll(syntaxErrors); + continue; + } _validateDataAsset( input, DataAsset.fromEncoded(asset), @@ -76,6 +91,26 @@ void _validateDataAsset( errors.addAll(_validateFile('Data asset ${dataAsset.name} file', file)); } +List<String> _validateDataAssetSyntax(EncodedAsset encodedAsset) { + final syntaxNode = syntax.Asset.fromJson( + encodedAsset.toJson(), + path: encodedAsset.jsonPath ?? [], + ); + if (!syntaxNode.isDataAsset) { + return []; + } + final syntaxErrors = syntaxNode.asDataAsset.validate(); + if (syntaxErrors.isEmpty) { + return []; + } + return [...syntaxErrors, semanticValidationSkippedMessage(syntaxNode.path)]; +} + +String semanticValidationSkippedMessage(List<Object> jsonPath) { + final pathString = jsonPath.join('.'); + return "Syntax errors in '$pathString'. Semantic validation skipped."; +} + ValidationErrors _validateFile( String name, Uri uri, { diff --git a/pkgs/native_assets_cli/lib/src/extension.dart b/pkgs/native_assets_cli/lib/src/extension.dart index ed1231d325..384c0a323c 100644 --- a/pkgs/native_assets_cli/lib/src/extension.dart +++ b/pkgs/native_assets_cli/lib/src/extension.dart @@ -14,7 +14,7 @@ typedef ValidationErrors = List<String>; /// /// The extension contains callbacks to /// 1. setup the input, and -/// 2. validate semantic constraints. +/// 2. validate syntactic and semantic constraints. abstract interface class ProtocolExtension { /// The [HookConfig.buildAssetTypes] this extension adds. List<String> get buildAssetTypes; @@ -25,19 +25,19 @@ abstract interface class ProtocolExtension { /// Setup the [HookConfig] for this extension. void setupLinkInput(LinkInputBuilder input); - /// Reports semantic errors from this extension on the [BuildInput]. + /// Reports errors from this extension on the [BuildInput]. Future<ValidationErrors> validateBuildInput(BuildInput input); - /// Reports semantic errors from this extension on the [LinkInput]. + /// Reports errors from this extension on the [LinkInput]. Future<ValidationErrors> validateBuildOutput( BuildInput input, BuildOutput output, ); - /// Reports semantic errors from this extension on the [LinkInput]. + /// Reports errors from this extension on the [LinkInput]. Future<ValidationErrors> validateLinkInput(LinkInput input); - /// Reports semantic errors from this extension on the [LinkOutput]. + /// Reports errors from this extension on the [LinkOutput]. Future<ValidationErrors> validateLinkOutput( LinkInput input, LinkOutput output, diff --git a/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart b/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart index bb2b4492a9..b1820c61ff 100644 --- a/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart @@ -28,6 +28,10 @@ class Asset { json.setOrRemove('type', value); } + List<String> _validateType() => _reader.validate<String>('type'); + + List<String> validate() => [..._validateType()]; + @override String toString() => 'Asset($json)'; } @@ -54,6 +58,15 @@ class BuildConfig extends Config { json.setOrRemove('linking_enabled', value); } + List<String> _validateLinkingEnabled() => + _reader.validate<bool>('linking_enabled'); + + @override + List<String> validate() => [ + ...super.validate(), + ..._validateLinkingEnabled(), + ]; + @override String toString() => 'BuildConfig($json)'; } @@ -95,6 +108,16 @@ class BuildInput extends HookInput { json.setOrRemove('dependency_metadata', value); } + List<String> _validateDependencyMetadata() => + _reader.validateOptionalMap<Map<String, Object?>>('dependency_metadata'); + + @override + List<String> validate() => [ + ...super.validate(), + ..._validateConfig(), + ..._validateDependencyMetadata(), + ]; + @override String toString() => 'BuildInput($json)'; } @@ -157,6 +180,24 @@ class BuildOutput extends HookOutput { json.sortOnKey(); } + List<String> _validateAssetsForLinking() { + final mapErrors = _reader.validateOptionalMap('assetsForLinking'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final jsonValue = _reader.optionalMap('assetsForLinking'); + if (jsonValue == null) { + return []; + } + final result = <String>[]; + for (final list in assetsForLinking!.values) { + for (final element in list) { + result.addAll(element.validate()); + } + } + return result; + } + Map<String, Object?>? get metadata => _reader.optionalMap('metadata'); set metadata(Map<String, Object?>? value) { @@ -164,6 +205,15 @@ class BuildOutput extends HookOutput { json.sortOnKey(); } + List<String> _validateMetadata() => _reader.validateOptionalMap('metadata'); + + @override + List<String> validate() => [ + ...super.validate(), + ..._validateAssetsForLinking(), + ..._validateMetadata(), + ]; + @override String toString() => 'BuildOutput($json)'; } @@ -189,6 +239,11 @@ class Config { json.sortOnKey(); } + List<String> _validateBuildAssetTypes() => + _reader.validateStringList('build_asset_types'); + + List<String> validate() => [..._validateBuildAssetTypes()]; + @override String toString() => 'Config($json)'; } @@ -232,6 +287,14 @@ class HookInput { json.sortOnKey(); } + List<String> _validateConfig() { + final mapErrors = _reader.validate<Map<String, Object?>>('config'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return config.validate(); + } + Uri get outDir => _reader.path$('out_dir'); set outDir(Uri value) { @@ -239,6 +302,8 @@ class HookInput { json.sortOnKey(); } + List<String> _validateOutDir() => _reader.validatePath('out_dir'); + Uri get outDirShared => _reader.path$('out_dir_shared'); set outDirShared(Uri value) { @@ -246,6 +311,9 @@ class HookInput { json.sortOnKey(); } + List<String> _validateOutDirShared() => + _reader.validatePath('out_dir_shared'); + Uri? get outFile => _reader.optionalPath('out_file'); set outFile(Uri? value) { @@ -253,6 +321,8 @@ class HookInput { json.sortOnKey(); } + List<String> _validateOutFile() => _reader.validateOptionalPath('out_file'); + String get packageName => _reader.get<String>('package_name'); set packageName(String value) { @@ -260,6 +330,9 @@ class HookInput { json.sortOnKey(); } + List<String> _validatePackageName() => + _reader.validate<String>('package_name'); + Uri get packageRoot => _reader.path$('package_root'); set packageRoot(Uri value) { @@ -267,6 +340,8 @@ class HookInput { json.sortOnKey(); } + List<String> _validatePackageRoot() => _reader.validatePath('package_root'); + String get version => _reader.get<String>('version'); set version(String value) { @@ -274,6 +349,18 @@ class HookInput { json.sortOnKey(); } + List<String> _validateVersion() => _reader.validate<String>('version'); + + List<String> validate() => [ + ..._validateConfig(), + ..._validateOutDir(), + ..._validateOutDirShared(), + ..._validateOutFile(), + ..._validatePackageName(), + ..._validatePackageRoot(), + ..._validateVersion(), + ]; + @override String toString() => 'HookInput($json)'; } @@ -321,6 +408,20 @@ class HookOutput { json.sortOnKey(); } + List<String> _validateAssets() { + final listErrors = _reader.validateOptionalList<Map<String, Object?>>( + 'assets', + ); + if (listErrors.isNotEmpty) { + return listErrors; + } + final elements = assets; + if (elements == null) { + return []; + } + return [for (final element in elements) ...element.validate()]; + } + List<Uri>? get dependencies => _reader.optionalPathList('dependencies'); set dependencies(List<Uri>? value) { @@ -328,6 +429,9 @@ class HookOutput { json.sortOnKey(); } + List<String> _validateDependencies() => + _reader.validateOptionalPathList('dependencies'); + String get timestamp => _reader.get<String>('timestamp'); set timestamp(String value) { @@ -335,6 +439,8 @@ class HookOutput { json.sortOnKey(); } + List<String> _validateTimestamp() => _reader.validate<String>('timestamp'); + String get version => _reader.get<String>('version'); set version(String value) { @@ -342,6 +448,15 @@ class HookOutput { json.sortOnKey(); } + List<String> _validateVersion() => _reader.validate<String>('version'); + + List<String> validate() => [ + ..._validateAssets(), + ..._validateDependencies(), + ..._validateTimestamp(), + ..._validateVersion(), + ]; + @override String toString() => 'HookOutput($json)'; } @@ -392,12 +507,36 @@ class LinkInput extends HookInput { } } + List<String> _validateAssets() { + final listErrors = _reader.validateOptionalList<Map<String, Object?>>( + 'assets', + ); + if (listErrors.isNotEmpty) { + return listErrors; + } + final elements = assets; + if (elements == null) { + return []; + } + return [for (final element in elements) ...element.validate()]; + } + Uri? get resourceIdentifiers => _reader.optionalPath('resource_identifiers'); set _resourceIdentifiers(Uri? value) { json.setOrRemove('resource_identifiers', value?.toFilePath()); } + List<String> _validateResourceIdentifiers() => + _reader.validateOptionalPath('resource_identifiers'); + + @override + List<String> validate() => [ + ...super.validate(), + ..._validateAssets(), + ..._validateResourceIdentifiers(), + ]; + @override String toString() => 'LinkInput($json)'; } @@ -412,6 +551,9 @@ class LinkOutput extends HookOutput { required super.version, }) : super(); + @override + List<String> validate() => [...super.validate()]; + @override String toString() => 'LinkOutput($json)'; } @@ -432,22 +574,46 @@ class JsonReader { T get<T extends Object?>(String key) { final value = json[key]; if (value is T) return value; - final pathString = _jsonPathToString([key]); - if (value == null) { - throw FormatException("No value was provided for '$pathString'."); - } throwFormatException(value, T, [key]); } + List<String> validate<T extends Object?>(String key) { + final value = json[key]; + if (value is T) return []; + return [ + errorString(value, T, [key]), + ]; + } + List<T> list<T extends Object?>(String key) => _castList<T>(get<List<Object?>>(key), key); + List<String> validateList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + return _validateListElements(get<List<Object?>>(key), key); + } + List<T>? optionalList<T extends Object?>(String key) => switch (get<List<Object?>?>(key)?.cast<T>()) { null => null, final l => _castList<T>(l, key), }; + List<String> validateOptionalList<T extends Object?>(String key) { + final listErrors = validate<List<Object?>?>(key); + if (listErrors.isNotEmpty) { + return listErrors; + } + final list = get<List<Object?>?>(key); + if (list == null) { + return []; + } + return _validateListElements(list, key); + } + /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { var index = 0; @@ -460,6 +626,21 @@ class JsonReader { return list.cast(); } + List<String> _validateListElements<T extends Object?>( + List<Object?> list, + String key, + ) { + var index = 0; + final result = <String>[]; + for (final value in list) { + if (value is! T) { + result.add(errorString(value, T, [key, index])); + } + index++; + } + return result; + } + List<T>? optionalListParsed<T extends Object?>( String key, T Function(Object?) elementParser, @@ -472,12 +653,32 @@ class JsonReader { Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); + List<String> validateMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return _validateMapElements<T>(get<Map<String, Object?>>(key), key); + } + Map<String, T>? optionalMap<T extends Object?>(String key) => switch (get<Map<String, Object?>?>(key)) { null => null, final m => _castMap<T>(m, key), }; + List<String> validateOptionalMap<T extends Object?>(String key) { + final mapErrors = validate<Map<String, Object?>?>(key); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final map = get<Map<String, Object?>?>(key); + if (map == null) { + return []; + } + return _validateMapElements<T>(map, key); + } + /// [Map.cast] but with [FormatException]s. Map<String, T> _castMap<T extends Object?>( Map<String, Object?> map_, @@ -491,18 +692,40 @@ class JsonReader { return map_.cast(); } + List<String> _validateMapElements<T extends Object?>( + Map<String, Object?> map_, + String parentKey, + ) { + final result = <String>[]; + for (final MapEntry(:key, :value) in map_.entries) { + if (value is! T) { + result.add(errorString(value, T, [parentKey, key])); + } + } + return result; + } + List<String>? optionalStringList(String key) => optionalList<String>(key); + List<String> validateOptionalStringList(String key) => + validateOptionalList<String>(key); + List<String> stringList(String key) => list<String>(key); + List<String> validateStringList(String key) => validateList<String>(key); + Uri path$(String key) => _fileSystemPathToUri(get<String>(key)); + List<String> validatePath(String key) => validate<String>(key); + Uri? optionalPath(String key) { final value = get<String?>(key); if (value == null) return null; return _fileSystemPathToUri(value); } + List<String> validateOptionalPath(String key) => validate<String?>(key); + List<Uri>? optionalPathList(String key) { final strings = optionalStringList(key); if (strings == null) { @@ -511,6 +734,9 @@ class JsonReader { return [for (final string in strings) _fileSystemPathToUri(string)]; } + List<String> validateOptionalPathList(String key) => + validateOptionalStringList(key); + static Uri _fileSystemPathToUri(String path) { if (path.endsWith(Platform.pathSeparator)) { return Uri.directory(path); @@ -525,12 +751,22 @@ class JsonReader { Object? value, Type expectedType, List<Object> pathExtension, + ) { + throw FormatException(errorString(value, expectedType, pathExtension)); + } + + String errorString( + Object? value, + Type expectedType, + List<Object> pathExtension, ) { final pathString = _jsonPathToString(pathExtension); - throw FormatException( - "Unexpected value '$value' (${value.runtimeType}) for '$pathString'. " - 'Expected a $expectedType.', - ); + if (value == null) { + return "No value was provided for '$pathString'." + ' Expected a $expectedType.'; + } + return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'." + ' Expected a $expectedType.'; } } diff --git a/pkgs/native_assets_cli/lib/src/validation.dart b/pkgs/native_assets_cli/lib/src/validation.dart index bec1b1915c..d0f2fd8cb3 100644 --- a/pkgs/native_assets_cli/lib/src/validation.dart +++ b/pkgs/native_assets_cli/lib/src/validation.dart @@ -5,13 +5,25 @@ import 'dart:io'; import '../native_assets_cli_builder.dart'; +import 'hook/syntax.g.dart' as syntax; typedef ValidationErrors = List<String>; -Future<ValidationErrors> validateBuildInput(BuildInput input) async => - _validateHookInput('BuildInput', input); +Future<ValidationErrors> validateBuildInput(BuildInput input) async { + final syntaxErrors = syntax.BuildInput.fromJson(input.json).validate(); + if (syntaxErrors.isNotEmpty) { + return [...syntaxErrors, _semanticValidationSkippedMessage]; + } + + return _validateHookInput('BuildInput', input); +} Future<ValidationErrors> validateLinkInput(LinkInput input) async { + final syntaxErrors = syntax.LinkInput.fromJson(input.json).validate(); + if (syntaxErrors.isNotEmpty) { + return [...syntaxErrors, _semanticValidationSkippedMessage]; + } + final recordUses = input.recordedUsagesFile; return <String>[ ..._validateHookInput('LinkInput', input), @@ -61,6 +73,11 @@ Future<ValidationErrors> validateBuildOutput( BuildInput input, BuildOutput output, ) async { + final syntaxErrors = syntax.BuildOutput.fromJson(output.json).validate(); + if (syntaxErrors.isNotEmpty) { + return [...syntaxErrors, _semanticValidationSkippedMessage]; + } + final errors = [ ..._validateAssetsForLinking(input, output), ..._validateOutputAssetTypes(input, output.assets.encodedAssets), @@ -78,6 +95,11 @@ Future<ValidationErrors> validateLinkOutput( LinkInput input, LinkOutput output, ) async { + final syntaxErrors = syntax.LinkOutput.fromJson(output.json).validate(); + if (syntaxErrors.isNotEmpty) { + return [...syntaxErrors, _semanticValidationSkippedMessage]; + } + final errors = [ ..._validateOutputAssetTypes(input, output.assets.encodedAssets), ]; @@ -121,6 +143,9 @@ List<String> _validateAssetsForLinking(BuildInput input, BuildOutput output) { return errors; } +const _semanticValidationSkippedMessage = + 'Syntax errors. Semantic validation skipped.'; + class ValidationFailure implements Exception { final String? message; From 30913fe791e8020567f3f8b83ebc123ca5bad8ee Mon Sep 17 00:00:00 2001 From: Daco Harkes <dacoharkes@google.com> Date: Thu, 20 Mar 2025 14:04:46 +0100 Subject: [PATCH 2/2] address comments --- .../lib/src/generator/helper_library.dart | 17 +----- .../lib/src/generator/property_generator.dart | 22 ++++---- .../lib/src/code_assets/syntax.g.dart | 17 +----- .../lib/src/code_assets/validation.dart | 10 ++-- .../lib/src/data_assets/syntax.g.dart | 17 +----- .../lib/src/data_assets/validation.dart | 4 +- .../lib/src/hook/syntax.g.dart | 56 ++++++++----------- .../native_assets_cli/lib/src/validation.dart | 7 ++- 8 files changed, 51 insertions(+), 99 deletions(-) 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 91857c6b70..e93a71bffb 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/helper_library.dart @@ -64,12 +64,10 @@ class JsonReader { /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { - var index = 0; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { throwFormatException(value, T, [key, index]); } - index++; } return list.cast(); } @@ -78,26 +76,15 @@ class JsonReader { List<Object?> list, String key, ) { - var index = 0; final result = <String>[]; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { result.add(errorString(value, T, [key, index])); } - index++; } return result; } - List<T>? optionalListParsed<T extends Object?>( - String key, - T Function(Object?) elementParser, - ) { - final jsonValue = optionalList(key); - if (jsonValue == null) return null; - return [for (final element in jsonValue) elementParser(element)]; - } - Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); 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 093838d88f..f12daf3dd3 100644 --- a/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart +++ b/pkgs/json_syntax_generator/lib/src/generator/property_generator.dart @@ -218,12 +218,11 @@ $dartType get $fieldName { } final result = <String, List<Asset>>{}; for (final MapEntry(:key, :value) in jsonValue.entries) { - var index = 0; result[key] = [ - for (final item in value as List<Object?>) + for (final (index, item) in (value as List<Object?>).indexed) $typeName.fromJson( item as $jsonObjectDartType, - path: [...path, key, index++], + path: [...path, key, index], ), ]; } @@ -311,14 +310,15 @@ List<String> $validateName() => _reader.validateOptionalMap('$jsonKey'); } buffer.writeln(''' $dartType get $fieldName { - var index = 0; - return _reader.optionalListParsed( - '$jsonKey', - (e) => $typeName.fromJson( - e as Map<String, Object?>, - path: [...path, '$jsonKey', index++], - ), - ); + final jsonValue = _reader.optionalList('$jsonKey'); + if (jsonValue == null) return null; + return [ + for (final (index, element) in jsonValue.indexed) + $typeName.fromJson( + element as Map<String, Object?>, + path: [...path, '$jsonKey', index], + ), + ]; } set $setterName($dartType value) { diff --git a/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart b/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart index ff06c586ff..b81e9e86f7 100644 --- a/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/code_assets/syntax.g.dart @@ -953,12 +953,10 @@ class JsonReader { /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { - var index = 0; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { throwFormatException(value, T, [key, index]); } - index++; } return list.cast(); } @@ -967,26 +965,15 @@ class JsonReader { List<Object?> list, String key, ) { - var index = 0; final result = <String>[]; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { result.add(errorString(value, T, [key, index])); } - index++; } return result; } - List<T>? optionalListParsed<T extends Object?>( - String key, - T Function(Object?) elementParser, - ) { - final jsonValue = optionalList(key); - if (jsonValue == null) return null; - return [for (final element in jsonValue) elementParser(element)]; - } - Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); diff --git a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart index d68dcea6a5..7a19e24b16 100644 --- a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart +++ b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart @@ -78,7 +78,7 @@ ValidationErrors _validateConfig(String inputName, HookConfig config) { return errors; } -List<String> _validateConfigSyntax(HookConfig config) { +ValidationErrors _validateConfigSyntax(HookConfig config) { final syntaxNode = syntax.Config.fromJson(config.json, path: config.path); final syntaxErrors = syntaxNode.validate(); if (syntaxErrors.isEmpty) { @@ -206,7 +206,7 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( return errors; } -List<String> _validateCodeAssetSyntax(EncodedAsset encodedAsset) { +ValidationErrors _validateCodeAssetSyntax(EncodedAsset encodedAsset) { final syntaxNode = syntax.Asset.fromJson( encodedAsset.toJson(), path: encodedAsset.jsonPath ?? [], @@ -230,7 +230,7 @@ void _validateCodeAsset( HookInput input, CodeConfig codeConfig, CodeAsset codeAsset, - List<String> errors, + ValidationErrors errors, Set<String> ids, bool validateAssetId, bool validateLinkMode, @@ -281,7 +281,7 @@ void _validateCodeAsset( errors.addAll(_validateCodeAssetFile(codeAsset)); } -List<String> _validateCodeAssetFile(CodeAsset codeAsset) { +ValidationErrors _validateCodeAssetFile(CodeAsset codeAsset) { final id = codeAsset.id; final file = codeAsset.file; return [ @@ -313,7 +313,7 @@ void _groupCodeAssetsByFilename( } void _validateNoDuplicateDylibNames( - List<String> errors, + ValidationErrors errors, Map<String, Set<String>> fileNameToEncodedAssetId, ) { for (final fileName in fileNameToEncodedAssetId.keys) { diff --git a/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart b/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart index 34c1649c17..6d028093d8 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/syntax.g.dart @@ -160,12 +160,10 @@ class JsonReader { /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { - var index = 0; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { throwFormatException(value, T, [key, index]); } - index++; } return list.cast(); } @@ -174,26 +172,15 @@ class JsonReader { List<Object?> list, String key, ) { - var index = 0; final result = <String>[]; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { result.add(errorString(value, T, [key, index])); } - index++; } return result; } - List<T>? optionalListParsed<T extends Object?>( - String key, - T Function(Object?) elementParser, - ) { - final jsonValue = optionalList(key); - if (jsonValue == null) return null; - return [for (final element in jsonValue) elementParser(element)]; - } - Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); diff --git a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart index dc6801a55e..0b88dc6117 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart @@ -77,7 +77,7 @@ Future<ValidationErrors> _validateDataAssetBuildOrLinkOutput( void _validateDataAsset( HookInput input, DataAsset dataAsset, - List<String> errors, + ValidationErrors errors, Set<String> ids, bool isBuild, ) { @@ -91,7 +91,7 @@ void _validateDataAsset( errors.addAll(_validateFile('Data asset ${dataAsset.name} file', file)); } -List<String> _validateDataAssetSyntax(EncodedAsset encodedAsset) { +ValidationErrors _validateDataAssetSyntax(EncodedAsset encodedAsset) { final syntaxNode = syntax.Asset.fromJson( encodedAsset.toJson(), path: encodedAsset.jsonPath ?? [], diff --git a/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart b/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart index b1820c61ff..335f574d81 100644 --- a/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/hook/syntax.g.dart @@ -156,12 +156,11 @@ class BuildOutput extends HookOutput { } final result = <String, List<Asset>>{}; for (final MapEntry(:key, :value) in jsonValue.entries) { - var index = 0; result[key] = [ - for (final item in value as List<Object?>) + for (final (index, item) in (value as List<Object?>).indexed) Asset.fromJson( item as Map<String, Object?>, - path: [...path, key, index++], + path: [...path, key, index], ), ]; } @@ -389,14 +388,15 @@ class HookOutput { } List<Asset>? get assets { - var index = 0; - return _reader.optionalListParsed( - 'assets', - (e) => Asset.fromJson( - e as Map<String, Object?>, - path: [...path, 'assets', index++], - ), - ); + final jsonValue = _reader.optionalList('assets'); + if (jsonValue == null) return null; + return [ + for (final (index, element) in jsonValue.indexed) + Asset.fromJson( + element as Map<String, Object?>, + path: [...path, 'assets', index], + ), + ]; } set assets(List<Asset>? value) { @@ -489,14 +489,15 @@ class LinkInput extends HookInput { } List<Asset>? get assets { - var index = 0; - return _reader.optionalListParsed( - 'assets', - (e) => Asset.fromJson( - e as Map<String, Object?>, - path: [...path, 'assets', index++], - ), - ); + final jsonValue = _reader.optionalList('assets'); + if (jsonValue == null) return null; + return [ + for (final (index, element) in jsonValue.indexed) + Asset.fromJson( + element as Map<String, Object?>, + path: [...path, 'assets', index], + ), + ]; } set _assets(List<Asset>? value) { @@ -616,12 +617,10 @@ class JsonReader { /// [List.cast] but with [FormatException]s. List<T> _castList<T extends Object?>(List<Object?> list, String key) { - var index = 0; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { throwFormatException(value, T, [key, index]); } - index++; } return list.cast(); } @@ -630,26 +629,15 @@ class JsonReader { List<Object?> list, String key, ) { - var index = 0; final result = <String>[]; - for (final value in list) { + for (final (index, value) in list.indexed) { if (value is! T) { result.add(errorString(value, T, [key, index])); } - index++; } return result; } - List<T>? optionalListParsed<T extends Object?>( - String key, - T Function(Object?) elementParser, - ) { - final jsonValue = optionalList(key); - if (jsonValue == null) return null; - return [for (final element in jsonValue) elementParser(element)]; - } - Map<String, T> map$<T extends Object?>(String key) => _castMap<T>(get<Map<String, Object?>>(key), key); diff --git a/pkgs/native_assets_cli/lib/src/validation.dart b/pkgs/native_assets_cli/lib/src/validation.dart index d0f2fd8cb3..1c798ffee7 100644 --- a/pkgs/native_assets_cli/lib/src/validation.dart +++ b/pkgs/native_assets_cli/lib/src/validation.dart @@ -107,7 +107,7 @@ Future<ValidationErrors> validateLinkOutput( } /// Only output asset types that are supported by the embedder. -List<String> _validateOutputAssetTypes( +ValidationErrors _validateOutputAssetTypes( HookInput input, Iterable<EncodedAsset> assets, ) { @@ -130,7 +130,10 @@ List<String> _validateOutputAssetTypes( } /// EncodedAssetsForLinking should be empty if linking is not supported. -List<String> _validateAssetsForLinking(BuildInput input, BuildOutput output) { +ValidationErrors _validateAssetsForLinking( + BuildInput input, + BuildOutput output, +) { final errors = <String>[]; if (!input.config.linkingEnabled) { if (output.assets.encodedAssetsForLinking.isNotEmpty) {