pjson 2.0.0
A small, owning JSON value for C++11
Loading...
Searching...
No Matches
pjson_schema.h
1//
2// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved.
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// http://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//===----------------------------------------------------------------------===//
16// pjson_schema.h — standalone JSON Schema validation for pjson.
17//
18// pJsonSchemaValidator validates a pjson value against a schema that is itself a
19// pjson value. It is a pure consumer of pjson's public API: it holds a compiled
20// (deep-copied) schema plus options and validates many instances against it.
21// The core pjson type has no schema dependency. The implementation remains in
22// focused private translation units so it can later become an independently
23// linked optional component without changing the DOM API.
24//
25// Default options preserve pjson's documented subset dialect. The explicit
26// Options::draft2020() mode implements the required Draft 2020-12 vocabularies
27// and meta-schema compilation; optional format-assertion, arbitrary-precision,
28// and cross-draft behavior remains outside the contract.
29//
30// Author: Praveen Babu J D
31// License: Apache 2.0
32//
33#ifndef PRAVEENJSON_SCHEMA_H
34#define PRAVEENJSON_SCHEMA_H
35
36#include "pjson.h"
37
38#include <cstddef>
39#include <string>
40#include <vector>
41
42namespace ByteDance {
43 //==[Interface]============================================================
44 /// Validates pjson values against a schema (itself a pjson value).
45 ///
46 /// Construct once from a schema; validate many instances. The schema is
47 /// deep-copied on construction, so the caller's schema value may change or
48 /// be destroyed afterward. Validation never throws and never mutates its
49 /// inputs. Construction resolves and owns external resources; validate() is
50 /// read-only and may be called concurrently when each caller uses its own
51 /// error vector.
52 ///
53 /// Dialect contract: default construction implements the named subset dialect.
54 /// Options::draft2020() selects official Draft 2020-12, bundled meta-schema
55 /// validation, and per-resource vocabulary activation. Custom meta-schemas
56 /// require explicit resolver-based loading. Unknown optional vocabularies are
57 /// annotations and unknown required vocabularies fail compilation.
58 ///
59 /// Supported keywords (subset defaults and required Draft 2020-12 vocabularies):
60 /// type, enum, const, $ref, $dynamicRef, $id, $anchor, $dynamicAnchor;
61 /// properties, patternProperties, propertyNames, required,
62 /// dependentRequired, dependencies, dependentSchemas,
63 /// additionalProperties, unevaluatedProperties, minProperties, maxProperties;
64 /// items, prefixItems, contains, minContains, maxContains,
65 /// minItems, maxItems, uniqueItems, unevaluatedItems;
66 /// minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf;
67 /// minLength, maxLength, pattern, format;
68 /// allOf, anyOf, oneOf, not, if, then, else.
69 /// A boolean schema (true/false) accepts/rejects everything. By default
70 /// unknown or unsupported keywords are ignored; strict() rejects unsupported
71 /// standard keywords. External references require an explicit Resolver;
72 /// pjson never performs network I/O. Regular expressions use a private Unicode-aware
73 /// ECMAScript engine, including property escapes.
74 class PJSON_API pJsonSchemaValidator {
75 public:
76 /// Resolves one absolute schema-document URI during construction.
77 /// Implementations populate aSchema and return true, or return false
78 /// when unavailable. The resolved document is copied into a cache owned
79 /// by the validator; pjson performs no implicit network I/O. The callback
80 /// and aContext need remain valid only until construction returns.
81 typedef bool (*Resolver)(const std::string& aDocumentUri, pjson& aSchema, void* aContext);
82
83 //== Diagnostics =====================================================
84 /// One schema-compilation or instance-validation failure.
85 struct PJSON_API Error {
86 /// Distinguishes an instance-validation failure from an invalid or
87 /// unsupported schema contract discovered while compiling the validator.
88 enum Category {
89 InstanceValidation, ///< The instance violates a valid schema.
90 SchemaCompilation ///< The schema contract is invalid or unsupported.
91 };
92
93 /// Stable machine-readable failure category. `keyword` identifies
94 /// the precise keyword when several keywords share a category.
95 enum Code {
96 None, ///< No failure.
97 FalseSchema, ///< A false boolean schema rejected the instance.
98 TypeMismatch, ///< The instance does not satisfy `type`.
99 ConstMismatch, ///< The instance does not satisfy `const`.
100 EnumMismatch, ///< The instance does not satisfy `enum`.
101 NumericConstraint, ///< A numeric assertion failed.
102 StringConstraint, ///< A string assertion failed.
103 ArrayConstraint, ///< An array assertion failed.
104 ObjectConstraint, ///< An object assertion failed.
105 FormatMismatch, ///< A known asserted format failed.
106 CombinatorMismatch, ///< A logical applicator did not satisfy its contract.
107 ReferenceFailure, ///< A schema reference could not be resolved.
108 ReferenceCycle, ///< Reference evaluation encountered a cycle.
109 RegexFailure, ///< A pattern was invalid or rejected by safety policy.
110 UnsupportedKeyword, ///< Strict mode found an unsupported standard keyword.
111 InvalidSchema, ///< A schema or keyword has an invalid shape or value.
112 UnsupportedDialect, ///< The selected schema dialect is unsupported.
113 UnsupportedVocabulary, ///< A required vocabulary is unsupported.
114 ResolverFailure, ///< The caller's external resolver could not load a schema.
115 ResourceLimit, ///< A compilation, validation, or diagnostic limit was hit.
116 AllocationFailure, ///< Validation could not allocate required temporary state.
117 InternalError ///< An unexpected exception was contained.
118 };
119
120 Code code; ///< Stable machine-readable failure category.
121 Category category; ///< Compilation or instance-validation phase.
122 std::string instanceLocation; ///< JSON Pointer into the instance; empty is root.
123 std::string schemaLocation; ///< Pointer or retrieval URI plus fragment for the schema.
124 std::string keyword; ///< Triggering keyword; empty for whole-schema failures.
125 std::string message; ///< Human-readable diagnostic, not a stable API token.
126 std::vector<Error> causes; ///< Optional bounded combinator branch failures.
127 /// Constructs an empty diagnostic.
128 Error();
129 /// Constructs a fully located diagnostic.
130 Error(Code aCode, Category aCategory, const std::string& aInstanceLocation,
131 const std::string& aSchemaLocation, const std::string& aKeyword,
132 const std::string& aMessage);
133 };
134
135 //== Options =========================================================
136 /// Bounds schema work and controls dialect, format, and resolver policy.
137 ///
138 /// By default only a conservative, non-ambiguous ECMAScript subset is
139 /// accepted and both pattern/subject sizes are capped, preventing
140 /// catastrophic backtracking. trustedRegex() restores unrestricted
141 /// ECMAScript regex behavior for trusted schemas/data; the backend still
142 /// enforces its own finite work ceiling.
143 struct PJSON_API Options {
144 size_t maxRegexPatternBytes; ///< 0 = unlimited (default: 256).
145 size_t maxRegexSubjectBytes; ///< 0 = unlimited (default: 4096).
146 bool allowUnsafeRegex; ///< Permits unrestricted ECMAScript regex (default false).
147 /// Recursive validation depth (default and absolute hard ceiling: 32).
148 /// Zero selects 32, and larger values are clamped to 32.
149 size_t maxValidationDepth; ///< Recursive validation depth budget.
150 /// Resolved references (default 1024); zero selects the hard ceiling of 1024.
151 size_t maxRefResolutions; ///< Resolved-reference budget.
152 /// Validation work units (default 1,000,000); zero selects that hard ceiling.
153 size_t maxValidationWork; ///< Total validation work-unit budget.
154 /// Reported errors (default 100); zero selects the hard ceiling of 100.
155 size_t maxErrors; ///< Collected top-level and nested diagnostic budget.
156 bool stopAfterFirstError; ///< Stops compilation or validation after the first error.
157 bool collectNestedCauses; ///< Retains bounded anyOf/oneOf branch failures.
158 bool validateFormats; ///< Validates known string formats (default true).
159 // Strict, fail-closed subset mode. When true, a schema that uses a
160 // standard validation/applicator keyword this validator does not
161 // implement makes validation fail rather than silently ignoring the
162 // constraint. Unknown non-standard extension keywords are still
163 // allowed as annotations. Default false keeps permissive behavior.
164 bool strictSubset; ///< Rejects unsupported standard keywords when true.
165 /// Applies `$ref` siblings using pjson's modern subset semantics.
166 /// The default false preserves legacy Draft 7 replacement semantics.
167 bool refSiblings; ///< True when `$ref` siblings must also be evaluated.
168 /// Allows an unknown absolute `$schema` URI to be resolved during
169 /// construction and interpreted through its `$vocabulary` object.
170 bool resolveCustomDialects; ///< Enables explicit custom meta-schema resolution.
171 /// Retrieval URI used as the initial base when the root has no `$id`.
172 /// Leave empty only when all references are absolute or local.
173 std::string retrievalUri; ///< Initial base URI for a root without `$id`.
174 /// Dialect used when the root schema has no `$schema`. An empty value
175 /// selects documentedSubsetDialectUri(). Use draft2020() instead of
176 /// setting this field directly when selecting the official dialect.
177 std::string defaultDialectUri; ///< Dialect used when `$schema` is absent.
178 Resolver resolver; ///< Construction-only external-schema resolver.
179 void* resolverContext; ///< Construction-only opaque resolver context.
180 size_t maxResolvedDocuments; ///< External-document budget (default 32).
181 size_t maxResolvedBytes; ///< Compact resolved-DOM byte budget (default 16 MiB).
182 /// Selects bounded safe-regex, traversal, reference, work, error, and format defaults.
183 Options();
184 /// Disables only regex restrictions; all other defaults remain enabled.
186 /// Returns the defaults with strict fail-closed subset mode enabled.
187 static Options strict();
188 /// Selects modern subset semantics: `$ref` siblings apply and
189 /// `format` is annotation-only unless the caller re-enables it.
191 /// Selects the official Draft 2020-12 dialect, modern `$ref`
192 /// semantics, annotation-only format, and custom dialect resolution.
194 };
195
196 //== Construction ====================================================
197 /// Compiles aSchema (deep-copied with the default allocator) and all
198 /// resolved resources. Resolver failures, malformed references, and
199 /// duplicate IDs/anchors are reported through isSchemaValid() and
200 /// schemaErrors(); allocation failure may still throw std::bad_alloc.
201 explicit pJsonSchemaValidator(const pjson& aSchema, const Options& aOptions = Options());
202 /// Destroys the compiled schema.
204
205 //== Validation ======================================================
206 /// Returns whether aInstance conforms to the compiled schema. Never throws.
207 bool validate(const pjson& aInstance) const noexcept;
208 /// Validates and appends discovered failures to aErrors. If schema
209 /// compilation failed, appends schemaErrors() instead. Never throws.
210 bool validate(const pjson& aInstance, std::vector<Error>& aErrors) const noexcept;
211
212 //== Dialect / compilation contract ===================================
213 /// URI naming pjson's explicitly documented JSON Schema subset dialect.
214 static const char* documentedSubsetDialectUri() noexcept;
215 /// URI naming the vocabulary implemented by the subset dialect.
216 static const char* documentedSubsetVocabularyUri() noexcept;
217 /// URI of the supported official JSON Schema Draft 2020-12 dialect.
218 static const char* draft2020DialectUri() noexcept;
219 /// Returns whether dialect and required-vocabulary compilation succeeded.
220 bool isSchemaValid() const noexcept;
221 /// Returns immutable schema-compilation diagnostics (paths address the schema).
222 const std::vector<Error>& schemaErrors() const noexcept;
223 /// Returns the effective dialect URI selected by `$schema` or the option default.
224 const std::string& dialect() const noexcept;
225
226 //== Introspection ===================================================
227 /// Returns the compiled schema value (read-only).
228 const pjson& schema() const noexcept;
229 /// Returns the options in effect for this validator.
230 /// Construction-only resolver pointers are cleared in this snapshot.
231 const Options& options() const noexcept;
232
233 private:
234 struct Impl;
237
238 Impl* _impl;
239 };
240} // namespace ByteDance
241
242#endif /* !PRAVEENJSON_SCHEMA_H */
Validates pjson values against a schema (itself a pjson value).
~pJsonSchemaValidator()
Destroys the compiled schema.
bool validate(const pjson &aInstance, std::vector< Error > &aErrors) const noexcept
Validates and appends discovered failures to aErrors. If schema compilation failed,...
bool validate(const pjson &aInstance) const noexcept
Returns whether aInstance conforms to the compiled schema. Never throws.
pJsonSchemaValidator(const pjson &aSchema, const Options &aOptions=Options())
Compiles aSchema (deep-copied with the default allocator) and all resolved resources....
static const char * documentedSubsetDialectUri() noexcept
URI naming pjson's explicitly documented JSON Schema subset dialect.
Owning, mutable JSON value with deep-copy semantics.
Definition pjson.h:68
Namespace containing the pjson public API.
Definition pjson.h:58
One schema-compilation or instance-validation failure.
std::string instanceLocation
JSON Pointer into the instance; empty is root.
Code code
Stable machine-readable failure category.
std::string message
Human-readable diagnostic, not a stable API token.
Code
Stable machine-readable failure category. keyword identifies the precise keyword when several keyword...
@ UnsupportedVocabulary
A required vocabulary is unsupported.
@ AllocationFailure
Validation could not allocate required temporary state.
@ StringConstraint
A string assertion failed.
@ ConstMismatch
The instance does not satisfy const.
@ RegexFailure
A pattern was invalid or rejected by safety policy.
@ CombinatorMismatch
A logical applicator did not satisfy its contract.
@ ArrayConstraint
An array assertion failed.
@ UnsupportedKeyword
Strict mode found an unsupported standard keyword.
@ ReferenceFailure
A schema reference could not be resolved.
@ EnumMismatch
The instance does not satisfy enum.
@ ObjectConstraint
An object assertion failed.
@ ResolverFailure
The caller's external resolver could not load a schema.
@ FalseSchema
A false boolean schema rejected the instance.
@ ReferenceCycle
Reference evaluation encountered a cycle.
@ InvalidSchema
A schema or keyword has an invalid shape or value.
@ ResourceLimit
A compilation, validation, or diagnostic limit was hit.
@ TypeMismatch
The instance does not satisfy type.
@ FormatMismatch
A known asserted format failed.
@ UnsupportedDialect
The selected schema dialect is unsupported.
@ NumericConstraint
A numeric assertion failed.
std::string keyword
Triggering keyword; empty for whole-schema failures.
Category
Distinguishes an instance-validation failure from an invalid or unsupported schema contract discovere...
@ InstanceValidation
The instance violates a valid schema.
Error(Code aCode, Category aCategory, const std::string &aInstanceLocation, const std::string &aSchemaLocation, const std::string &aKeyword, const std::string &aMessage)
Constructs a fully located diagnostic.
std::string schemaLocation
Pointer or retrieval URI plus fragment for the schema.
std::vector< Error > causes
Optional bounded combinator branch failures. Constructs an empty diagnostic.
Category category
Compilation or instance-validation phase.
Bounds schema work and controls dialect, format, and resolver policy.
bool collectNestedCauses
Retains bounded anyOf/oneOf branch failures.
static Options trustedRegex()
Disables only regex restrictions; all other defaults remain enabled.
void * resolverContext
Construction-only opaque resolver context.
size_t maxRefResolutions
Resolved-reference budget. Validation work units (default 1,000,000); zero selects that hard ceiling.
static Options strict()
Returns the defaults with strict fail-closed subset mode enabled.
bool allowUnsafeRegex
Permits unrestricted ECMAScript regex (default false). Recursive validation depth (default and absolu...
Resolver resolver
Construction-only external-schema resolver.
std::string defaultDialectUri
Dialect used when $schema is absent.
size_t maxRegexSubjectBytes
0 = unlimited (default: 4096).
bool strictSubset
Rejects unsupported standard keywords when true. Applies $ref siblings using pjson's modern subset se...
static Options draft2020()
Selects the official Draft 2020-12 dialect, modern $ref semantics, annotation-only format,...
bool refSiblings
True when $ref siblings must also be evaluated. Allows an unknown absolute $schema URI to be resolved...
size_t maxValidationDepth
Recursive validation depth budget. Resolved references (default 1024); zero selects the hard ceiling ...
static Options modernSubset()
Selects modern subset semantics: $ref siblings apply and format is annotation-only unless the caller ...
size_t maxValidationWork
Total validation work-unit budget. Reported errors (default 100); zero selects the hard ceiling of 10...
size_t maxRegexPatternBytes
0 = unlimited (default: 256).
std::string retrievalUri
Initial base URI for a root without $id. Dialect used when the root schema has no $schema....
size_t maxResolvedDocuments
External-document budget (default 32).
bool stopAfterFirstError
Stops compilation or validation after the first error.
size_t maxErrors
Collected top-level and nested diagnostic budget.
size_t maxResolvedBytes
Compact resolved-DOM byte budget (default 16 MiB). Selects bounded safe-regex, traversal,...
bool resolveCustomDialects
Enables explicit custom meta-schema resolution. Retrieval URI used as the initial base when the root ...
bool validateFormats
Validates known string formats (default true).