Line data Source code
1 : // Copyright 2026 The Authors.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // https://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : import 'dart:convert';
16 :
17 : import 'schema_parser.dart';
18 : import 'schema_rule.dart';
19 : import 'schema_violation.dart';
20 :
21 : /// A compiled JSON Schema validator that validates Dart maps against a schema.
22 : ///
23 : /// [JsonSchemaValidator] is a convenience wrapper around [SchemaParser] that
24 : /// accepts either a `Map<String, dynamic>` or a JSON string, compiles the
25 : /// schema at construction time, and exposes a single [validate] call.
26 : ///
27 : /// This type is intended for consumers who need to validate documents
28 : /// against a JSON Schema without understanding the internal [SchemaRule]
29 : /// hierarchy.
30 : ///
31 : /// Example:
32 : /// ```dart
33 : /// final validator = JsonSchemaValidator.fromMap({
34 : /// 'required': ['name', 'email'],
35 : /// 'properties': {
36 : /// 'name': {'type': 'string', 'minLength': 1},
37 : /// 'email': {'type': 'string', 'format': 'email'},
38 : /// },
39 : /// });
40 : ///
41 : /// final violations = validator.validate({'name': 'Alice'});
42 : /// // → [SchemaViolation(path: 'email', message: 'required field is missing')]
43 : /// ```
44 : final class JsonSchemaValidator {
45 : /// Compiles [schema] into a validator.
46 : ///
47 : /// [schema] must be a JSON Schema subset map. Unknown keywords are silently
48 : /// ignored. An empty map produces a validator that always passes.
49 1 : JsonSchemaValidator.fromMap(Map<String, dynamic> schema)
50 2 : : _rule = SchemaParser().parse(schema);
51 :
52 : /// Parses [json] as a JSON Schema string and compiles it into a validator.
53 : ///
54 : /// Throws [FormatException] if [json] is not valid JSON or if the decoded
55 : /// value is not a JSON object (e.g. an array or a scalar).
56 : ///
57 : /// Example:
58 : /// ```dart
59 : /// final validator = JsonSchemaValidator.fromJson(
60 : /// '{"required": ["name"], "properties": {"name": {"type": "string"}}}',
61 : /// );
62 : /// ```
63 1 : factory JsonSchemaValidator.fromJson(String json) {
64 : final Object? decoded;
65 : try {
66 1 : decoded = jsonDecode(json);
67 1 : } on FormatException {
68 : rethrow;
69 : }
70 1 : if (decoded is! Map<String, dynamic>) {
71 2 : throw FormatException(
72 : 'JSON Schema must be a JSON object, '
73 1 : 'got ${decoded.runtimeType}',
74 : json,
75 : );
76 : }
77 1 : return JsonSchemaValidator.fromMap(decoded);
78 : }
79 :
80 : /// The compiled rule tree produced by [SchemaParser].
81 : final SchemaRule _rule;
82 :
83 : /// Validates [document] against the compiled schema.
84 : ///
85 : /// Returns a list of every [SchemaViolation] found. An empty list means the
86 : /// document is valid. All violations are collected in a single pass so callers
87 : /// can surface every error at once.
88 : ///
89 : /// Example:
90 : /// ```dart
91 : /// final violations = validator.validate({'email': 'bob@example.com'});
92 : /// for (final v in violations) {
93 : /// print(v); // e.g. "name: required field is missing"
94 : /// }
95 : /// ```
96 1 : List<SchemaViolation> validate(Map<String, dynamic> document) {
97 2 : return _rule.validate(document, '');
98 : }
99 : }
|