Skip to content

Commit ee47c28

Browse files
authored
[json_syntax_generator] Support String regex patterns (#2441)
1 parent 3ff7b44 commit ee47c28

File tree

17 files changed

+527
-1180
lines changed

17 files changed

+527
-1180
lines changed

pkgs/code_assets/lib/src/code_assets/syntax.g.dart

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,28 @@ class JsonReader {
11071107
return result;
11081108
}
11091109

1110+
String string(String key, RegExp? pattern) {
1111+
final value = get<String>(key);
1112+
if (pattern != null && !pattern.hasMatch(value)) {
1113+
throwFormatException(value, String, [key], pattern: pattern);
1114+
}
1115+
return value;
1116+
}
1117+
1118+
List<String> validateString(String key, RegExp? pattern) {
1119+
final errors = validate<String>(key);
1120+
if (errors.isNotEmpty) {
1121+
return errors;
1122+
}
1123+
final value = get<String>(key);
1124+
if (pattern != null && !pattern.hasMatch(value)) {
1125+
return [
1126+
errorString(value, String, [key], pattern: pattern),
1127+
];
1128+
}
1129+
return [];
1130+
}
1131+
11101132
List<String>? optionalStringList(String key) => optionalList<String>(key);
11111133

11121134
List<String> validateOptionalStringList(String key) =>
@@ -1152,23 +1174,28 @@ class JsonReader {
11521174
Never throwFormatException(
11531175
Object? value,
11541176
Type expectedType,
1155-
List<Object> pathExtension,
1156-
) {
1157-
throw FormatException(errorString(value, expectedType, pathExtension));
1177+
List<Object> pathExtension, {
1178+
RegExp? pattern,
1179+
}) {
1180+
throw FormatException(
1181+
errorString(value, expectedType, pathExtension, pattern: pattern),
1182+
);
11581183
}
11591184

11601185
String errorString(
11611186
Object? value,
11621187
Type expectedType,
1163-
List<Object> pathExtension,
1164-
) {
1188+
List<Object> pathExtension, {
1189+
RegExp? pattern,
1190+
}) {
11651191
final pathString = _jsonPathToString(pathExtension);
11661192
if (value == null) {
11671193
return "No value was provided for '$pathString'."
11681194
' Expected a $expectedType.';
11691195
}
1196+
final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}';
11701197
return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'."
1171-
' Expected a $expectedType.';
1198+
' Expected a $expectedType$satisfying.';
11721199
}
11731200

11741201
/// Traverses a JSON path, returns `null` if the path cannot be traversed.

pkgs/data_assets/lib/src/data_assets/syntax.g.dart

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,28 @@ class JsonReader {
296296
return result;
297297
}
298298

299+
String string(String key, RegExp? pattern) {
300+
final value = get<String>(key);
301+
if (pattern != null && !pattern.hasMatch(value)) {
302+
throwFormatException(value, String, [key], pattern: pattern);
303+
}
304+
return value;
305+
}
306+
307+
List<String> validateString(String key, RegExp? pattern) {
308+
final errors = validate<String>(key);
309+
if (errors.isNotEmpty) {
310+
return errors;
311+
}
312+
final value = get<String>(key);
313+
if (pattern != null && !pattern.hasMatch(value)) {
314+
return [
315+
errorString(value, String, [key], pattern: pattern),
316+
];
317+
}
318+
return [];
319+
}
320+
299321
List<String>? optionalStringList(String key) => optionalList<String>(key);
300322

301323
List<String> validateOptionalStringList(String key) =>
@@ -341,23 +363,28 @@ class JsonReader {
341363
Never throwFormatException(
342364
Object? value,
343365
Type expectedType,
344-
List<Object> pathExtension,
345-
) {
346-
throw FormatException(errorString(value, expectedType, pathExtension));
366+
List<Object> pathExtension, {
367+
RegExp? pattern,
368+
}) {
369+
throw FormatException(
370+
errorString(value, expectedType, pathExtension, pattern: pattern),
371+
);
347372
}
348373

349374
String errorString(
350375
Object? value,
351376
Type expectedType,
352-
List<Object> pathExtension,
353-
) {
377+
List<Object> pathExtension, {
378+
RegExp? pattern,
379+
}) {
354380
final pathString = _jsonPathToString(pathExtension);
355381
if (value == null) {
356382
return "No value was provided for '$pathString'."
357383
' Expected a $expectedType.';
358384
}
385+
final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}';
359386
return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'."
360-
' Expected a $expectedType.';
387+
' Expected a $expectedType$satisfying.';
361388
}
362389

363390
/// Traverses a JSON path, returns `null` if the path cannot be traversed.

pkgs/hooks/lib/src/hooks/syntax.g.dart

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,28 @@ class JsonReader {
11571157
return result;
11581158
}
11591159

1160+
String string(String key, RegExp? pattern) {
1161+
final value = get<String>(key);
1162+
if (pattern != null && !pattern.hasMatch(value)) {
1163+
throwFormatException(value, String, [key], pattern: pattern);
1164+
}
1165+
return value;
1166+
}
1167+
1168+
List<String> validateString(String key, RegExp? pattern) {
1169+
final errors = validate<String>(key);
1170+
if (errors.isNotEmpty) {
1171+
return errors;
1172+
}
1173+
final value = get<String>(key);
1174+
if (pattern != null && !pattern.hasMatch(value)) {
1175+
return [
1176+
errorString(value, String, [key], pattern: pattern),
1177+
];
1178+
}
1179+
return [];
1180+
}
1181+
11601182
List<String>? optionalStringList(String key) => optionalList<String>(key);
11611183

11621184
List<String> validateOptionalStringList(String key) =>
@@ -1202,23 +1224,28 @@ class JsonReader {
12021224
Never throwFormatException(
12031225
Object? value,
12041226
Type expectedType,
1205-
List<Object> pathExtension,
1206-
) {
1207-
throw FormatException(errorString(value, expectedType, pathExtension));
1227+
List<Object> pathExtension, {
1228+
RegExp? pattern,
1229+
}) {
1230+
throw FormatException(
1231+
errorString(value, expectedType, pathExtension, pattern: pattern),
1232+
);
12081233
}
12091234

12101235
String errorString(
12111236
Object? value,
12121237
Type expectedType,
1213-
List<Object> pathExtension,
1214-
) {
1238+
List<Object> pathExtension, {
1239+
RegExp? pattern,
1240+
}) {
12151241
final pathString = _jsonPathToString(pathExtension);
12161242
if (value == null) {
12171243
return "No value was provided for '$pathString'."
12181244
' Expected a $expectedType.';
12191245
}
1246+
final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}';
12201247
return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'."
1221-
' Expected a $expectedType.';
1248+
' Expected a $expectedType$satisfying.';
12221249
}
12231250

12241251
/// Traverses a JSON path, returns `null` if the path cannot be traversed.

pkgs/hooks/tool/normalize.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ void main(List<String> arguments) {
2828
Directory.fromUri(packageUri),
2929
Directory.fromUri(packageUri.resolve('../code_assets/')),
3030
Directory.fromUri(packageUri.resolve('../data_assets/')),
31+
Directory.fromUri(packageUri.resolve('../pub_formats/')),
3132
];
3233
for (final directory in directories) {
3334
final result = processDirectory(directory);

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,28 @@ class JsonReader {
154154
return result;
155155
}
156156
157+
String string(String key, RegExp? pattern) {
158+
final value = get<String>(key);
159+
if (pattern != null && !pattern.hasMatch(value)) {
160+
throwFormatException(value, String, [key], pattern: pattern);
161+
}
162+
return value;
163+
}
164+
165+
List<String> validateString(String key, RegExp? pattern) {
166+
final errors = validate<String>(key);
167+
if (errors.isNotEmpty) {
168+
return errors;
169+
}
170+
final value = get<String>(key);
171+
if (pattern != null && !pattern.hasMatch(value)) {
172+
return [
173+
errorString(value, String, [key], pattern: pattern),
174+
];
175+
}
176+
return [];
177+
}
178+
157179
List<String>? optionalStringList(String key) => optionalList<String>(key);
158180
159181
List<String> validateOptionalStringList(String key) =>
@@ -199,23 +221,28 @@ class JsonReader {
199221
Never throwFormatException(
200222
Object? value,
201223
Type expectedType,
202-
List<Object> pathExtension,
203-
) {
204-
throw FormatException(errorString(value, expectedType, pathExtension));
224+
List<Object> pathExtension, {
225+
RegExp? pattern,
226+
}) {
227+
throw FormatException(
228+
errorString(value, expectedType, pathExtension, pattern: pattern),
229+
);
205230
}
206231
207232
String errorString(
208233
Object? value,
209234
Type expectedType,
210-
List<Object> pathExtension,
211-
) {
235+
List<Object> pathExtension, {
236+
RegExp? pattern,
237+
}) {
212238
final pathString = _jsonPathToString(pathExtension);
213239
if (value == null) {
214240
return "No value was provided for '$pathString'."
215241
' Expected a $expectedType.';
216242
}
243+
final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}';
217244
return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'."
218-
' Expected a $expectedType.';
245+
' Expected a $expectedType$satisfying.';
219246
}
220247
221248
/// Traverses a JSON path, returns `null` if the path cannot be traversed.

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,33 @@ List<String> $validateName() {
162162
final fieldName = property.name;
163163
final validateName = property.validateName;
164164

165+
if (dartType is StringDartType && dartType.pattern != null) {
166+
if (dartType.isNullable) {
167+
throw UnimplementedError();
168+
}
169+
final pattern = dartType.pattern!;
170+
buffer.writeln('''
171+
static final _${fieldName}Pattern = RegExp(r'${pattern.pattern}');
172+
173+
$dartType get $fieldName => _reader.string('$jsonKey', _${fieldName}Pattern);
174+
175+
set $setterName($dartType value) {
176+
if (!_${fieldName}Pattern.hasMatch(value)) {
177+
throw ArgumentError.value(
178+
value,
179+
'value',
180+
'Value does not satisify pattern: \${_${fieldName}Pattern.pattern}.',
181+
);
182+
}
183+
json.setOrRemove('$jsonKey', value);
184+
$sortOnKey
185+
}
186+
187+
List<String> $validateName() => _reader.validateString('$jsonKey', _${fieldName}Pattern);
188+
''');
189+
return;
190+
}
191+
165192
buffer.writeln('''
166193
$dartType get $fieldName => _reader.get<$dartType>('$jsonKey');
167194

pkgs/json_syntax_generator/lib/src/model/dart_type.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ sealed class DartType {
2424
int get hashCode => isNullable.hashCode;
2525

2626
String toNonNullableString();
27+
28+
String toSerializedString() => toString();
2729
}
2830

2931
/// A simple Dart type.
@@ -46,13 +48,24 @@ sealed class SimpleDartType extends DartType {
4648
}
4749

4850
class StringDartType extends SimpleDartType {
49-
const StringDartType({required super.isNullable}) : super(typeName: 'String');
51+
final RegExp? pattern;
52+
53+
const StringDartType({required super.isNullable, this.pattern})
54+
: super(typeName: 'String');
5055

5156
@override
5257
bool operator ==(Object other) => super == other && other is StringDartType;
5358

5459
@override
5560
int get hashCode => Object.hash(super.hashCode, 'String');
61+
62+
@override
63+
String toSerializedString() {
64+
if (pattern != null) {
65+
return '$typeName(pattern: ${pattern!.pattern})';
66+
}
67+
return typeName;
68+
}
5669
}
5770

5871
class IntDartType extends SimpleDartType {

pkgs/json_syntax_generator/lib/src/model/property_info.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class PropertyInfo {
4949
PropertyInfo(
5050
name: $name,
5151
jsonKey: $jsonKey,
52-
type: $type,
52+
type: ${type.toSerializedString()},
5353
isOverride: $isOverride,
5454
setterPrivate: $setterPrivate,
5555
)''';

pkgs/json_syntax_generator/lib/src/parser/schema_analyzer.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,11 @@ class SchemaAnalyzer {
320320
final classInfo = _classes[schemas.className]!;
321321
dartType = ClassDartType(classInfo: classInfo, isNullable: !required);
322322
} else {
323-
dartType = StringDartType(isNullable: !required);
323+
if (schemas.patterns.length > 1) {
324+
throw UnsupportedError('Only one pattern is supported.');
325+
}
326+
final pattern = schemas.patterns.firstOrNull;
327+
dartType = StringDartType(isNullable: !required, pattern: pattern);
324328
}
325329
case SchemaType.object:
326330
final additionalPropertiesSchema = schemas.additionalPropertiesSchemas;
@@ -690,7 +694,20 @@ extension on JsonSchemas {
690694
bool get generateClass => type == SchemaType.object && !generateMapOf;
691695

692696
/// Generate getters/setters as `Uri`.
693-
bool get generateUri => type == SchemaType.string && patterns.isNotEmpty;
697+
bool get generateUri {
698+
if (!stringWithPattern) return false;
699+
if (patterns.length != 1) return false;
700+
final pattern = patterns.single;
701+
if (pattern.pattern == '^(\\/|[A-Za-z]:)' ||
702+
pattern.pattern == '^([A-Za-z])') {
703+
// Patterns for a file path.
704+
return true;
705+
}
706+
return false;
707+
}
708+
709+
bool get stringWithPattern =>
710+
type == SchemaType.string && patterns.isNotEmpty;
694711

695712
static String? _pathToClassName(String path) {
696713
if (path.contains('#/definitions/')) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The syntax generator is tested by running the various generators on this
2+
repository.

0 commit comments

Comments
 (0)