Skip to content

Commit 543902c

Browse files
authored
[native_assets_cli] Add paths to syntax views (#2107)
Bug: #1826 This PR adds the path that was navigated through the JSON to the syntax classes. This makes syntax errors more descriptive: `Unexpected value '123' for 'target_os'. Expected a String.` -> `Unexpected value '123' (int) for 'config.code.target_os'. Expected a String.`. Because the data asset and code asset extensions don't know the internals of the base protocol extension, the extension points now expose both a `json` and a `jsonPath`. That path is then used to instantiate the syntax view of the extension, so it can give more precise error messages. * For `CodeConfig` this comes naturally, it's a clean view. * For `EncodedAsset` this is somewhat weird because it is half-used as value class: #2045 (The next PR will add a full syntax validation pass, at which point we should be able to address #2039.)
1 parent 10a25e3 commit 543902c

16 files changed

+634
-420
lines changed

pkgs/json_syntax_generator/lib/src/generator/helper_library.dart

+43-20
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@
66
///
77
/// This simplifies the code generator.
88
const helperLib = r'''
9-
extension on Map<String, Object?> {
9+
class JsonReader {
10+
/// The JSON Object this reader is reading.
11+
final Map<String, Object?> json;
12+
13+
/// The path traversed by readers of the surrounding JSON.
14+
///
15+
/// Contains [String] property keys and [int] indices.
16+
///
17+
/// This is used to give more precise error messages.
18+
final List<Object> path;
19+
20+
JsonReader(this.json, this.path);
21+
1022
T get<T extends Object?>(String key) {
11-
final value = this[key];
23+
final value = json[key];
1224
if (value is T) return value;
25+
final pathString = _jsonPathToString([key]);
1326
if (value == null) {
14-
throw FormatException('No value was provided for required key: $key');
27+
throw FormatException("No value was provided for '$pathString'.");
1528
}
16-
throw FormatException(
17-
'Unexpected value \'$value\' for key \'.$key\'. '
18-
'Expected a $T.',
19-
);
29+
throwFormatException(value, T, [key]);
2030
}
2131
2232
List<T> list<T extends Object?>(String key) =>
@@ -29,14 +39,13 @@ extension on Map<String, Object?> {
2939
};
3040
3141
/// [List.cast] but with [FormatException]s.
32-
static List<T> _castList<T extends Object?>(List<Object?> list, String key) {
42+
List<T> _castList<T extends Object?>(List<Object?> list, String key) {
43+
var index = 0;
3344
for (final value in list) {
3445
if (value is! T) {
35-
throw FormatException(
36-
'Unexpected value \'$list\' (${list.runtimeType}) for key \'.$key\'. '
37-
'Expected a ${List<T>}.',
38-
);
46+
throwFormatException(value, T, [key, index]);
3947
}
48+
index++;
4049
}
4150
return list.cast();
4251
}
@@ -60,16 +69,13 @@ extension on Map<String, Object?> {
6069
};
6170
6271
/// [Map.cast] but with [FormatException]s.
63-
static Map<String, T> _castMap<T extends Object?>(
72+
Map<String, T> _castMap<T extends Object?>(
6473
Map<String, Object?> map_,
65-
String key,
74+
String parentKey,
6675
) {
67-
for (final value in map_.values) {
76+
for (final MapEntry(:key, :value) in map_.entries) {
6877
if (value is! T) {
69-
throw FormatException(
70-
'Unexpected value \'$map_\' (${map_.runtimeType}) for key \'.$key\'.'
71-
'Expected a ${Map<String, T>}.',
72-
);
78+
throwFormatException(value, T, [parentKey, key]);
7379
}
7480
}
7581
return map_.cast();
@@ -79,7 +85,7 @@ extension on Map<String, Object?> {
7985
8086
List<String> stringList(String key) => list<String>(key);
8187
82-
Uri path(String key) => _fileSystemPathToUri(get<String>(key));
88+
Uri path$(String key) => _fileSystemPathToUri(get<String>(key));
8389
8490
Uri? optionalPath(String key) {
8591
final value = get<String?>(key);
@@ -102,6 +108,23 @@ extension on Map<String, Object?> {
102108
return Uri.file(path);
103109
}
104110
111+
String _jsonPathToString(List<Object> pathEnding) =>
112+
[...path, ...pathEnding].join('.');
113+
114+
Never throwFormatException(
115+
Object? value,
116+
Type expectedType,
117+
List<Object> pathExtension,
118+
) {
119+
final pathString = _jsonPathToString(pathExtension);
120+
throw FormatException(
121+
"Unexpected value '$value' (${value.runtimeType}) for '$pathString'. "
122+
'Expected a $expectedType.',
123+
);
124+
}
125+
}
126+
127+
extension on Map<String, Object?> {
105128
void setOrRemove(String key, Object? value) {
106129
if (value == null) {
107130
remove(key);

pkgs/json_syntax_generator/lib/src/generator/normal_class_generator.dart

+14-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ class ClassGenerator {
7272
if (superclass != null) {
7373
buffer.writeln('''
7474
class $className extends $superclassName {
75-
$className.fromJson(super.json) : super.fromJson();
75+
$className.fromJson(super.json, {
76+
super.path,
77+
}) : super.fromJson();
7678
7779
$className(${wrapBracesIfNotEmpty(constructorParams.join(', '))})
7880
: super(${superParams.join(',')})
@@ -102,9 +104,18 @@ class $className extends $superclassName {
102104
class $className {
103105
final Map<String, Object?> json;
104106
105-
$className.fromJson(this.json);
107+
final List<Object> path;
106108
107-
$className(${wrapBracesIfNotEmpty(constructorParams.join(', '))}) : json = {} {
109+
JsonReader get _reader => JsonReader(json, path);
110+
111+
$className.fromJson(this.json, {
112+
this.path = const [],
113+
});
114+
115+
$className(${wrapBracesIfNotEmpty(constructorParams.join(', '))})
116+
: json = {},
117+
path = const []
118+
{
108119
${constructorSetterCalls.join('\n ')}
109120
}
110121

pkgs/json_syntax_generator/lib/src/generator/property_generator.dart

+34-21
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class PropertyGenerator {
9595

9696
buffer.writeln('''
9797
$dartType get $fieldName {
98-
final jsonValue = json.get<$dartStringType>('$jsonKey'); $earlyReturn
98+
final jsonValue = _reader.get<$dartStringType>('$jsonKey'); $earlyReturn
9999
return $classType.fromJson(jsonValue);
100100
}
101101
''');
@@ -115,8 +115,8 @@ set $setterName($dartType value) {
115115
final jsonRead = required ? 'map\$' : 'optionalMap';
116116
buffer.writeln('''
117117
$dartType get $fieldName {
118-
final jsonValue = json.$jsonRead('$jsonKey'); $earlyReturn
119-
return $classType.fromJson(jsonValue);
118+
final jsonValue = _reader.$jsonRead('$jsonKey'); $earlyReturn
119+
return $classType.fromJson(jsonValue, path: [...path, '$jsonKey']);
120120
}
121121
''');
122122
if (!property.isOverride) {
@@ -142,7 +142,7 @@ set $setterName($dartType value) {
142142
final fieldName = property.name;
143143

144144
buffer.writeln('''
145-
$dartType get $fieldName => json.get<$dartType>('$jsonKey');
145+
$dartType get $fieldName => _reader.get<$dartType>('$jsonKey');
146146
147147
set $setterName($dartType value) {
148148
json.setOrRemove('$jsonKey', value);
@@ -169,7 +169,7 @@ set $setterName($dartType value) {
169169
case MapDartType():
170170
buffer.writeln('''
171171
$dartType get $fieldName =>
172-
json.optionalMap<${dartType.valueType}>('$jsonKey');
172+
_reader.optionalMap<${dartType.valueType}>('$jsonKey');
173173
174174
set $setterName($dartType value) {
175175
json.setOrRemove('$jsonKey', value);
@@ -181,17 +181,22 @@ set $setterName($dartType value) {
181181
final typeName = itemType.toString();
182182
buffer.writeln('''
183183
$dartType get $fieldName {
184-
final map_ = json.optionalMap('$jsonKey');
185-
if(map_ == null){
184+
final jsonValue = _reader.optionalMap('$jsonKey');
185+
if (jsonValue == null) {
186186
return null;
187187
}
188-
return {
189-
for (final MapEntry(:key, :value) in map_.entries)
190-
key : [
191-
for (final item in value as List<Object?>)
192-
$typeName.fromJson(item as $jsonObjectDartType)
193-
],
194-
};
188+
final result = <String, List<Asset>>{};
189+
for (final MapEntry(:key, :value) in jsonValue.entries) {
190+
var index = 0;
191+
result[key] = [
192+
for (final item in value as List<Object?>)
193+
$typeName.fromJson(
194+
item as $jsonObjectDartType,
195+
path: [...path, key, index++],
196+
),
197+
];
198+
}
199+
return result;
195200
}
196201
197202
set $setterName($dartType value) {
@@ -214,7 +219,7 @@ set $setterName($dartType value) {
214219
case 'Object':
215220
if (valueType.isNullable) {
216221
buffer.writeln('''
217-
$dartType get $fieldName => json.optionalMap('$jsonKey');
222+
$dartType get $fieldName => _reader.optionalMap('$jsonKey');
218223
219224
set $setterName($dartType value) {
220225
json.setOrRemove('$jsonKey', value);
@@ -251,8 +256,16 @@ set $setterName($dartType value) {
251256
throw UnimplementedError('Expected an optional property.');
252257
}
253258
buffer.writeln('''
254-
$dartType get $fieldName =>
255-
json.optionalListParsed('$jsonKey', (e) => $typeName.fromJson(e as Map<String, Object?>));
259+
$dartType get $fieldName {
260+
var index = 0;
261+
return _reader.optionalListParsed(
262+
'$jsonKey',
263+
(e) => $typeName.fromJson(
264+
e as Map<String, Object?>,
265+
path: [...path, '$jsonKey', index++],
266+
),
267+
);
268+
}
256269
257270
set $setterName($dartType value) {
258271
if (value == null) {
@@ -273,7 +286,7 @@ set $setterName($dartType value) {
273286
final jsonRead = required ? 'stringList' : 'optionalStringList';
274287
final setter = setOrRemove(dartType, jsonKey);
275288
buffer.writeln('''
276-
$dartType get $fieldName => json.$jsonRead('$jsonKey');
289+
$dartType get $fieldName => _reader.$jsonRead('$jsonKey');
277290
278291
set $setterName($dartType value) {
279292
$setter
@@ -288,7 +301,7 @@ set $setterName($dartType value) {
288301
final jsonRead = required ? 'pathList' : 'optionalPathList';
289302
final setter = setOrRemove(dartType, jsonKey, '.toJson()');
290303
buffer.writeln('''
291-
$dartType get $fieldName => json.$jsonRead('$jsonKey');
304+
$dartType get $fieldName => _reader.$jsonRead('$jsonKey');
292305
293306
set $setterName($dartType value) {
294307
$setter
@@ -310,10 +323,10 @@ set $setterName($dartType value) {
310323
) {
311324
final fieldName = property.name;
312325
final required = property.isRequired;
313-
final jsonRead = required ? 'path' : 'optionalPath';
326+
final jsonRead = required ? r'path$' : 'optionalPath';
314327
final setter = setOrRemove(dartType, jsonKey, '.toFilePath()');
315328
buffer.writeln('''
316-
$dartType get $fieldName => json.$jsonRead('$jsonKey');
329+
$dartType get $fieldName => _reader.$jsonRead('$jsonKey');
317330
318331
set $setterName($dartType value) {
319332
$setter

pkgs/native_assets_cli/lib/src/code_assets/c_compiler_config.dart

-13
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,6 @@ final class CCompilerConfig {
3737
WindowsCCompilerConfig? windows,
3838
}) : _windows = windows;
3939

40-
/// Constructs a [CCompilerConfig] from the given [json].
41-
///
42-
/// The json is expected to be valid encoding obtained via
43-
/// [CCompilerConfig.toJson].
44-
factory CCompilerConfig.fromJson(Map<String, Object?> json) =>
45-
CCompilerConfigSyntax.fromSyntax(syntax.CCompilerConfig.fromJson(json));
46-
47-
/// The json representation of this [CCompilerConfig].
48-
///
49-
/// The returned json can be used in [CCompilerConfig.fromJson] to
50-
/// obtain a [CCompilerConfig] again.
51-
Map<String, Object?> toJson() => toSyntax().json;
52-
5340
@override
5441
bool operator ==(Object other) {
5542
if (other is! CCompilerConfig) {

pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart

+4-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ final class CodeAsset {
107107
factory CodeAsset.fromEncoded(EncodedAsset asset) {
108108
assert(asset.type == CodeAsset.type);
109109
final jsonMap = asset.encoding;
110-
final syntaxNode = syntax.NativeCodeAsset.fromJson(jsonMap);
110+
final syntaxNode = syntax.NativeCodeAsset.fromJson(
111+
jsonMap,
112+
path: asset.jsonPath ?? [],
113+
);
111114
return CodeAsset._(
112115
id: syntaxNode.id,
113116
os: OSSyntax.fromSyntax(syntaxNode.os),

pkgs/native_assets_cli/lib/src/code_assets/config.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import 'syntax.g.dart' as syntax;
1616
/// to code assets (only available if code assets are supported).
1717
extension CodeAssetHookConfig on HookConfig {
1818
/// Code asset specific configuration.
19-
CodeConfig get code => CodeConfig._fromJson(json);
19+
CodeConfig get code => CodeConfig._fromJson(json, path);
2020

2121
bool get buildCodeAssets => buildAssetTypes.contains(CodeAsset.type);
2222
}
@@ -41,8 +41,8 @@ extension CodeAssetLinkInput on LinkInputAssets {
4141
class CodeConfig {
4242
final syntax.CodeConfig _syntax;
4343

44-
CodeConfig._fromJson(Map<String, Object?> json)
45-
: _syntax = syntax.Config.fromJson(json).code!;
44+
CodeConfig._fromJson(Map<String, Object?> json, List<Object> path)
45+
: _syntax = syntax.Config.fromJson(json, path: path).code!;
4646

4747
/// The architecture the code code asset should be built for.
4848
///

0 commit comments

Comments
 (0)