Skip to content

Commit eaf24d6

Browse files
committed
Added information about which nested builder failed.
1 parent 9cc02cd commit eaf24d6

File tree

16 files changed

+558
-104
lines changed

16 files changed

+558
-104
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Changelog
22

3-
## 4.5.3
3+
## 4.6.0
44

5+
- Add custom `Error` classes: `BuiltValueNullFieldError`,
6+
`BuiltValueMissingGenericsError` and `BuiltValueNestedFieldError`. These
7+
provide clearer error messages on failure. In particular, errors in nested
8+
builders now report the enclosing class and field name, making them much
9+
more useful.
510
- Fix serialization when using polymorphism with StandardJsonPlugin.
611

712
## 4.5.2

benchmark/lib/node.g.dart

Lines changed: 18 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

built_value/lib/built_value.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,18 @@ class BuiltValueMissingGenericsError extends Error {
283283
'Tried to construct class "$type" with missing or dynamic '
284284
'type argument "$parameter". All type arguments must be specified.';
285285
}
286+
287+
/// [Error] indicating that a built_value `build` method failed because a
288+
/// nested field builder failed.
289+
class BuiltValueNestedFieldError extends Error {
290+
final String type;
291+
final String field;
292+
final String error;
293+
294+
BuiltValueNestedFieldError(this.type, this.field, this.error);
295+
296+
@override
297+
String toString() =>
298+
'Tried to build class "$type" but nested builder for field "$field" '
299+
'threw: $error';
300+
}

built_value_generator/lib/src/value_source_class.dart

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -572,21 +572,68 @@ abstract class ValueSourceClass
572572

573573
result.writeln('@override');
574574
result.writeln('$implName$_generics build() {');
575-
result.writeln('final _\$result = _\$v ?? ');
576-
result.writeln('new $implName$_generics._(');
577-
result.write(fields.map((field) {
575+
576+
// Construct a map from field to how it's built. If it's a normal field,
577+
// this is just the field name; if it's a nested builder, this is an
578+
// invocation of the nested builder taking into account nullability.
579+
final fieldBuilders = <String, String>{};
580+
fields.forEach((field) {
578581
final name = field.name;
579-
if (!field.isNestedBuilder) return '$name: $name';
580-
// If not nullable, go via the public accessor, which instantiates
581-
// if needed.
582-
if (!field.isNullable) return '$name: $name?.build()';
583-
// Otherwise access the private field: in super if there's a manually
584-
// maintaned builder, otherwise here.
585-
return hasBuilder
586-
? '$name: super.$name?.build()'
587-
: '$name: _$name?.build()';
588-
}).join(', '));
582+
if (!field.isNestedBuilder) {
583+
fieldBuilders[name] = name;
584+
} else if (!field.isNullable) {
585+
// If not nullable, go via the public accessor, which instantiates
586+
// if needed.
587+
fieldBuilders[name] = '$name.build()';
588+
} else if (hasBuilder) {
589+
// Otherwise access the private field: in super if there's a manually
590+
// maintained builder.
591+
fieldBuilders[name] = 'super.$name?.build()';
592+
} else {
593+
// Or, directly if there is no manually maintained builder.
594+
fieldBuilders[name] = '_$name?.build()';
595+
}
596+
});
597+
598+
// If there are nested builders then wrap the build in a try/catch so we
599+
// can add information should a nested builder fail.
600+
final needsTryCatchOnBuild =
601+
fieldBuilders.keys.any((field) => fieldBuilders[field] != field);
602+
603+
if (needsTryCatchOnBuild) {
604+
result.writeln('$implName$_generics _\$result;');
605+
result.writeln('try {');
606+
} else {
607+
result.write('final ');
608+
}
609+
result.writeln('_\$result = _\$v ?? ');
610+
result.writeln('new $implName$_generics._(');
611+
result.write(fieldBuilders.keys
612+
.map((field) => '$field: ${fieldBuilders[field]}')
613+
.join(','));
589614
result.writeln(');');
615+
616+
if (needsTryCatchOnBuild) {
617+
// Handle errors by re-running all nested builders; if there's an error
618+
// in a nested builder then throw with more information. Otherwise,
619+
// just rethrow.
620+
result.writeln('} catch (_) {');
621+
result.writeln('String _\$failedField;');
622+
result.writeln('try {');
623+
result.write(fieldBuilders.keys.map((field) {
624+
final fieldBuilder = fieldBuilders[field];
625+
if (fieldBuilder == field) return '';
626+
return "_\$failedField = '$field'; $fieldBuilder;";
627+
}).join('\n'));
628+
629+
result.writeln('} catch (e) {');
630+
result.writeln('throw new BuiltValueNestedFieldError('
631+
"'$name', _\$failedField, e.toString());");
632+
result.writeln('}');
633+
result.writeln('rethrow;');
634+
result.writeln('}');
635+
}
636+
590637
// Set _$v to the built value, so it will be lazily copied if needed.
591638
result.writeln('replace(_\$result);');
592639
result.writeln('return _\$result;');

built_value_test/test/values.g.dart

Lines changed: 30 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chat_example/lib/data_model/data_model.g.dart

Lines changed: 56 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

end_to_end_test/lib/collections.g.dart

Lines changed: 43 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)