Add Ref in schema

This commit is contained in:
miloyip 2015-05-09 11:46:45 +08:00
parent 1e4a3818ed
commit f0c3fa84fc
2 changed files with 132 additions and 13 deletions

View File

@ -116,6 +116,7 @@ struct SchemaValidationContext {
valueSchema(), valueSchema(),
patternPropertiesSchemas(), patternPropertiesSchemas(),
notValidator(), notValidator(),
refValidator(),
patternPropertiesSchemaCount(), patternPropertiesSchemaCount(),
valuePatternValidatorType(kPatternValidatorOnly), valuePatternValidatorType(kPatternValidatorOnly),
objectDependencies(), objectDependencies(),
@ -125,6 +126,7 @@ struct SchemaValidationContext {
~SchemaValidationContext() { ~SchemaValidationContext() {
delete notValidator; delete notValidator;
delete refValidator;
delete[] patternPropertiesSchemas; delete[] patternPropertiesSchemas;
delete[] objectDependencies; delete[] objectDependencies;
} }
@ -139,6 +141,7 @@ struct SchemaValidationContext {
SchemaValidatorArray patternPropertiesValidators; SchemaValidatorArray patternPropertiesValidators;
const SchemaType** patternPropertiesSchemas; const SchemaType** patternPropertiesSchemas;
ISchemaValidator* notValidator; ISchemaValidator* notValidator;
ISchemaValidator* refValidator;
SizeType patternPropertiesSchemaCount; SizeType patternPropertiesSchemaCount;
PatternValidatorType valuePatternValidatorType; PatternValidatorType valuePatternValidatorType;
PatternValidatorType objectPatternValidatorType; PatternValidatorType objectPatternValidatorType;
@ -158,10 +161,12 @@ public:
typedef Schema<Encoding, Allocator> SchemaType; typedef Schema<Encoding, Allocator> SchemaType;
typedef GenericValue<Encoding, Allocator> ValueType; typedef GenericValue<Encoding, Allocator> ValueType;
typedef GenericPointer<ValueType> PointerType; typedef GenericPointer<ValueType> PointerType;
friend class GenericSchemaDocument<Encoding, Allocator>;
template <typename ValueType> template <typename ValueType>
Schema(SchemaDocumentType* document, const PointerType& p, const ValueType& value) : Schema(SchemaDocumentType* document, const PointerType& p, const ValueType& value) :
not_(), not_(),
ref_(),
type_((1 << kTotalSchemaType) - 1), // typeless type_((1 << kTotalSchemaType) - 1), // typeless
properties_(), properties_(),
additionalPropertiesSchema_(), additionalPropertiesSchema_(),
@ -217,6 +222,9 @@ public:
if (const ValueType* v = GetMember(value, "not")) if (const ValueType* v = GetMember(value, "not"))
not_ = document->CreateSchema(p.Append("not"), *v); not_ = document->CreateSchema(p.Append("not"), *v);
if (const ValueType* v = GetMember(value, "$ref"))
document->AddRefSchema(this, *v);
// Object // Object
const ValueType* properties = GetMember(value, "properties"); const ValueType* properties = GetMember(value, "properties");
@ -456,7 +464,10 @@ public:
return false; return false;
} }
return !not_ || !context.notValidator->IsValid(); if (not_ && context.notValidator->IsValid())
return false;
return !ref_ || context.refValidator->IsValid();
} }
bool Null(Context& context) const { bool Null(Context& context) const {
@ -741,6 +752,8 @@ private:
if (oneOf_.schemas) CreateSchemaValidators(context, context.oneOfValidators, oneOf_); if (oneOf_.schemas) CreateSchemaValidators(context, context.oneOfValidators, oneOf_);
if (not_ && !context.notValidator) if (not_ && !context.notValidator)
context.notValidator = context.factory->CreateSchemaValidator(*not_); context.notValidator = context.factory->CreateSchemaValidator(*not_);
if (ref_ && !context.refValidator)
context.refValidator = context.factory->CreateSchemaValidator(*ref_);
if (hasSchemaDependencies_ && !context.dependencyValidators.validators) { if (hasSchemaDependencies_ && !context.dependencyValidators.validators) {
context.dependencyValidators.validators = new ISchemaValidator*[propertyCount_]; context.dependencyValidators.validators = new ISchemaValidator*[propertyCount_];
@ -817,6 +830,7 @@ private:
SchemaArrayType anyOf_; SchemaArrayType anyOf_;
SchemaArrayType oneOf_; SchemaArrayType oneOf_;
SchemaType* not_; SchemaType* not_;
SchemaType* ref_;
unsigned type_; // bitmask of kSchemaType unsigned type_; // bitmask of kSchemaType
Property* properties_; Property* properties_;
@ -858,18 +872,44 @@ public:
friend class Schema<Encoding, Allocator>; friend class Schema<Encoding, Allocator>;
template <typename DocumentType> template <typename DocumentType>
GenericSchemaDocument(const DocumentType& document, Allocator* allocator = 0) : root_(), schemas_(), schemaCount_(), schemaMap_(allocator, kInitialSchemaMapSize) { GenericSchemaDocument(const DocumentType& document, Allocator* allocator = 0) : root_(), schemas_(), schemaCount_(), schemaMap_(allocator, kInitialSchemaMapSize), schemaRef_(allocator, kInitialSchemaRefSize) {
typedef typename DocumentType::ValueType ValueType; typedef typename DocumentType::ValueType ValueType;
typedef SchemaEntry<ValueType> SchemaEntryType;
typedef GenericPointer<ValueType> PointerType;
root_ = CreateSchema(GenericPointer<ValueType>(), static_cast<const ValueType&>(document)); // Generate root schema, it will call CreateSchema() to create sub-schemas,
// And call AddRefSchema() if there are $ref.
root_ = CreateSchema(PointerType(), static_cast<const ValueType&>(document));
// Copy to schemas and destroy the map // Resolve $ref
while (!schemaRef_.Empty()) {
SchemaEntryType* refEntry = schemaRef_.template Pop<SchemaEntryType>(1);
PointerType p = refEntry->pointer; // Due to re-entrance,
SchemaType* source = refEntry->schema; // backup the entry first,
refEntry->~SchemaEntryType(); // and then destruct it.
bool resolved = false;
for (SchemaEntryType* target = schemaMap_.template Bottom<SchemaEntryType>(); target <= schemaMap_.template Top<SchemaEntryType>(); ++target)
if (p == target->pointer) {
source->ref_ = target->schema;
resolved = true;
break;
}
// If not reesolved to existing schemas, try to create schema from the pointer
if (!resolved) {
if (const ValueType* v = p.Get(document))
source->ref_ = CreateSchema(p, *v); // cause re-entrance (modifying schemaRef_)
}
}
// Copy to schemas_ and destroy schemaMap_ entries.
schemas_ = new SchemaType*[schemaCount_]; schemas_ = new SchemaType*[schemaCount_];
size_t i = schemaCount_; size_t i = schemaCount_;
while (!schemaMap_.Empty()) { while (!schemaMap_.Empty()) {
SchemaEntry<ValueType>* e = schemaMap_.template Pop<SchemaEntry<ValueType> > (1); SchemaEntryType* e = schemaMap_.template Pop<SchemaEntryType>(1);
schemas_[--i] = e->schema; schemas_[--i] = e->schema;
e->~SchemaEntry<ValueType>(); e->~SchemaEntryType();
} }
} }
@ -891,18 +931,30 @@ private:
template <typename ValueType> template <typename ValueType>
SchemaType* CreateSchema(const GenericPointer<ValueType>& pointer, const ValueType& v) { SchemaType* CreateSchema(const GenericPointer<ValueType>& pointer, const ValueType& v) {
RAPIDJSON_ASSERT(pointer.IsValid());
SchemaType* schema = new SchemaType(this, pointer, v); SchemaType* schema = new SchemaType(this, pointer, v);
new (schemaMap_.template Push<SchemaEntry<ValueType> >()) SchemaEntry<ValueType>(pointer, schema); new (schemaMap_.template Push<SchemaEntry<ValueType> >()) SchemaEntry<ValueType>(pointer, schema);
schemaCount_++; schemaCount_++;
return schema; return schema;
} }
static const size_t kInitialSchemaMapSize = 1024; template <typename ValueType>
void AddRefSchema(SchemaType* schema, const ValueType& v) {
if (v.IsString()) {
GenericPointer<ValueType> pointer(v.GetString(), v.GetStringLength());
if (pointer.IsValid())
new (schemaRef_.template Push<SchemaEntry<ValueType> >()) SchemaEntry<ValueType>(pointer, schema);
}
}
SchemaType* root_; static const size_t kInitialSchemaMapSize = 1024;
SchemaType** schemas_; static const size_t kInitialSchemaRefSize = 1024;
size_t schemaCount_;
internal::Stack<Allocator> schemaMap_; // Stores SchemaEntry<ValueType> SchemaType* root_; //!< Root schema.
SchemaType** schemas_; //!< ALl schemas are owned by SchemaDocument
size_t schemaCount_; //!< Number of schemas owned
internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas
internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref
}; };
typedef GenericSchemaDocument<UTF8<> > SchemaDocument; typedef GenericSchemaDocument<UTF8<> > SchemaDocument;
@ -974,6 +1026,8 @@ public:
static_cast<GenericSchemaValidator*>(context->oneOfValidators.validators[i_])->method arg2;\ static_cast<GenericSchemaValidator*>(context->oneOfValidators.validators[i_])->method arg2;\
if (context->notValidator)\ if (context->notValidator)\
static_cast<GenericSchemaValidator*>(context->notValidator)->method arg2;\ static_cast<GenericSchemaValidator*>(context->notValidator)->method arg2;\
if (context->refValidator)\
static_cast<GenericSchemaValidator*>(context->refValidator)->method arg2;\
if (context->dependencyValidators.validators)\ if (context->dependencyValidators.validators)\
for (SizeType i_ = 0; i_ < context->dependencyValidators.count; i_++)\ for (SizeType i_ = 0; i_ < context->dependencyValidators.count; i_++)\
if (context->dependencyValidators.validators[i_])\ if (context->dependencyValidators.validators[i_])\

View File

@ -129,6 +129,71 @@ TEST(SchemaValidator, Not) {
VALIDATE(s, "\"I am a string\"", false); VALIDATE(s, "\"I am a string\"", false);
} }
TEST(SchemaValidator, Ref) {
Document sd;
sd.Parse(
"{"
" \"$schema\": \"http://json-schema.org/draft-04/schema#\","
""
" \"definitions\": {"
" \"address\": {"
" \"type\": \"object\","
" \"properties\": {"
" \"street_address\": { \"type\": \"string\" },"
" \"city\": { \"type\": \"string\" },"
" \"state\": { \"type\": \"string\" }"
" },"
" \"required\": [\"street_address\", \"city\", \"state\"]"
" }"
" },"
" \"type\": \"object\","
" \"properties\": {"
" \"billing_address\": { \"$ref\": \"#/definitions/address\" },"
" \"shipping_address\": { \"$ref\": \"#/definitions/address\" }"
" }"
"}");
SchemaDocument s(sd);
VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"}, \"billing_address\": {\"street_address\": \"1st Street SE\", \"city\": \"Washington\", \"state\": \"DC\"} }", true);
}
TEST(SchemaValidator, Ref_AllOf) {
Document sd;
sd.Parse(
"{"
" \"$schema\": \"http://json-schema.org/draft-04/schema#\","
""
" \"definitions\": {"
" \"address\": {"
" \"type\": \"object\","
" \"properties\": {"
" \"street_address\": { \"type\": \"string\" },"
" \"city\": { \"type\": \"string\" },"
" \"state\": { \"type\": \"string\" }"
" },"
" \"required\": [\"street_address\", \"city\", \"state\"]"
" }"
" },"
" \"type\": \"object\","
" \"properties\": {"
" \"billing_address\": { \"$ref\": \"#/definitions/address\" },"
" \"shipping_address\": {"
" \"allOf\": ["
" { \"$ref\": \"#/definitions/address\" },"
" { \"properties\":"
" { \"type\": { \"enum\": [ \"residential\", \"business\" ] } },"
" \"required\": [\"type\"]"
" }"
" ]"
" }"
" }"
"}");
SchemaDocument s(sd);
VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"} }", false);
VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\", \"type\": \"business\"} }", true);
}
TEST(SchemaValidator, String) { TEST(SchemaValidator, String) {
Document sd; Document sd;
sd.Parse("{\"type\":\"string\"}"); sd.Parse("{\"type\":\"string\"}");
@ -669,7 +734,7 @@ TEST(SchemaValidator, TestSuite) {
"additionalProperties.json", "additionalProperties.json",
"allOf.json", "allOf.json",
"anyOf.json", "anyOf.json",
//"definitions.json", "definitions.json",
"dependencies.json", "dependencies.json",
"enum.json", "enum.json",
"items.json", "items.json",
@ -687,7 +752,7 @@ TEST(SchemaValidator, TestSuite) {
"pattern.json", "pattern.json",
"patternProperties.json", "patternProperties.json",
"properties.json", "properties.json",
//"ref.json", "ref.json",
//"refRemote.json", //"refRemote.json",
"required.json", "required.json",
"type.json", "type.json",