• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / ffigen / 5305585135

18 Jun 2023 08:33PM UTC coverage: 85.041% (-6.8%) from 91.879%
5305585135

Pull #586

github

web-flow
Merge 27e2ea8df into 9030eb512
Pull Request #586: Add ffigen schema and refer them in example config yaml using modeline

603 of 603 new or added lines in 3 files covered. (100.0%)

3462 of 4071 relevant lines covered (85.04%)

25.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.61
/lib/src/config_provider/schema.dart
1
import 'dart:convert';
2

3
import 'package:ffigen/src/config_provider/config_types.dart';
4
import 'package:logging/logging.dart';
5
import 'package:yaml/yaml.dart';
6

7
final _logger = Logger('ffigen.config_provider.config');
12✔
8

9
/// A container object for a Schema Object.
10
class SchemaNode<E> {
11
  /// The path to this node.
12
  ///
13
  /// E.g - ["path", "to", "arr", "[1]", "item"]
14
  final List<String> path;
15

16
  /// Get a string representation for path.
17
  ///
18
  /// E.g - "path -> to -> arr -> [1] -> item"
19
  String get pathString => path.join(" -> ");
6✔
20

21
  /// Contains the underlying node value after all transformations and
22
  /// default values have been applied.
23
  final E value;
24

25
  /// Contains the raw underlying node value. Would be null for fields populated
26
  /// but default values
27
  final dynamic rawValue;
28

29
  SchemaNode({
50✔
30
    required this.path,
31
    required this.value,
32
    dynamic rawValue,
33
    bool nullRawValue = false,
34
  }) : rawValue = nullRawValue ? null : (rawValue ?? value);
35

36
  /// Copy object with a different value.
37
  SchemaNode<T> withValue<T>(T value, dynamic rawValue) {
50✔
38
    return SchemaNode<T>(
50✔
39
      path: path,
50✔
40
      value: value,
41
      rawValue: rawValue,
42
      nullRawValue: rawValue == null,
43
    );
44
  }
45

46
  /// Transforms this SchemaNode with a nullable [transform] or return itself
47
  /// and calls the [result] callback
48
  SchemaNode transformOrThis(
50✔
49
    dynamic Function(SchemaNode<E> value)? transform,
50
    void Function(SchemaNode node)? resultCallback,
51
  ) {
52
    SchemaNode returnValue = this;
53
    if (transform != null) {
54
      returnValue = this.withValue(transform.call(this), rawValue);
150✔
55
    }
56
    resultCallback?.call(returnValue);
50✔
57
    return returnValue;
58
  }
59

60
  /// Returns true if [value] is of Type [T].
61
  bool checkType<T>({bool log = true}) {
50✔
62
    if (value is! T) {
100✔
63
      if (log) {
64
        _logger.severe(
×
65
            "Expected value of key '$pathString' to be of type '$T' (Got ${value.runtimeType}).");
×
66
      }
67
      return false;
68
    }
69
    return true;
70
  }
71
}
72

73
class SchemaExtractionError extends Error {
74
  final SchemaNode? item;
75
  final String message;
76
  SchemaExtractionError(this.item, [this.message = "Invalid Schema"]);
×
77

78
  @override
×
79
  String toString() {
80
    if (item != null) {
×
81
      return "$runtimeType: $message @ ${item!.pathString}";
×
82
    }
83
    return "$runtimeType: $message";
×
84
  }
85
}
86

87
/// Base class for all Schemas to extend.
88
abstract class Schema<E> {
89
  /// Used to generate and refer the reference definition generated in json
90
  /// schema. Must be unique for a nested Schema.
91
  String? schemaDefName;
92

93
  /// Used to generate the description field in json schema.
94
  String? schemaDescription;
95

96
  /// Used to transform the payload to another type before passing to parent
97
  /// nodes and [result].
98
  dynamic Function(SchemaNode<E> node)? transform;
99

100
  /// Passed to parent nodes and result (only if required by parent)
101
  ///
102
  /// SchemaNode<void> is used since value should not be accessed here.
103
  dynamic Function(SchemaNode<void> node)? defaultValue;
104

105
  /// Called when final result is prepared via [extractNode] or
106
  /// [getDefaultValue].
107
  void Function(SchemaNode<dynamic> node)? result;
108
  Schema({
51✔
109
    /// Used
110
    this.schemaDefName,
111
    this.schemaDescription,
112
    this.transform,
113
    this.defaultValue,
114
    this.result,
115
  });
116

117
  bool validateNode(SchemaNode o, {bool log = true});
118

119
  SchemaNode extractNode(SchemaNode o);
120

121
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs);
122

123
  Map<String, dynamic> getJsonRefOrSchemaNode(Map<String, dynamic> defs) {
1✔
124
    if (schemaDefName == null) {
1✔
125
      return generateJsonSchemaNode(defs);
1✔
126
    }
127
    defs.putIfAbsent(schemaDefName!, () => generateJsonSchemaNode(defs));
4✔
128
    return {r"$ref": "#/\$defs/$schemaDefName"};
3✔
129
  }
130

131
  Map<String, dynamic> generateJsonSchema(String schemaId) {
1✔
132
    final defs = <String, dynamic>{};
1✔
133
    final schemaMap = generateJsonSchemaNode(defs);
1✔
134
    return {
1✔
135
      r"$id": schemaId,
1✔
136
      r"$schema": "https://json-schema.org/draft/2020-12/schema",
1✔
137
      ...schemaMap,
1✔
138
      r"$defs": defs,
1✔
139
    };
140
  }
141

142
  /// Returns default value or null for a node. Calls [result] if value is
143
  /// not null.
144
  dynamic getDefaultValue(SchemaNode o) {
50✔
145
    final v = defaultValue?.call(o.withValue(null, null));
150✔
146
    if (v != null) result?.call(o.withValue(v, null));
150✔
147
    return v;
148
  }
149

150
  /// Run validation on an object [value].
151
  bool validate(dynamic value) {
50✔
152
    return validateNode(SchemaNode(path: [], value: value));
150✔
153
  }
154

155
  /// Extract SchemaNode from [value]. This will call the [transform] for all
156
  /// underlying Schemas if valid.
157
  /// Should ideally only be called if [validate] returns True. Throws
158
  /// [SchemaExtractionError] if any validation fails.
159
  SchemaNode extract(dynamic value) {
50✔
160
    return extractNode(SchemaNode(path: [], value: value));
150✔
161
  }
162
}
163

164
/// Schema for a Map which has a fixed set of known keys.
165
class FixedMapSchema<CE> extends Schema<Map<dynamic, CE>> {
166
  final Map<dynamic, Schema> keys;
167
  final List<String> requiredKeys;
168

169
  FixedMapSchema({
51✔
170
    required this.keys,
171
    this.requiredKeys = const [],
172
    super.schemaDefName,
173
    super.schemaDescription,
174
    super.transform,
175
    super.defaultValue,
176
    super.result,
177
  }) {
178
    final unknownKeys =
179
        requiredKeys.where((element) => !keys.containsKey(element)).toList();
306✔
180
    if (unknownKeys.isNotEmpty) {
51✔
181
      throw ArgumentError(
×
182
          "Invalid requiredKeys: $unknownKeys, requiredKeys must be a subset of keys");
×
183
    }
184

185
    // Get default values of underlying keys if [defaultValue] is not specified
186
    // for this.
187
    super.defaultValue ??= (SchemaNode o) {
101✔
188
      final result = <dynamic, CE>{};
50✔
189
      for (final MapEntry(key: key, value: value) in keys.entries) {
250✔
190
        final defaultValue = value.getDefaultValue(
50✔
191
            SchemaNode(path: [...o.path, key.toString()], value: value));
200✔
192
        if (defaultValue != null) {
193
          result[key] = defaultValue as CE;
50✔
194
        }
195
      }
196
      return result.isEmpty
50✔
197
          ? null
198
          : o.withValue(result, null).transformOrThis(transform, null).value;
200✔
199
    };
200
  }
201

202
  @override
50✔
203
  bool validateNode(SchemaNode o, {bool log = true}) {
204
    if (!o.checkType<YamlMap>(log: log)) {
50✔
205
      return false;
206
    }
207

208
    var result = true;
209
    final inputMap = (o.value as YamlMap);
50✔
210

211
    for (final requiredKey in requiredKeys) {
100✔
212
      if (!inputMap.containsKey(requiredKey)) {
50✔
213
        _logger.severe(
×
214
            "Key '${[...o.path, requiredKey].join(' -> ')}' is required.");
×
215
        result = false;
216
      }
217
    }
218

219
    for (final MapEntry(key: key, value: value) in keys.entries) {
250✔
220
      final path = [...o.path, key.toString()];
150✔
221
      if (!inputMap.containsKey(key)) {
50✔
222
        continue;
223
      }
224
      final schemaNode = SchemaNode(path: path, value: inputMap[key]);
100✔
225
      if (!value.validateNode(schemaNode, log: log)) {
50✔
226
        result = false;
227
        continue;
228
      }
229
    }
230

231
    for (final key in inputMap.keys) {
100✔
232
      if (!keys.containsKey(key)) {
100✔
233
        if (log) {
234
          _logger.severe("Unknown key - '${[...o.path, key].join(' -> ')}'.");
21✔
235
        }
236
      }
237
    }
238

239
    return result;
240
  }
241

242
  @override
50✔
243
  SchemaNode extractNode(SchemaNode o) {
244
    if (!o.checkType<YamlMap>(log: false)) {
50✔
245
      throw SchemaExtractionError(o);
×
246
    }
247

248
    final inputMap = (o.value as YamlMap);
50✔
249
    final childExtracts = <dynamic, CE>{};
50✔
250

251
    for (final requiredKey in requiredKeys) {
100✔
252
      if (!inputMap.containsKey(requiredKey)) {
50✔
253
        throw SchemaExtractionError(
×
254
            null, "Invalid schema, missing required key - $requiredKey.");
×
255
      }
256
    }
257

258
    for (final MapEntry(key: key, value: value) in keys.entries) {
250✔
259
      final path = [...o.path, key.toString()];
150✔
260
      if (!inputMap.containsKey(key)) {
50✔
261
        // No value specified, fill in with default value instead.
262
        final defaultValue =
263
            value.getDefaultValue(SchemaNode(path: path, value: null));
100✔
264
        if (defaultValue != null) {
265
          childExtracts[key] = defaultValue as CE;
50✔
266
        }
267
        continue;
268
      }
269
      final schemaNode = SchemaNode(path: path, value: inputMap[key]);
100✔
270
      if (!value.validateNode(schemaNode, log: false)) {
50✔
271
        throw SchemaExtractionError(schemaNode);
×
272
      }
273
      childExtracts[key] = value.extractNode(schemaNode).value as CE;
150✔
274
    }
275
    return o
276
        .withValue(childExtracts, o.rawValue)
100✔
277
        .transformOrThis(transform, result);
150✔
278
  }
279

280
  @override
1✔
281
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
282
    return {
1✔
283
      "type": "object",
1✔
284
      if (schemaDescription != null) "description": schemaDescription!,
1✔
285
      if (keys.isNotEmpty)
2✔
286
        "properties": {
2✔
287
          for (final kv in keys.entries)
2✔
288
            kv.key: kv.value.getJsonRefOrSchemaNode(defs)
4✔
289
        },
290
      if (requiredKeys.isNotEmpty) "required": requiredKeys,
4✔
291
    };
292
  }
293
}
294

295
/// Schema for a Map that can have any number of keys.
296
class DynamicMapSchema<CE> extends Schema<Map<dynamic, CE>> {
297
  /// [keyRegexp] will convert it's input to a String before matching.
298
  final List<({String keyRegexp, Schema valueSchema})> keyValueSchemas;
299

300
  DynamicMapSchema({
51✔
301
    required this.keyValueSchemas,
302
    super.schemaDefName,
303
    super.schemaDescription,
304
    super.transform,
305
    super.defaultValue,
306
    super.result,
307
  });
308

309
  @override
9✔
310
  bool validateNode(SchemaNode o, {bool log = true}) {
311
    if (!o.checkType<YamlMap>(log: log)) {
9✔
312
      return false;
313
    }
314

315
    var result = true;
316
    final inputMap = (o.value as YamlMap);
9✔
317

318
    for (final MapEntry(key: key, value: value) in inputMap.entries) {
36✔
319
      final schemaNode =
320
          SchemaNode(path: [...o.path, key.toString()], value: value);
36✔
321
      var keyValueMatch = false;
322

323
      /// Running first time with no logs.
324
      for (final (keyRegexp: keyRegexp, valueSchema: valueSchema)
325
          in keyValueSchemas) {
18✔
326
        if (RegExp(keyRegexp, dotAll: true).hasMatch(key.toString()) &&
27✔
327
            valueSchema.validateNode(schemaNode, log: false)) {
9✔
328
          keyValueMatch = true;
329
          break;
330
        }
331
      }
332
      if (!keyValueMatch) {
333
        result = false;
334
        // No schema matched, running again to print logs this time.
335
        if (log) {
336
          _logger.severe(
2✔
337
              "'${schemaNode.pathString}' must match atleast one of the allowed key regex and schema.");
2✔
338
          for (final (keyRegexp: keyRegexp, valueSchema: valueSchema)
339
              in keyValueSchemas) {
2✔
340
            if (!RegExp(keyRegexp, dotAll: true).hasMatch(key.toString())) {
3✔
341
              _logger.severe(
×
342
                  "'${schemaNode.pathString}' does not match regex - '$keyRegexp' (Input - $key)");
×
343
              continue;
344
            }
345
            if (valueSchema.validateNode(schemaNode, log: log)) {
1✔
346
              continue;
347
            }
348
          }
349
        }
350
      }
351
    }
352

353
    return result;
354
  }
355

356
  @override
9✔
357
  SchemaNode extractNode(SchemaNode o) {
358
    if (!o.checkType<YamlMap>(log: false)) {
9✔
359
      throw SchemaExtractionError(o);
×
360
    }
361

362
    final inputMap = (o.value as YamlMap);
9✔
363
    final childExtracts = <dynamic, CE>{};
9✔
364
    for (final MapEntry(key: key, value: value) in inputMap.entries) {
36✔
365
      final schemaNode =
366
          SchemaNode(path: [...o.path, key.toString()], value: value);
36✔
367
      var keyValueMatch = false;
368
      for (final (keyRegexp: keyRegexp, valueSchema: valueSchema)
369
          in keyValueSchemas) {
18✔
370
        if (RegExp(keyRegexp, dotAll: true).hasMatch(key.toString()) &&
27✔
371
            valueSchema.validateNode(schemaNode, log: false)) {
9✔
372
          childExtracts[key] = valueSchema.extractNode(schemaNode).value as CE;
27✔
373
          keyValueMatch = true;
374
          break;
375
        }
376
      }
377
      if (!keyValueMatch) {
378
        throw SchemaExtractionError(schemaNode);
×
379
      }
380
    }
381

382
    return o
383
        .withValue(childExtracts, o.rawValue)
18✔
384
        .transformOrThis(transform, result);
27✔
385
  }
386

387
  @override
1✔
388
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
389
    return {
1✔
390
      "type": "object",
1✔
391
      if (schemaDescription != null) "description": schemaDescription!,
1✔
392
      if (keyValueSchemas.isNotEmpty)
2✔
393
        "patternProperties": {
2✔
394
          for (final (keyRegexp: keyRegexp, valueSchema: valueSchema)
395
              in keyValueSchemas)
1✔
396
            keyRegexp: valueSchema.getJsonRefOrSchemaNode(defs)
2✔
397
        }
398
    };
399
  }
400
}
401

402
/// Schema for a List.
403
class ListSchema<CE> extends Schema<List<CE>> {
404
  final Schema childSchema;
405

406
  ListSchema({
51✔
407
    required this.childSchema,
408
    super.schemaDefName,
409
    super.schemaDescription,
410
    super.transform,
411
    super.defaultValue,
412
    super.result,
413
  });
414

415
  @override
50✔
416
  bool validateNode(SchemaNode o, {bool log = true}) {
417
    if (!o.checkType<YamlList>(log: log)) {
50✔
418
      return false;
419
    }
420
    final inputList = (o.value as YamlList).cast<dynamic>();
100✔
421
    var result = true;
422
    for (final (i, input) in inputList.indexed) {
100✔
423
      final schemaNode = SchemaNode(path: [...o.path, "[$i]"], value: input);
200✔
424
      if (!childSchema.validateNode(schemaNode, log: log)) {
100✔
425
        result = false;
426
        continue;
427
      }
428
    }
429

430
    return result;
431
  }
432

433
  @override
50✔
434
  SchemaNode extractNode(SchemaNode o) {
435
    if (!o.checkType<YamlList>(log: false)) {
50✔
436
      throw SchemaExtractionError(o);
×
437
    }
438
    final inputList = (o.value as YamlList).cast<dynamic>();
100✔
439
    final childExtracts = <CE>[];
50✔
440
    for (final (i, input) in inputList.indexed) {
100✔
441
      final schemaNode =
442
          SchemaNode(path: [...o.path, i.toString()], value: input);
200✔
443
      if (!childSchema.validateNode(schemaNode, log: false)) {
100✔
444
        throw SchemaExtractionError(schemaNode);
×
445
      }
446
      childExtracts.add(childSchema.extractNode(schemaNode).value as CE);
200✔
447
    }
448
    return o
449
        .withValue(childExtracts, o.rawValue)
100✔
450
        .transformOrThis(transform, result);
150✔
451
  }
452

453
  @override
1✔
454
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
455
    return {
1✔
456
      "type": "array",
1✔
457
      if (schemaDescription != null) "description": schemaDescription!,
1✔
458
      "items": childSchema.getJsonRefOrSchemaNode(defs),
3✔
459
    };
460
  }
461
}
462

463
/// Schema for a String.
464
class StringSchema extends Schema<String> {
465
  StringSchema({
51✔
466
    super.schemaDefName,
467
    super.schemaDescription,
468
    super.transform,
469
    super.defaultValue,
470
    super.result,
471
  });
472

473
  @override
50✔
474
  bool validateNode(SchemaNode o, {bool log = true}) {
475
    if (!o.checkType<String>(log: log)) {
50✔
476
      return false;
477
    }
478
    return true;
479
  }
480

481
  @override
50✔
482
  SchemaNode extractNode(SchemaNode o) {
483
    if (!o.checkType<String>(log: false)) {
50✔
484
      throw SchemaExtractionError(o);
×
485
    }
486
    return o
487
        .withValue(o.value as String, o.rawValue)
150✔
488
        .transformOrThis(transform, result);
150✔
489
  }
490

491
  @override
1✔
492
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
493
    return {
1✔
494
      "type": "string",
1✔
495
      if (schemaDescription != null) "description": schemaDescription!,
1✔
496
    };
497
  }
498
}
499

500
/// Schema for an Int.
501
class IntSchema extends Schema<int> {
502
  IntSchema({
×
503
    super.schemaDefName,
504
    super.schemaDescription,
505
    super.transform,
506
    super.defaultValue,
507
    super.result,
508
  });
509

510
  @override
×
511
  bool validateNode(SchemaNode o, {bool log = true}) {
512
    if (!o.checkType<int>(log: log)) {
×
513
      return false;
514
    }
515
    return true;
516
  }
517

518
  @override
×
519
  SchemaNode extractNode(SchemaNode o) {
520
    if (!o.checkType<int>(log: false)) {
×
521
      throw SchemaExtractionError(o);
×
522
    }
523
    return o
524
        .withValue(o.value as int, o.rawValue)
×
525
        .transformOrThis(transform, result);
×
526
  }
527

528
  @override
×
529
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
530
    return {
×
531
      "type": "integer",
×
532
      if (schemaDescription != null) "description": schemaDescription!,
×
533
    };
534
  }
535
}
536

537
/// Schema for an object where only specific values are allowed.
538
class EnumSchema<CE> extends Schema<CE> {
539
  Set<CE> allowedValues;
540
  EnumSchema({
51✔
541
    required this.allowedValues,
542
    super.schemaDefName,
543
    super.schemaDescription,
544
    super.transform,
545
    super.defaultValue,
546
    super.result,
547
  });
548

549
  @override
23✔
550
  bool validateNode(SchemaNode o, {bool log = true}) {
551
    if (!allowedValues.contains(o.value)) {
69✔
552
      if (log) {
553
        _logger.severe(
2✔
554
            "'${o.pathString}' must be one of the following - $allowedValues (Got ${o.value})");
4✔
555
      }
556
      return false;
557
    }
558
    return true;
559
  }
560

561
  @override
23✔
562
  SchemaNode extractNode(SchemaNode o) {
563
    if (!allowedValues.contains(o.value)) {
69✔
564
      throw SchemaExtractionError(o);
×
565
    }
566
    return o
567
        .withValue(o.value as CE, o.rawValue)
69✔
568
        .transformOrThis(transform, result);
69✔
569
  }
570

571
  @override
1✔
572
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
573
    return {
1✔
574
      "enum": allowedValues.toList(),
3✔
575
      if (schemaDescription != null) "description": schemaDescription!,
1✔
576
    };
577
  }
578
}
579

580
/// Schema for a bool.
581
class BoolSchema extends Schema<bool> {
582
  BoolSchema({
51✔
583
    super.schemaDefName,
584
    super.schemaDescription,
585
    super.transform,
586
    super.defaultValue,
587
    super.result,
588
  });
589

590
  @override
23✔
591
  bool validateNode(SchemaNode o, {bool log = true}) {
592
    if (!o.checkType<bool>(log: log)) {
23✔
593
      return false;
594
    }
595
    return true;
596
  }
597

598
  @override
20✔
599
  SchemaNode extractNode(SchemaNode o) {
600
    if (!o.checkType<bool>(log: false)) {
20✔
601
      throw SchemaExtractionError(o);
×
602
    }
603
    return o
604
        .withValue(o.value as bool, o.rawValue)
60✔
605
        .transformOrThis(transform, result);
60✔
606
  }
607

608
  @override
1✔
609
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
610
    return {
1✔
611
      "type": "boolean",
1✔
612
      if (schemaDescription != null) "description": schemaDescription!,
1✔
613
    };
614
  }
615
}
616

617
/// Schema which checks if atleast one of the underlying Schema matches.
618
class OneOfSchema<E> extends Schema<E> {
619
  final List<Schema> childSchemas;
620

621
  OneOfSchema({
51✔
622
    required this.childSchemas,
623
    super.schemaDefName,
624
    super.schemaDescription,
625
    super.transform,
626
    super.defaultValue,
627
    super.result,
628
  });
629

630
  @override
50✔
631
  bool validateNode(SchemaNode o, {bool log = true}) {
632
    // Running first time with no logs.
633
    for (final schema in childSchemas) {
100✔
634
      if (schema.validateNode(o, log: false)) {
50✔
635
        return true;
636
      }
637
    }
638
    // No schema matched, running again to print logs this time.
639
    if (log) {
640
      _logger.severe(
×
641
          "'${o.pathString}' must match atleast one of the allowed schema -");
×
642
      for (final schema in childSchemas) {
×
643
        if (schema.validateNode(o, log: log)) {
×
644
          return true;
645
        }
646
      }
647
    }
648
    return false;
649
  }
650

651
  @override
50✔
652
  SchemaNode extractNode(SchemaNode o) {
653
    for (final schema in childSchemas) {
100✔
654
      if (schema.validateNode(o, log: false)) {
50✔
655
        return o
656
            .withValue(schema.extractNode(o).value as E, o.rawValue)
200✔
657
            .transformOrThis(transform, result);
150✔
658
      }
659
    }
660
    throw SchemaExtractionError(o);
×
661
  }
662

663
  @override
1✔
664
  Map<String, dynamic> generateJsonSchemaNode(Map<String, dynamic> defs) {
665
    return {
1✔
666
      if (schemaDescription != null) "description": schemaDescription!,
1✔
667
      r"$oneOf": childSchemas
2✔
668
          .map((child) => child.getJsonRefOrSchemaNode(defs))
3✔
669
          .toList(),
1✔
670
    };
671
  }
672
}
673

674
void main() {
×
675
  final extractMap = <String, dynamic>{};
×
676
  final testSchema = FixedMapSchema<dynamic>(
×
677
      keys: {
×
678
        "name": StringSchema(
×
679
          result: (node) => extractMap[node.pathString] = node.value,
×
680
        ),
681
        "description": StringSchema(
×
682
          result: (node) => extractMap[node.pathString] = node.value,
×
683
        ),
684
        "output": OneOfSchema(
×
685
          childSchemas: [
×
686
            StringSchema(
×
687
              schemaDefName: "outputBindings",
688
              transform: (node) => extractMap[node.pathString] = node.value,
×
689
            ),
690
            FixedMapSchema(
×
691
              keys: {
×
692
                "bindings": StringSchema(
×
693
                  schemaDefName: "outputBindings",
694
                ),
695
                "symbol-file": FixedMapSchema<dynamic>(keys: {
×
696
                  "output": StringSchema(),
×
697
                  "import-path": StringSchema(),
×
698
                })
699
              },
700
              requiredKeys: ["bindings"],
×
701
              transform: (node) =>
×
702
                  OutputConfig(node.value["bindings"] as String, null),
×
703
              result: (node) => extractMap[node.pathString] = node.value,
×
704
            )
705
          ],
706
        ),
707
        "headers": FixedMapSchema<List<String>>(
×
708
          keys: {
×
709
            "entry-points": ListSchema<String>(childSchema: StringSchema()),
×
710
            "include-directives":
711
                ListSchema<String>(childSchema: StringSchema()),
×
712
          },
713
          result: (node) => extractMap[node.pathString] = node.value,
×
714
        ),
715
        "structs": FixedMapSchema<dynamic>(keys: {
×
716
          "include": ListSchema<String>(childSchema: StringSchema()),
×
717
          "exclude": ListSchema<String>(childSchema: StringSchema()),
×
718
          "rename": DynamicMapSchema<dynamic>(keyValueSchemas: [
×
719
            (
720
              keyRegexp: r"^.+$",
721
              valueSchema:
722
                  OneOfSchema(childSchemas: [StringSchema(), IntSchema()]),
×
723
            )
724
          ]),
725
          "pack": DynamicMapSchema<dynamic>(keyValueSchemas: [
×
726
            (
727
              keyRegexp: r"^.+$",
728
              valueSchema:
729
                  EnumSchema<dynamic>(allowedValues: {null, 1, 2, 4, 8, 16}),
×
730
            )
731
          ])
732
        }),
733
        "comments": FixedMapSchema<dynamic>(
×
734
          keys: {
×
735
            "style": EnumSchema(
×
736
              allowedValues: {"any", "doxygen"},
×
737
              defaultValue: (node) => "doxygen",
×
738
            ),
739
            "length": EnumSchema(
×
740
              allowedValues: {"brief", "full"},
×
741
              defaultValue: (node) => "brief",
×
742
              result: (node) => extractMap[node.pathString] = node.value,
×
743
            ),
744
          },
745
          result: (node) {
×
746
            print("comments rawValue: ${node.rawValue}");
×
747
            print("comments value: ${node.value}");
×
748
            extractMap[node.pathString] = node.value;
×
749
          },
750
        ),
751
      },
752
      result: (node) {
×
753
        print("root rawValue: ${node.rawValue}");
×
754
        print("root value: ${node.value}");
×
755
        extractMap[node.pathString] = node.value;
×
756
      });
757
  _logger.onRecord.listen((event) => print(event));
×
758
  final yaml = loadYaml("""
×
759
name: NativeLibrary
760
description: Bindings to `headers/example.h`.
761
output: 'generated_bindings.dart'
762
headers:
763
  entry-points:
764
    - 'headers/example.h'
765
structs:
766
  rename:
767
    a: b
768
  pack:
769
    a: 2
770
""");
771

772
  print("validate: ${testSchema.validate(yaml)}");
×
773
  print("extract: ${testSchema.extract(yaml).value}");
×
774
  print("extractMap: $extractMap");
×
775
  final defs = <String, dynamic>{};
×
776
  final jsonSchema = testSchema.generateJsonSchemaNode(defs);
×
777
  print("jsonschema object: $jsonSchema");
×
778
  print("defs: $defs");
×
779
  final jsonSchemaJson = jsonEncode({
×
780
    r"$id": "test",
×
781
    r"$schema": "https://json-schema.org/draft/2020-12/schema",
×
782
    ...jsonSchema,
×
783
    r"$defs": defs,
×
784
  });
785
  print("jsonschema file: $jsonSchemaJson");
×
786
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc