Merge pull request #1068 from yurikhan/violationDetails
Schema violation details
This commit is contained in:
commit
daabb88e00
280
doc/schema.md
280
doc/schema.md
@ -8,7 +8,7 @@ RapidJSON implemented a JSON Schema validator for [JSON Schema Draft v4](http://
|
||||
|
||||
[TOC]
|
||||
|
||||
# Basic Usage {#BasicUsage}
|
||||
# Basic Usage {#Basic}
|
||||
|
||||
First of all, you need to parse a JSON Schema into `Document`, and then compile the `Document` into a `SchemaDocument`.
|
||||
|
||||
@ -52,11 +52,11 @@ Some notes:
|
||||
* One `SchemaDocument` can be referenced by multiple `SchemaValidator`s. It will not be modified by `SchemaValidator`s.
|
||||
* A `SchemaValidator` may be reused to validate multiple documents. To run it for other documents, call `validator.Reset()` first.
|
||||
|
||||
# Validation during parsing/serialization {#ParsingSerialization}
|
||||
# Validation during parsing/serialization {#Fused}
|
||||
|
||||
Unlike most JSON Schema validator implementations, RapidJSON provides a SAX-based schema validator. Therefore, you can parse a JSON from a stream while validating it on the fly. If the validator encounters a JSON value that invalidates the supplied schema, the parsing will be terminated immediately. This design is especially useful for parsing large JSON files.
|
||||
|
||||
## DOM parsing {#DomParsing}
|
||||
## DOM parsing {#DOM}
|
||||
|
||||
For using DOM in parsing, `Document` needs some preparation and finalizing tasks, in addition to receiving SAX events, thus it needs some work to route the reader, validator and the document. `SchemaValidatingReader` is a helper class that doing such work.
|
||||
|
||||
@ -97,7 +97,7 @@ if (!reader.GetParseResult()) {
|
||||
}
|
||||
~~~
|
||||
|
||||
## SAX parsing {#SaxParsing}
|
||||
## SAX parsing {#SAX}
|
||||
|
||||
For using SAX in parsing, it is much simpler. If it only need to validate the JSON without further processing, it is simply:
|
||||
|
||||
@ -144,7 +144,7 @@ if (!d.Accept(validator)) {
|
||||
|
||||
Of course, if your application only needs SAX-style serialization, it can simply send SAX events to `SchemaValidator` instead of `Writer`.
|
||||
|
||||
# Remote Schema {#RemoteSchema}
|
||||
# Remote Schema {#Remote}
|
||||
|
||||
JSON Schema supports [`$ref` keyword](http://spacetelescope.github.io/understanding-json-schema/structuring.html), which is a [JSON pointer](doc/pointer.md) referencing to a local or remote schema. Local pointer is prefixed with `#`, while remote pointer is an relative or absolute URI. For example:
|
||||
|
||||
@ -176,7 +176,7 @@ The failed test is "changed scope ref invalid" of "change resolution scope" in `
|
||||
|
||||
Besides, the `format` schema keyword for string values is ignored, since it is not required by the specification.
|
||||
|
||||
## Regular Expression {#RegEx}
|
||||
## Regular Expression {#Regex}
|
||||
|
||||
The schema keyword `pattern` and `patternProperties` uses regular expression to match the required pattern.
|
||||
|
||||
@ -235,3 +235,271 @@ On a Mac Book Pro (2.8 GHz Intel Core i7), the following results are collected.
|
||||
|[`jayschema`](https://github.com/natesilva/jayschema)|0.1%|21 (± 1.14%)|
|
||||
|
||||
That is, RapidJSON is about 1.5x faster than the fastest JavaScript library (ajv). And 1400x faster than the slowest one.
|
||||
|
||||
# Schema violation reporting {#Reporting}
|
||||
|
||||
(Unreleased as of 2017-09-20)
|
||||
|
||||
When validating an instance against a JSON Schema,
|
||||
it is often desirable to report not only whether the instance is valid,
|
||||
but also the ways in which it violates the schema.
|
||||
|
||||
The `SchemaValidator` class
|
||||
collects errors encountered during validation
|
||||
into a JSON `Value`.
|
||||
This error object can then be accessed as `validator.GetError()`.
|
||||
|
||||
The structure of the error object is subject to change
|
||||
in future versions of RapidJSON,
|
||||
as there is no standard schema for violations.
|
||||
The details below this point are provisional only.
|
||||
|
||||
## General provisions {#ReportingGeneral}
|
||||
|
||||
Validation of an instance value against a schema
|
||||
produces an error value.
|
||||
The error value is always an object.
|
||||
An empty object `{}` indicates the instance is valid.
|
||||
|
||||
* The name of each member
|
||||
corresponds to the JSON Schema keyword that is violated.
|
||||
* The value is either an object describing a single violation,
|
||||
or an array of such objects.
|
||||
|
||||
Each violation object contains two string-valued members
|
||||
named `instanceRef` and `schemaRef`.
|
||||
`instanceRef` contains the URI fragment serialization
|
||||
of a JSON Pointer to the instance subobject
|
||||
in which the violation was detected.
|
||||
`schemaRef` contains the URI of the schema
|
||||
and the fragment serialization of a JSON Pointer
|
||||
to the subschema that was violated.
|
||||
|
||||
Individual violation objects can contain other keyword-specific members.
|
||||
These are detailed further.
|
||||
|
||||
For example, validating this instance:
|
||||
|
||||
~~~json
|
||||
{"numbers": [1, 2, "3", 4, 5]}
|
||||
~~~
|
||||
|
||||
against this schema:
|
||||
|
||||
~~~json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"numbers": {"$ref": "numbers.schema.json"}
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
where `numbers.schema.json` refers
|
||||
(via a suitable `IRemoteSchemaDocumentProvider`)
|
||||
to this schema:
|
||||
|
||||
~~~json
|
||||
{
|
||||
"type": "array",
|
||||
"items": {"type": "number"}
|
||||
}
|
||||
~~~
|
||||
|
||||
produces the following error object:
|
||||
|
||||
~~~json
|
||||
{
|
||||
"type": {
|
||||
"instanceRef": "#/numbers/2",
|
||||
"schemaRef": "numbers.schema.json#/items",
|
||||
"expected": ["number"],
|
||||
"actual": "string"
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## Validation keywords for numbers {#Numbers}
|
||||
|
||||
### multipleOf {#multipleof}
|
||||
|
||||
* `expected`: required number strictly greater than 0.
|
||||
The value of the `multipleOf` keyword specified in the schema.
|
||||
* `actual`: required number.
|
||||
The instance value.
|
||||
|
||||
### maximum {#maximum}
|
||||
|
||||
* `expected`: required number.
|
||||
The value of the `maximum` keyword specified in the schema.
|
||||
* `exclusiveMaximum`: optional boolean.
|
||||
This will be true if the schema specified `"exclusiveMaximum": true`,
|
||||
and will be omitted otherwise.
|
||||
* `actual`: required number.
|
||||
The instance value.
|
||||
|
||||
### minimum {#minimum}
|
||||
|
||||
* `expected`: required number.
|
||||
The value of the `minimum` keyword specified in the schema.
|
||||
* `exclusiveMinimum`: optional boolean.
|
||||
This will be true if the schema specified `"exclusiveMinimum": true`,
|
||||
and will be omitted otherwise.
|
||||
* `actual`: required number.
|
||||
The instance value.
|
||||
|
||||
## Validation keywords for strings {#Strings}
|
||||
|
||||
### maxLength {#maxLength}
|
||||
|
||||
* `expected`: required number greater than or equal to 0.
|
||||
The value of the `maxLength` keyword specified in the schema.
|
||||
* `actual`: required string.
|
||||
The instance value.
|
||||
|
||||
### minLength {#minLength}
|
||||
|
||||
* `expected`: required number greater than or equal to 0.
|
||||
The value of the `minLength` keyword specified in the schema.
|
||||
* `actual`: required string.
|
||||
The instance value.
|
||||
|
||||
### pattern {#pattern}
|
||||
|
||||
* `actual`: required string.
|
||||
The instance value.
|
||||
|
||||
(The expected pattern is not reported
|
||||
because the internal representation in `SchemaDocument`
|
||||
does not store the pattern in original string form.)
|
||||
|
||||
## Validation keywords for arrays {#Arrays}
|
||||
|
||||
### additionalItems {#additionalItems}
|
||||
|
||||
This keyword is reported
|
||||
when the value of `items` schema keyword is an array,
|
||||
the value of `additionalItems` is `false`,
|
||||
and the instance is an array
|
||||
with more items than specified in the `items` array.
|
||||
|
||||
* `disallowed`: required integer greater than or equal to 0.
|
||||
The index of the first item that has no corresponding schema.
|
||||
|
||||
### maxItems and minItems {#maxItems-minItems}
|
||||
|
||||
* `expected`: required integer greater than or equal to 0.
|
||||
The value of `maxItems` (respectively, `minItems`)
|
||||
specified in the schema.
|
||||
* `actual`: required integer greater than or equal to 0.
|
||||
Number of items in the instance array.
|
||||
|
||||
### uniqueItems {#uniqueItems}
|
||||
|
||||
* `duplicates`: required array
|
||||
whose items are integers greater than or equal to 0.
|
||||
Indices of items of the instance that are equal.
|
||||
|
||||
(RapidJSON only reports the first two equal items,
|
||||
for performance reasons.)
|
||||
|
||||
## Validation keywords for objects
|
||||
|
||||
### maxProperties and minProperties {#maxProperties-minProperties}
|
||||
|
||||
* `expected`: required integer greater than or equal to 0.
|
||||
The value of `maxProperties` (respectively, `minProperties`)
|
||||
specified in the schema.
|
||||
* `actual`: required integer greater than or equal to 0.
|
||||
Number of properties in the instance object.
|
||||
|
||||
### required {#required}
|
||||
|
||||
* `missing`: required array of one or more unique strings.
|
||||
The names of properties
|
||||
that are listed in the value of the `required` schema keyword
|
||||
but not present in the instance object.
|
||||
|
||||
### additionalProperties {#additionalProperties}
|
||||
|
||||
This keyword is reported
|
||||
when the schema specifies `additionalProperties: false`
|
||||
and the name of a property of the instance is
|
||||
neither listed in the `properties` keyword
|
||||
nor matches any regular expression in the `patternProperties` keyword.
|
||||
|
||||
* `disallowed`: required string.
|
||||
Name of the offending property of the instance.
|
||||
|
||||
(For performance reasons,
|
||||
RapidJSON only reports the first such property encountered.)
|
||||
|
||||
### dependencies {#dependencies}
|
||||
|
||||
* `errors`: required object with one or more properties.
|
||||
Names and values of its properties are described below.
|
||||
|
||||
Recall that JSON Schema Draft 04 supports
|
||||
*schema dependencies*,
|
||||
where presence of a named *controlling* property
|
||||
requires the instance object to be valid against a subschema,
|
||||
and *property dependencies*,
|
||||
where presence of a controlling property
|
||||
requires other *dependent* properties to be also present.
|
||||
|
||||
For a violated schema dependency,
|
||||
`errors` will contain a property
|
||||
with the name of the controlling property
|
||||
and its value will be the error object
|
||||
produced by validating the instance object
|
||||
against the dependent schema.
|
||||
|
||||
For a violated property dependency,
|
||||
`errors` will contain a property
|
||||
with the name of the controlling property
|
||||
and its value will be an array of one or more unique strings
|
||||
listing the missing dependent properties.
|
||||
|
||||
## Validation keywords for any instance type {#AnyTypes}
|
||||
|
||||
### enum {#enum}
|
||||
|
||||
This keyword has no additional properties
|
||||
beyond `instanceRef` and `schemaRef`.
|
||||
|
||||
* The allowed values are not listed
|
||||
because `SchemaDocument` does not store them in original form.
|
||||
* The violating value is not reported
|
||||
because it might be unwieldy.
|
||||
|
||||
If you need to report these details to your users,
|
||||
you can access the necessary information
|
||||
by following `instanceRef` and `schemaRef`.
|
||||
|
||||
### type {#type}
|
||||
|
||||
* `expected`: required array of one or more unique strings,
|
||||
each of which is one of the seven primitive types
|
||||
defined by the JSON Schema Draft 04 Core specification.
|
||||
Lists the types allowed by the `type` schema keyword.
|
||||
* `actual`: required string, also one of seven primitive types.
|
||||
The primitive type of the instance.
|
||||
|
||||
### allOf, anyOf, and oneOf {#allOf-anyOf-oneOf}
|
||||
|
||||
* `errors`: required array of at least one object.
|
||||
There will be as many items as there are subschemas
|
||||
in the `allOf`, `anyOf` or `oneOf` schema keyword, respectively.
|
||||
Each item will be the error value
|
||||
produced by validating the instance
|
||||
against the corresponding subschema.
|
||||
|
||||
For `allOf`, at least one error value will be non-empty.
|
||||
For `anyOf`, all error values will be non-empty.
|
||||
For `oneOf`, either all error values will be non-empty,
|
||||
or more than one will be empty.
|
||||
|
||||
### not {#not}
|
||||
|
||||
This keyword has no additional properties
|
||||
apart from `instanceRef` and `schemaRef`.
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "rapidjson/filereadstream.h"
|
||||
#include "rapidjson/schema.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
@ -67,6 +68,11 @@ int main(int argc, char *argv[]) {
|
||||
sb.Clear();
|
||||
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
|
||||
fprintf(stderr, "Invalid document: %s\n", sb.GetString());
|
||||
// Detailed violation report is available as a JSON value
|
||||
sb.Clear();
|
||||
PrettyWriter<StringBuffer> w(sb);
|
||||
validator.GetError().Accept(w);
|
||||
fprintf(stderr, "Error report:\n%s\n", sb.GetString());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "document.h"
|
||||
#include "pointer.h"
|
||||
#include "stringbuffer.h"
|
||||
#include <cmath> // abs, floor
|
||||
|
||||
#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX)
|
||||
@ -157,6 +158,62 @@ public:
|
||||
virtual void FreeState(void* p) = 0;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// IValidationErrorHandler
|
||||
|
||||
template <typename SchemaType>
|
||||
class IValidationErrorHandler {
|
||||
public:
|
||||
typedef typename SchemaType::Ch Ch;
|
||||
typedef typename SchemaType::SValue SValue;
|
||||
|
||||
virtual ~IValidationErrorHandler() {}
|
||||
|
||||
virtual void NotMultipleOf(int64_t actual, const SValue& expected) = 0;
|
||||
virtual void NotMultipleOf(uint64_t actual, const SValue& expected) = 0;
|
||||
virtual void NotMultipleOf(double actual, const SValue& expected) = 0;
|
||||
virtual void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) = 0;
|
||||
virtual void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) = 0;
|
||||
virtual void AboveMaximum(double actual, const SValue& expected, bool exclusive) = 0;
|
||||
virtual void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) = 0;
|
||||
virtual void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) = 0;
|
||||
virtual void BelowMinimum(double actual, const SValue& expected, bool exclusive) = 0;
|
||||
|
||||
virtual void TooLong(const Ch* str, SizeType length, SizeType expected) = 0;
|
||||
virtual void TooShort(const Ch* str, SizeType length, SizeType expected) = 0;
|
||||
virtual void DoesNotMatch(const Ch* str, SizeType length) = 0;
|
||||
|
||||
virtual void DisallowedItem(SizeType index) = 0;
|
||||
virtual void TooFewItems(SizeType actualCount, SizeType expectedCount) = 0;
|
||||
virtual void TooManyItems(SizeType actualCount, SizeType expectedCount) = 0;
|
||||
virtual void DuplicateItems(SizeType index1, SizeType index2) = 0;
|
||||
|
||||
virtual void TooManyProperties(SizeType actualCount, SizeType expectedCount) = 0;
|
||||
virtual void TooFewProperties(SizeType actualCount, SizeType expectedCount) = 0;
|
||||
virtual void StartMissingProperties() = 0;
|
||||
virtual void AddMissingProperty(const SValue& name) = 0;
|
||||
virtual bool EndMissingProperties() = 0;
|
||||
virtual void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) = 0;
|
||||
virtual void DisallowedProperty(const Ch* name, SizeType length) = 0;
|
||||
|
||||
virtual void StartDependencyErrors() = 0;
|
||||
virtual void StartMissingDependentProperties() = 0;
|
||||
virtual void AddMissingDependentProperty(const SValue& targetName) = 0;
|
||||
virtual void EndMissingDependentProperties(const SValue& sourceName) = 0;
|
||||
virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0;
|
||||
virtual bool EndDependencyErrors() = 0;
|
||||
|
||||
virtual void DisallowedValue() = 0;
|
||||
virtual void StartDisallowedType() = 0;
|
||||
virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0;
|
||||
virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0;
|
||||
virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0;
|
||||
virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0;
|
||||
virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0;
|
||||
virtual void Disallowed() = 0;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Hasher
|
||||
|
||||
@ -261,6 +318,7 @@ template <typename SchemaDocumentType>
|
||||
struct SchemaValidationContext {
|
||||
typedef Schema<SchemaDocumentType> SchemaType;
|
||||
typedef ISchemaStateFactory<SchemaType> SchemaValidatorFactoryType;
|
||||
typedef IValidationErrorHandler<SchemaType> ErrorHandlerType;
|
||||
typedef typename SchemaType::ValueType ValueType;
|
||||
typedef typename ValueType::Ch Ch;
|
||||
|
||||
@ -270,8 +328,9 @@ struct SchemaValidationContext {
|
||||
kPatternValidatorWithAdditionalProperty
|
||||
};
|
||||
|
||||
SchemaValidationContext(SchemaValidatorFactoryType& f, const SchemaType* s) :
|
||||
SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) :
|
||||
factory(f),
|
||||
error_handler(eh),
|
||||
schema(s),
|
||||
valueSchema(),
|
||||
invalidKeyword(),
|
||||
@ -311,6 +370,7 @@ struct SchemaValidationContext {
|
||||
}
|
||||
|
||||
SchemaValidatorFactoryType& factory;
|
||||
ErrorHandlerType& error_handler;
|
||||
const SchemaType* schema;
|
||||
const SchemaType* valueSchema;
|
||||
const Ch* invalidKeyword;
|
||||
@ -345,10 +405,12 @@ public:
|
||||
typedef SchemaValidationContext<SchemaDocumentType> Context;
|
||||
typedef Schema<SchemaDocumentType> SchemaType;
|
||||
typedef GenericValue<EncodingType, AllocatorType> SValue;
|
||||
typedef IValidationErrorHandler<Schema> ErrorHandler;
|
||||
friend class GenericSchemaDocument<ValueType, AllocatorType>;
|
||||
|
||||
Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) :
|
||||
allocator_(allocator),
|
||||
uri_(schemaDocument->GetURI(), *allocator),
|
||||
pointer_(p),
|
||||
typeless_(schemaDocument->GetTypeless()),
|
||||
enum_(),
|
||||
@ -598,6 +660,10 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
const SValue& GetURI() const {
|
||||
return uri_;
|
||||
}
|
||||
|
||||
const PointerType& GetPointer() const {
|
||||
return pointer_;
|
||||
}
|
||||
@ -616,8 +682,10 @@ public:
|
||||
context.valueSchema = additionalItemsSchema_;
|
||||
else if (additionalItems_)
|
||||
context.valueSchema = typeless_;
|
||||
else
|
||||
else {
|
||||
context.error_handler.DisallowedItem(context.arrayElementIndex);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString());
|
||||
}
|
||||
}
|
||||
else
|
||||
context.valueSchema = typeless_;
|
||||
@ -642,15 +710,21 @@ public:
|
||||
}
|
||||
|
||||
if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) {
|
||||
if (!patternValid)
|
||||
if (!patternValid) {
|
||||
context.error_handler.PropertyViolations(context.patternPropertiesValidators, count);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString());
|
||||
}
|
||||
}
|
||||
else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) {
|
||||
if (!patternValid || !otherValid)
|
||||
if (!patternValid || !otherValid) {
|
||||
context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString());
|
||||
}
|
||||
}
|
||||
else if (!patternValid && !otherValid) // kPatternValidatorWithAdditionalProperty)
|
||||
else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty)
|
||||
context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString());
|
||||
}
|
||||
}
|
||||
|
||||
if (enum_) {
|
||||
@ -658,19 +732,23 @@ public:
|
||||
for (SizeType i = 0; i < enumCount_; i++)
|
||||
if (enum_[i] == h)
|
||||
goto foundEnum;
|
||||
context.error_handler.DisallowedValue();
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString());
|
||||
foundEnum:;
|
||||
}
|
||||
|
||||
if (allOf_.schemas)
|
||||
for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++)
|
||||
if (!context.validators[i]->IsValid())
|
||||
if (!context.validators[i]->IsValid()) {
|
||||
context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString());
|
||||
}
|
||||
|
||||
if (anyOf_.schemas) {
|
||||
for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++)
|
||||
if (context.validators[i]->IsValid())
|
||||
goto foundAny;
|
||||
context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString());
|
||||
foundAny:;
|
||||
}
|
||||
@ -679,30 +757,39 @@ public:
|
||||
bool oneValid = false;
|
||||
for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++)
|
||||
if (context.validators[i]->IsValid()) {
|
||||
if (oneValid)
|
||||
if (oneValid) {
|
||||
context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString());
|
||||
else
|
||||
} else
|
||||
oneValid = true;
|
||||
}
|
||||
if (!oneValid)
|
||||
if (!oneValid) {
|
||||
context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString());
|
||||
}
|
||||
}
|
||||
|
||||
if (not_ && context.validators[notValidatorIndex_]->IsValid())
|
||||
if (not_ && context.validators[notValidatorIndex_]->IsValid()) {
|
||||
context.error_handler.Disallowed();
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Null(Context& context) const {
|
||||
if (!(type_ & (1 << kNullSchemaType)))
|
||||
if (!(type_ & (1 << kNullSchemaType))) {
|
||||
DisallowedType(context, GetNullString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
return CreateParallelValidator(context);
|
||||
}
|
||||
|
||||
bool Bool(Context& context, bool) const {
|
||||
if (!(type_ & (1 << kBooleanSchemaType)))
|
||||
if (!(type_ & (1 << kBooleanSchemaType))) {
|
||||
DisallowedType(context, GetBooleanString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
return CreateParallelValidator(context);
|
||||
}
|
||||
|
||||
@ -731,8 +818,10 @@ public:
|
||||
}
|
||||
|
||||
bool Double(Context& context, double d) const {
|
||||
if (!(type_ & (1 << kNumberSchemaType)))
|
||||
if (!(type_ & (1 << kNumberSchemaType))) {
|
||||
DisallowedType(context, GetNumberString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
|
||||
if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d))
|
||||
return false;
|
||||
@ -747,28 +836,38 @@ public:
|
||||
}
|
||||
|
||||
bool String(Context& context, const Ch* str, SizeType length, bool) const {
|
||||
if (!(type_ & (1 << kStringSchemaType)))
|
||||
if (!(type_ & (1 << kStringSchemaType))) {
|
||||
DisallowedType(context, GetStringString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
|
||||
if (minLength_ != 0 || maxLength_ != SizeType(~0)) {
|
||||
SizeType count;
|
||||
if (internal::CountStringCodePoint<EncodingType>(str, length, &count)) {
|
||||
if (count < minLength_)
|
||||
if (count < minLength_) {
|
||||
context.error_handler.TooShort(str, length, minLength_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString());
|
||||
if (count > maxLength_)
|
||||
}
|
||||
if (count > maxLength_) {
|
||||
context.error_handler.TooLong(str, length, maxLength_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pattern_ && !IsPatternMatch(pattern_, str, length))
|
||||
if (pattern_ && !IsPatternMatch(pattern_, str, length)) {
|
||||
context.error_handler.DoesNotMatch(str, length);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString());
|
||||
}
|
||||
|
||||
return CreateParallelValidator(context);
|
||||
}
|
||||
|
||||
bool StartObject(Context& context) const {
|
||||
if (!(type_ & (1 << kObjectSchemaType)))
|
||||
if (!(type_ & (1 << kObjectSchemaType))) {
|
||||
DisallowedType(context, GetObjectString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
|
||||
if (hasDependencies_ || hasRequired_) {
|
||||
context.propertyExist = static_cast<bool*>(context.factory.MallocState(sizeof(bool) * propertyCount_));
|
||||
@ -826,45 +925,65 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.patternPropertiesSchemaCount == 0) // patternProperties are not additional properties
|
||||
if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties
|
||||
context.error_handler.DisallowedProperty(str, len);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndObject(Context& context, SizeType memberCount) const {
|
||||
if (hasRequired_)
|
||||
if (hasRequired_) {
|
||||
context.error_handler.StartMissingProperties();
|
||||
for (SizeType index = 0; index < propertyCount_; index++)
|
||||
if (properties_[index].required)
|
||||
if (!context.propertyExist[index])
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString());
|
||||
if (properties_[index].required && !context.propertyExist[index])
|
||||
context.error_handler.AddMissingProperty(properties_[index].name);
|
||||
if (context.error_handler.EndMissingProperties())
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString());
|
||||
}
|
||||
|
||||
if (memberCount < minProperties_)
|
||||
if (memberCount < minProperties_) {
|
||||
context.error_handler.TooFewProperties(memberCount, minProperties_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString());
|
||||
}
|
||||
|
||||
if (memberCount > maxProperties_)
|
||||
if (memberCount > maxProperties_) {
|
||||
context.error_handler.TooManyProperties(memberCount, maxProperties_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString());
|
||||
}
|
||||
|
||||
if (hasDependencies_) {
|
||||
for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++)
|
||||
context.error_handler.StartDependencyErrors();
|
||||
for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) {
|
||||
const Property& source = properties_[sourceIndex];
|
||||
if (context.propertyExist[sourceIndex]) {
|
||||
if (properties_[sourceIndex].dependencies) {
|
||||
if (source.dependencies) {
|
||||
context.error_handler.StartMissingDependentProperties();
|
||||
for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++)
|
||||
if (properties_[sourceIndex].dependencies[targetIndex] && !context.propertyExist[targetIndex])
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString());
|
||||
if (source.dependencies[targetIndex] && !context.propertyExist[targetIndex])
|
||||
context.error_handler.AddMissingDependentProperty(properties_[targetIndex].name);
|
||||
context.error_handler.EndMissingDependentProperties(source.name);
|
||||
}
|
||||
else if (source.dependenciesSchema) {
|
||||
ISchemaValidator* dependenciesValidator = context.validators[source.dependenciesValidatorIndex];
|
||||
if (!dependenciesValidator->IsValid())
|
||||
context.error_handler.AddDependencySchemaError(source.name, dependenciesValidator);
|
||||
}
|
||||
else if (properties_[sourceIndex].dependenciesSchema)
|
||||
if (!context.validators[properties_[sourceIndex].dependenciesValidatorIndex]->IsValid())
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString());
|
||||
}
|
||||
}
|
||||
if (context.error_handler.EndDependencyErrors())
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StartArray(Context& context) const {
|
||||
if (!(type_ & (1 << kArraySchemaType)))
|
||||
if (!(type_ & (1 << kArraySchemaType))) {
|
||||
DisallowedType(context, GetArrayString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
|
||||
context.arrayElementIndex = 0;
|
||||
context.inArray = true;
|
||||
@ -875,11 +994,15 @@ public:
|
||||
bool EndArray(Context& context, SizeType elementCount) const {
|
||||
context.inArray = false;
|
||||
|
||||
if (elementCount < minItems_)
|
||||
if (elementCount < minItems_) {
|
||||
context.error_handler.TooFewItems(elementCount, minItems_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString());
|
||||
}
|
||||
|
||||
if (elementCount > maxItems_)
|
||||
if (elementCount > maxItems_) {
|
||||
context.error_handler.TooManyItems(elementCount, maxItems_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1100,15 +1223,20 @@ private:
|
||||
}
|
||||
|
||||
bool CheckInt(Context& context, int64_t i) const {
|
||||
if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType))))
|
||||
if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) {
|
||||
DisallowedType(context, GetIntegerString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
|
||||
if (!minimum_.IsNull()) {
|
||||
if (minimum_.IsInt64()) {
|
||||
if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64())
|
||||
if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) {
|
||||
context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString());
|
||||
}
|
||||
}
|
||||
else if (minimum_.IsUint64()) {
|
||||
context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64()
|
||||
}
|
||||
else if (!CheckDoubleMinimum(context, static_cast<double>(i)))
|
||||
@ -1117,8 +1245,10 @@ private:
|
||||
|
||||
if (!maximum_.IsNull()) {
|
||||
if (maximum_.IsInt64()) {
|
||||
if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64())
|
||||
if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) {
|
||||
context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString());
|
||||
}
|
||||
}
|
||||
else if (maximum_.IsUint64()) { }
|
||||
/* do nothing */ // i <= max(int64_t) < maximum_.GetUint64()
|
||||
@ -1128,8 +1258,10 @@ private:
|
||||
|
||||
if (!multipleOf_.IsNull()) {
|
||||
if (multipleOf_.IsUint64()) {
|
||||
if (static_cast<uint64_t>(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0)
|
||||
if (static_cast<uint64_t>(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) {
|
||||
context.error_handler.NotMultipleOf(i, multipleOf_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString());
|
||||
}
|
||||
}
|
||||
else if (!CheckDoubleMultipleOf(context, static_cast<double>(i)))
|
||||
return false;
|
||||
@ -1139,13 +1271,17 @@ private:
|
||||
}
|
||||
|
||||
bool CheckUint(Context& context, uint64_t i) const {
|
||||
if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType))))
|
||||
if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) {
|
||||
DisallowedType(context, GetIntegerString());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString());
|
||||
}
|
||||
|
||||
if (!minimum_.IsNull()) {
|
||||
if (minimum_.IsUint64()) {
|
||||
if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64())
|
||||
if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) {
|
||||
context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString());
|
||||
}
|
||||
}
|
||||
else if (minimum_.IsInt64())
|
||||
/* do nothing */; // i >= 0 > minimum.Getint64()
|
||||
@ -1155,19 +1291,25 @@ private:
|
||||
|
||||
if (!maximum_.IsNull()) {
|
||||
if (maximum_.IsUint64()) {
|
||||
if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64())
|
||||
if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) {
|
||||
context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString());
|
||||
}
|
||||
}
|
||||
else if (maximum_.IsInt64())
|
||||
else if (maximum_.IsInt64()) {
|
||||
context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_
|
||||
}
|
||||
else if (!CheckDoubleMaximum(context, static_cast<double>(i)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!multipleOf_.IsNull()) {
|
||||
if (multipleOf_.IsUint64()) {
|
||||
if (i % multipleOf_.GetUint64() != 0)
|
||||
if (i % multipleOf_.GetUint64() != 0) {
|
||||
context.error_handler.NotMultipleOf(i, multipleOf_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString());
|
||||
}
|
||||
}
|
||||
else if (!CheckDoubleMultipleOf(context, static_cast<double>(i)))
|
||||
return false;
|
||||
@ -1177,14 +1319,18 @@ private:
|
||||
}
|
||||
|
||||
bool CheckDoubleMinimum(Context& context, double d) const {
|
||||
if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble())
|
||||
if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) {
|
||||
context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckDoubleMaximum(Context& context, double d) const {
|
||||
if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble())
|
||||
if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) {
|
||||
context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1192,11 +1338,29 @@ private:
|
||||
double a = std::abs(d), b = std::abs(multipleOf_.GetDouble());
|
||||
double q = std::floor(a / b);
|
||||
double r = a - q * b;
|
||||
if (r > 0.0)
|
||||
if (r > 0.0) {
|
||||
context.error_handler.NotMultipleOf(d, multipleOf_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisallowedType(Context& context, const ValueType& actualType) const {
|
||||
ErrorHandler& eh = context.error_handler;
|
||||
eh.StartDisallowedType();
|
||||
|
||||
if (type_ & (1 << kNullSchemaType)) eh.AddExpectedType(GetNullString());
|
||||
if (type_ & (1 << kBooleanSchemaType)) eh.AddExpectedType(GetBooleanString());
|
||||
if (type_ & (1 << kObjectSchemaType)) eh.AddExpectedType(GetObjectString());
|
||||
if (type_ & (1 << kArraySchemaType)) eh.AddExpectedType(GetArrayString());
|
||||
if (type_ & (1 << kStringSchemaType)) eh.AddExpectedType(GetStringString());
|
||||
|
||||
if (type_ & (1 << kNumberSchemaType)) eh.AddExpectedType(GetNumberString());
|
||||
else if (type_ & (1 << kIntegerSchemaType)) eh.AddExpectedType(GetIntegerString());
|
||||
|
||||
eh.EndDisallowedType(actualType);
|
||||
}
|
||||
|
||||
struct Property {
|
||||
Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {}
|
||||
~Property() { AllocatorType::Free(dependencies); }
|
||||
@ -1221,6 +1385,7 @@ private:
|
||||
};
|
||||
|
||||
AllocatorType* allocator_;
|
||||
SValue uri_;
|
||||
PointerType pointer_;
|
||||
const SchemaType* typeless_;
|
||||
uint64_t* enum_;
|
||||
@ -1331,6 +1496,7 @@ public:
|
||||
typedef typename EncodingType::Ch Ch;
|
||||
typedef internal::Schema<GenericSchemaDocument> SchemaType;
|
||||
typedef GenericPointer<ValueType, Allocator> PointerType;
|
||||
typedef GenericValue<EncodingType, Allocator> URIType;
|
||||
friend class internal::Schema<GenericSchemaDocument>;
|
||||
template <typename, typename, typename>
|
||||
friend class GenericSchemaValidator;
|
||||
@ -1340,10 +1506,13 @@ public:
|
||||
Compile a JSON document into schema document.
|
||||
|
||||
\param document A JSON document as source.
|
||||
\param uri The base URI of this schema document for purposes of violation reporting.
|
||||
\param uriLength Length of \c name, in code points.
|
||||
\param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null.
|
||||
\param allocator An optional allocator instance for allocating memory. Can be null.
|
||||
*/
|
||||
explicit GenericSchemaDocument(const ValueType& document, IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) :
|
||||
explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0,
|
||||
IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) :
|
||||
remoteProvider_(remoteProvider),
|
||||
allocator_(allocator),
|
||||
ownAllocator_(),
|
||||
@ -1355,8 +1524,11 @@ public:
|
||||
if (!allocator_)
|
||||
ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)();
|
||||
|
||||
Ch noUri[1] = {0};
|
||||
uri_.SetString(uri ? uri : noUri, uriLength, *allocator_);
|
||||
|
||||
typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType)));
|
||||
new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), 0);
|
||||
new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_);
|
||||
|
||||
// Generate root schema, it will call CreateSchema() to create sub-schemas,
|
||||
// And call AddRefSchema() if there are $ref.
|
||||
@ -1394,7 +1566,8 @@ public:
|
||||
root_(rhs.root_),
|
||||
typeless_(rhs.typeless_),
|
||||
schemaMap_(std::move(rhs.schemaMap_)),
|
||||
schemaRef_(std::move(rhs.schemaRef_))
|
||||
schemaRef_(std::move(rhs.schemaRef_)),
|
||||
uri_(std::move(rhs.uri_))
|
||||
{
|
||||
rhs.remoteProvider_ = 0;
|
||||
rhs.allocator_ = 0;
|
||||
@ -1416,6 +1589,8 @@ public:
|
||||
RAPIDJSON_DELETE(ownAllocator_);
|
||||
}
|
||||
|
||||
const URIType& GetURI() const { return uri_; }
|
||||
|
||||
//! Get the root schema.
|
||||
const SchemaType& GetRoot() const { return *root_; }
|
||||
|
||||
@ -1546,6 +1721,7 @@ private:
|
||||
SchemaType* typeless_;
|
||||
internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas
|
||||
internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref
|
||||
URIType uri_;
|
||||
};
|
||||
|
||||
//! GenericSchemaDocument using Value type.
|
||||
@ -1574,13 +1750,17 @@ template <
|
||||
typename StateAllocator = CrtAllocator>
|
||||
class GenericSchemaValidator :
|
||||
public internal::ISchemaStateFactory<typename SchemaDocumentType::SchemaType>,
|
||||
public internal::ISchemaValidator
|
||||
public internal::ISchemaValidator,
|
||||
public internal::IValidationErrorHandler<typename SchemaDocumentType::SchemaType>
|
||||
{
|
||||
public:
|
||||
typedef typename SchemaDocumentType::SchemaType SchemaType;
|
||||
typedef typename SchemaDocumentType::PointerType PointerType;
|
||||
typedef typename SchemaType::EncodingType EncodingType;
|
||||
typedef typename SchemaType::SValue SValue;
|
||||
typedef typename EncodingType::Ch Ch;
|
||||
typedef GenericStringRef<Ch> StringRefType;
|
||||
typedef GenericValue<EncodingType, StateAllocator> ValueType;
|
||||
|
||||
//! Constructor without output handler.
|
||||
/*!
|
||||
@ -1602,6 +1782,9 @@ public:
|
||||
schemaStack_(allocator, schemaStackCapacity),
|
||||
documentStack_(allocator, documentStackCapacity),
|
||||
outputHandler_(0),
|
||||
error_(kObjectType),
|
||||
currentError_(),
|
||||
missingDependents_(),
|
||||
valid_(true)
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
, depth_(0)
|
||||
@ -1630,6 +1813,9 @@ public:
|
||||
schemaStack_(allocator, schemaStackCapacity),
|
||||
documentStack_(allocator, documentStackCapacity),
|
||||
outputHandler_(&outputHandler),
|
||||
error_(kObjectType),
|
||||
currentError_(),
|
||||
missingDependents_(),
|
||||
valid_(true)
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
, depth_(0)
|
||||
@ -1648,6 +1834,9 @@ public:
|
||||
while (!schemaStack_.Empty())
|
||||
PopSchema();
|
||||
documentStack_.Clear();
|
||||
error_.SetObject();
|
||||
currentError_.SetNull();
|
||||
missingDependents_.SetNull();
|
||||
valid_ = true;
|
||||
}
|
||||
|
||||
@ -1655,6 +1844,10 @@ public:
|
||||
// Implementation of ISchemaValidator
|
||||
virtual bool IsValid() const { return valid_; }
|
||||
|
||||
//! Gets the error object.
|
||||
ValueType& GetError() { return error_; }
|
||||
const ValueType& GetError() const { return error_; }
|
||||
|
||||
//! Gets the JSON pointer pointed to the invalid schema.
|
||||
PointerType GetInvalidSchemaPointer() const {
|
||||
return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer();
|
||||
@ -1670,6 +1863,188 @@ public:
|
||||
return documentStack_.Empty() ? PointerType() : PointerType(documentStack_.template Bottom<Ch>(), documentStack_.GetSize() / sizeof(Ch));
|
||||
}
|
||||
|
||||
void NotMultipleOf(int64_t actual, const SValue& expected) {
|
||||
AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected);
|
||||
}
|
||||
void NotMultipleOf(uint64_t actual, const SValue& expected) {
|
||||
AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected);
|
||||
}
|
||||
void NotMultipleOf(double actual, const SValue& expected) {
|
||||
AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected);
|
||||
}
|
||||
void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) {
|
||||
AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected,
|
||||
exclusive ? &SchemaType::GetExclusiveMaximumString : 0);
|
||||
}
|
||||
void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) {
|
||||
AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected,
|
||||
exclusive ? &SchemaType::GetExclusiveMaximumString : 0);
|
||||
}
|
||||
void AboveMaximum(double actual, const SValue& expected, bool exclusive) {
|
||||
AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected,
|
||||
exclusive ? &SchemaType::GetExclusiveMaximumString : 0);
|
||||
}
|
||||
void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) {
|
||||
AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected,
|
||||
exclusive ? &SchemaType::GetExclusiveMinimumString : 0);
|
||||
}
|
||||
void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) {
|
||||
AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected,
|
||||
exclusive ? &SchemaType::GetExclusiveMinimumString : 0);
|
||||
}
|
||||
void BelowMinimum(double actual, const SValue& expected, bool exclusive) {
|
||||
AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected,
|
||||
exclusive ? &SchemaType::GetExclusiveMinimumString : 0);
|
||||
}
|
||||
|
||||
void TooLong(const Ch* str, SizeType length, SizeType expected) {
|
||||
AddNumberError(SchemaType::GetMaxLengthString(),
|
||||
ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move());
|
||||
}
|
||||
void TooShort(const Ch* str, SizeType length, SizeType expected) {
|
||||
AddNumberError(SchemaType::GetMinLengthString(),
|
||||
ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move());
|
||||
}
|
||||
void DoesNotMatch(const Ch* str, SizeType length) {
|
||||
currentError_.SetObject();
|
||||
currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
AddCurrentError(SchemaType::GetPatternString());
|
||||
}
|
||||
|
||||
void DisallowedItem(SizeType index) {
|
||||
currentError_.SetObject();
|
||||
currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator());
|
||||
AddCurrentError(SchemaType::GetAdditionalItemsString(), true);
|
||||
}
|
||||
void TooFewItems(SizeType actualCount, SizeType expectedCount) {
|
||||
AddNumberError(SchemaType::GetMinItemsString(),
|
||||
ValueType(actualCount).Move(), SValue(expectedCount).Move());
|
||||
}
|
||||
void TooManyItems(SizeType actualCount, SizeType expectedCount) {
|
||||
AddNumberError(SchemaType::GetMaxItemsString(),
|
||||
ValueType(actualCount).Move(), SValue(expectedCount).Move());
|
||||
}
|
||||
void DuplicateItems(SizeType index1, SizeType index2) {
|
||||
ValueType duplicates(kArrayType);
|
||||
duplicates.PushBack(index1, GetStateAllocator());
|
||||
duplicates.PushBack(index2, GetStateAllocator());
|
||||
currentError_.SetObject();
|
||||
currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator());
|
||||
AddCurrentError(SchemaType::GetUniqueItemsString(), true);
|
||||
}
|
||||
|
||||
void TooManyProperties(SizeType actualCount, SizeType expectedCount) {
|
||||
AddNumberError(SchemaType::GetMaxPropertiesString(),
|
||||
ValueType(actualCount).Move(), SValue(expectedCount).Move());
|
||||
}
|
||||
void TooFewProperties(SizeType actualCount, SizeType expectedCount) {
|
||||
AddNumberError(SchemaType::GetMinPropertiesString(),
|
||||
ValueType(actualCount).Move(), SValue(expectedCount).Move());
|
||||
}
|
||||
void StartMissingProperties() {
|
||||
currentError_.SetArray();
|
||||
}
|
||||
void AddMissingProperty(const SValue& name) {
|
||||
currentError_.PushBack(ValueType(name, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
}
|
||||
bool EndMissingProperties() {
|
||||
if (currentError_.Empty())
|
||||
return false;
|
||||
ValueType error(kObjectType);
|
||||
error.AddMember(GetMissingString(), currentError_, GetStateAllocator());
|
||||
currentError_ = error;
|
||||
AddCurrentError(SchemaType::GetRequiredString());
|
||||
return true;
|
||||
}
|
||||
void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) {
|
||||
for (SizeType i = 0; i < count; ++i)
|
||||
MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError());
|
||||
}
|
||||
void DisallowedProperty(const Ch* name, SizeType length) {
|
||||
currentError_.SetObject();
|
||||
currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
AddCurrentError(SchemaType::GetAdditionalPropertiesString(), true);
|
||||
}
|
||||
|
||||
void StartDependencyErrors() {
|
||||
currentError_.SetObject();
|
||||
}
|
||||
void StartMissingDependentProperties() {
|
||||
missingDependents_.SetArray();
|
||||
}
|
||||
void AddMissingDependentProperty(const SValue& targetName) {
|
||||
missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
}
|
||||
void EndMissingDependentProperties(const SValue& sourceName) {
|
||||
if (!missingDependents_.Empty())
|
||||
currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(),
|
||||
missingDependents_, GetStateAllocator());
|
||||
}
|
||||
void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) {
|
||||
currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(),
|
||||
static_cast<GenericSchemaValidator*>(subvalidator)->GetError(), GetStateAllocator());
|
||||
}
|
||||
bool EndDependencyErrors() {
|
||||
if (currentError_.ObjectEmpty())
|
||||
return false;
|
||||
ValueType error(kObjectType);
|
||||
error.AddMember(GetErrorsString(), currentError_, GetStateAllocator());
|
||||
currentError_ = error;
|
||||
AddCurrentError(SchemaType::GetDependenciesString());
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisallowedValue() {
|
||||
currentError_.SetObject();
|
||||
AddCurrentError(SchemaType::GetEnumString());
|
||||
}
|
||||
void StartDisallowedType() {
|
||||
currentError_.SetArray();
|
||||
}
|
||||
void AddExpectedType(const typename SchemaType::ValueType& expectedType) {
|
||||
currentError_.PushBack(ValueType(expectedType, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
}
|
||||
void EndDisallowedType(const typename SchemaType::ValueType& actualType) {
|
||||
ValueType error(kObjectType);
|
||||
error.AddMember(GetExpectedString(), currentError_, GetStateAllocator());
|
||||
error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
currentError_ = error;
|
||||
AddCurrentError(SchemaType::GetTypeString());
|
||||
}
|
||||
void NotAllOf(ISchemaValidator** subvalidators, SizeType count) {
|
||||
for (SizeType i = 0; i < count; ++i) {
|
||||
MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError());
|
||||
}
|
||||
}
|
||||
void NoneOf(ISchemaValidator** subvalidators, SizeType count) {
|
||||
AddErrorArray(SchemaType::GetAnyOfString(), subvalidators, count);
|
||||
}
|
||||
void NotOneOf(ISchemaValidator** subvalidators, SizeType count) {
|
||||
AddErrorArray(SchemaType::GetOneOfString(), subvalidators, count);
|
||||
}
|
||||
void Disallowed() {
|
||||
currentError_.SetObject();
|
||||
AddCurrentError(SchemaType::GetNotString());
|
||||
}
|
||||
|
||||
#define RAPIDJSON_STRING_(name, ...) \
|
||||
static const StringRefType& Get##name##String() {\
|
||||
static const Ch s[] = { __VA_ARGS__, '\0' };\
|
||||
static const StringRefType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1)); \
|
||||
return v;\
|
||||
}
|
||||
|
||||
RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f')
|
||||
RAPIDJSON_STRING_(SchemaRef, 's', 'c', 'h', 'e', 'm', 'a', 'R', 'e', 'f')
|
||||
RAPIDJSON_STRING_(Expected, 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd')
|
||||
RAPIDJSON_STRING_(Actual, 'a', 'c', 't', 'u', 'a', 'l')
|
||||
RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd')
|
||||
RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g')
|
||||
RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's')
|
||||
RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's')
|
||||
|
||||
#undef RAPIDJSON_STRING_
|
||||
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \
|
||||
RAPIDJSON_MULTILINEMACRO_BEGIN\
|
||||
@ -1708,7 +2083,7 @@ RAPIDJSON_MULTILINEMACRO_END
|
||||
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\
|
||||
RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2)
|
||||
|
||||
bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext() ), ( )); }
|
||||
bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext()), ( )); }
|
||||
bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); }
|
||||
bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); }
|
||||
bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); }
|
||||
@ -1761,7 +2136,7 @@ RAPIDJSON_MULTILINEMACRO_END
|
||||
|
||||
// Implementation of ISchemaStateFactory<SchemaType>
|
||||
virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) {
|
||||
return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root,
|
||||
return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom<char>(), documentStack_.GetSize(),
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
depth_ + 1,
|
||||
#endif
|
||||
@ -1804,6 +2179,7 @@ private:
|
||||
GenericSchemaValidator(
|
||||
const SchemaDocumentType& schemaDocument,
|
||||
const SchemaType& root,
|
||||
const char* basePath, size_t basePathSize,
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
unsigned depth,
|
||||
#endif
|
||||
@ -1818,11 +2194,16 @@ private:
|
||||
schemaStack_(allocator, schemaStackCapacity),
|
||||
documentStack_(allocator, documentStackCapacity),
|
||||
outputHandler_(0),
|
||||
error_(kObjectType),
|
||||
currentError_(),
|
||||
missingDependents_(),
|
||||
valid_(true)
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
, depth_(depth)
|
||||
#endif
|
||||
{
|
||||
if (basePath && basePathSize)
|
||||
memcpy(documentStack_.template Push<char>(basePathSize), basePath, basePathSize);
|
||||
}
|
||||
|
||||
StateAllocator& GetStateAllocator() {
|
||||
@ -1886,8 +2267,10 @@ private:
|
||||
if (!a)
|
||||
CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType);
|
||||
for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr)
|
||||
if (itr->GetUint64() == h)
|
||||
if (itr->GetUint64() == h) {
|
||||
DuplicateItems(static_cast<SizeType>(itr - a->Begin()), a->Size());
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString());
|
||||
}
|
||||
a->PushBack(h, GetStateAllocator());
|
||||
}
|
||||
}
|
||||
@ -1916,7 +2299,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push<Context>()) Context(*this, &schema); }
|
||||
RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push<Context>()) Context(*this, *this, &schema); }
|
||||
|
||||
RAPIDJSON_FORCEINLINE void PopSchema() {
|
||||
Context* c = schemaStack_.template Pop<Context>(1);
|
||||
@ -1927,6 +2310,70 @@ private:
|
||||
c->~Context();
|
||||
}
|
||||
|
||||
void AddErrorLocation(ValueType& result, bool parent) {
|
||||
GenericStringBuffer<EncodingType> sb;
|
||||
PointerType instancePointer = GetInvalidDocumentPointer();
|
||||
((parent && instancePointer.GetTokenCount() > 0)
|
||||
? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1)
|
||||
: instancePointer).StringifyUriFragment(sb);
|
||||
ValueType instanceRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)),
|
||||
GetStateAllocator());
|
||||
result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator());
|
||||
sb.Clear();
|
||||
memcpy(sb.Push(CurrentSchema().GetURI().GetStringLength()),
|
||||
CurrentSchema().GetURI().GetString(),
|
||||
CurrentSchema().GetURI().GetStringLength() * sizeof(Ch));
|
||||
GetInvalidSchemaPointer().StringifyUriFragment(sb);
|
||||
ValueType schemaRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)),
|
||||
GetStateAllocator());
|
||||
result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator());
|
||||
}
|
||||
|
||||
void AddError(ValueType& keyword, ValueType& error) {
|
||||
typename ValueType::MemberIterator member = error_.FindMember(keyword);
|
||||
if (member == error_.MemberEnd())
|
||||
error_.AddMember(keyword, error, GetStateAllocator());
|
||||
else {
|
||||
if (member->value.IsObject()) {
|
||||
ValueType errors(kArrayType);
|
||||
errors.PushBack(member->value, GetStateAllocator());
|
||||
member->value = errors;
|
||||
}
|
||||
member->value.PushBack(error, GetStateAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
void AddCurrentError(const typename SchemaType::ValueType& keyword, bool parent = false) {
|
||||
AddErrorLocation(currentError_, parent);
|
||||
AddError(ValueType(keyword, GetStateAllocator(), false).Move(), currentError_);
|
||||
}
|
||||
|
||||
void MergeError(ValueType& other) {
|
||||
for (typename ValueType::MemberIterator it = other.MemberBegin(), end = other.MemberEnd(); it != end; ++it) {
|
||||
AddError(it->name, it->value);
|
||||
}
|
||||
}
|
||||
|
||||
void AddNumberError(const typename SchemaType::ValueType& keyword, ValueType& actual, const SValue& expected,
|
||||
const typename SchemaType::ValueType& (*exclusive)() = 0) {
|
||||
currentError_.SetObject();
|
||||
currentError_.AddMember(GetActualString(), actual, GetStateAllocator());
|
||||
currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator());
|
||||
if (exclusive)
|
||||
currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator());
|
||||
AddCurrentError(keyword);
|
||||
}
|
||||
|
||||
void AddErrorArray(const typename SchemaType::ValueType& keyword,
|
||||
ISchemaValidator** subvalidators, SizeType count) {
|
||||
ValueType errors(kArrayType);
|
||||
for (SizeType i = 0; i < count; ++i)
|
||||
errors.PushBack(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError(), GetStateAllocator());
|
||||
currentError_.SetObject();
|
||||
currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator());
|
||||
AddCurrentError(keyword);
|
||||
}
|
||||
|
||||
const SchemaType& CurrentSchema() const { return *schemaStack_.template Top<Context>()->schema; }
|
||||
Context& CurrentContext() { return *schemaStack_.template Top<Context>(); }
|
||||
const Context& CurrentContext() const { return *schemaStack_.template Top<Context>(); }
|
||||
@ -1940,6 +2387,9 @@ private:
|
||||
internal::Stack<StateAllocator> schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *)
|
||||
internal::Stack<StateAllocator> documentStack_; //!< stack to store the current path of validating document (Ch)
|
||||
OutputHandler* outputHandler_;
|
||||
ValueType error_;
|
||||
ValueType currentError_;
|
||||
ValueType missingDependents_;
|
||||
bool valid_;
|
||||
#if RAPIDJSON_SCHEMA_VERBOSE
|
||||
unsigned depth_;
|
||||
@ -1971,13 +2421,14 @@ class SchemaValidatingReader {
|
||||
public:
|
||||
typedef typename SchemaDocumentType::PointerType PointerType;
|
||||
typedef typename InputStream::Ch Ch;
|
||||
typedef GenericValue<SourceEncoding, StackAllocator> ValueType;
|
||||
|
||||
//! Constructor
|
||||
/*!
|
||||
\param is Input stream.
|
||||
\param sd Schema document.
|
||||
*/
|
||||
SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), isValid_(true) {}
|
||||
SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), error_(kObjectType), isValid_(true) {}
|
||||
|
||||
template <typename Handler>
|
||||
bool operator()(Handler& handler) {
|
||||
@ -1990,11 +2441,13 @@ public:
|
||||
invalidSchemaPointer_ = PointerType();
|
||||
invalidSchemaKeyword_ = 0;
|
||||
invalidDocumentPointer_ = PointerType();
|
||||
error_.SetObject();
|
||||
}
|
||||
else {
|
||||
invalidSchemaPointer_ = validator.GetInvalidSchemaPointer();
|
||||
invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword();
|
||||
invalidDocumentPointer_ = validator.GetInvalidDocumentPointer();
|
||||
error_.CopyFrom(validator.GetError(), allocator_);
|
||||
}
|
||||
|
||||
return parseResult_;
|
||||
@ -2005,6 +2458,7 @@ public:
|
||||
const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; }
|
||||
const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; }
|
||||
const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; }
|
||||
const ValueType& GetError() const { return error_; }
|
||||
|
||||
private:
|
||||
InputStream& is_;
|
||||
@ -2014,6 +2468,8 @@ private:
|
||||
PointerType invalidSchemaPointer_;
|
||||
const Ch* invalidSchemaKeyword_;
|
||||
PointerType invalidDocumentPointer_;
|
||||
StackAllocator allocator_;
|
||||
ValueType error_;
|
||||
bool isValid_;
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user