From f0c3fa84fc3fa4a0f6d8444143c061db48e1debd Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 9 May 2015 11:46:45 +0800 Subject: [PATCH] Add Ref in schema --- include/rapidjson/schema.h | 76 ++++++++++++++++++++++++++++++------ test/unittest/schematest.cpp | 69 +++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 13 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 5b56b45..cfdcb62 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -116,6 +116,7 @@ struct SchemaValidationContext { valueSchema(), patternPropertiesSchemas(), notValidator(), + refValidator(), patternPropertiesSchemaCount(), valuePatternValidatorType(kPatternValidatorOnly), objectDependencies(), @@ -125,6 +126,7 @@ struct SchemaValidationContext { ~SchemaValidationContext() { delete notValidator; + delete refValidator; delete[] patternPropertiesSchemas; delete[] objectDependencies; } @@ -139,6 +141,7 @@ struct SchemaValidationContext { SchemaValidatorArray patternPropertiesValidators; const SchemaType** patternPropertiesSchemas; ISchemaValidator* notValidator; + ISchemaValidator* refValidator; SizeType patternPropertiesSchemaCount; PatternValidatorType valuePatternValidatorType; PatternValidatorType objectPatternValidatorType; @@ -158,10 +161,12 @@ public: typedef Schema SchemaType; typedef GenericValue ValueType; typedef GenericPointer PointerType; + friend class GenericSchemaDocument; template Schema(SchemaDocumentType* document, const PointerType& p, const ValueType& value) : not_(), + ref_(), type_((1 << kTotalSchemaType) - 1), // typeless properties_(), additionalPropertiesSchema_(), @@ -217,6 +222,9 @@ public: if (const ValueType* v = GetMember(value, "not")) not_ = document->CreateSchema(p.Append("not"), *v); + if (const ValueType* v = GetMember(value, "$ref")) + document->AddRefSchema(this, *v); + // Object const ValueType* properties = GetMember(value, "properties"); @@ -456,7 +464,10 @@ public: return false; } - return !not_ || !context.notValidator->IsValid(); + if (not_ && context.notValidator->IsValid()) + return false; + + return !ref_ || context.refValidator->IsValid(); } bool Null(Context& context) const { @@ -741,6 +752,8 @@ private: if (oneOf_.schemas) CreateSchemaValidators(context, context.oneOfValidators, oneOf_); if (not_ && !context.notValidator) context.notValidator = context.factory->CreateSchemaValidator(*not_); + if (ref_ && !context.refValidator) + context.refValidator = context.factory->CreateSchemaValidator(*ref_); if (hasSchemaDependencies_ && !context.dependencyValidators.validators) { context.dependencyValidators.validators = new ISchemaValidator*[propertyCount_]; @@ -817,6 +830,7 @@ private: SchemaArrayType anyOf_; SchemaArrayType oneOf_; SchemaType* not_; + SchemaType* ref_; unsigned type_; // bitmask of kSchemaType Property* properties_; @@ -858,18 +872,44 @@ public: friend class Schema; template - 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 SchemaEntry SchemaEntryType; + typedef GenericPointer PointerType; - root_ = CreateSchema(GenericPointer(), static_cast(document)); + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + root_ = CreateSchema(PointerType(), static_cast(document)); - // Copy to schemas and destroy the map + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaEntryType* refEntry = schemaRef_.template Pop(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(); target <= schemaMap_.template Top(); ++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_]; size_t i = schemaCount_; while (!schemaMap_.Empty()) { - SchemaEntry* e = schemaMap_.template Pop > (1); + SchemaEntryType* e = schemaMap_.template Pop(1); schemas_[--i] = e->schema; - e->~SchemaEntry(); + e->~SchemaEntryType(); } } @@ -891,18 +931,30 @@ private: template SchemaType* CreateSchema(const GenericPointer& pointer, const ValueType& v) { + RAPIDJSON_ASSERT(pointer.IsValid()); SchemaType* schema = new SchemaType(this, pointer, v); new (schemaMap_.template Push >()) SchemaEntry(pointer, schema); schemaCount_++; return schema; } - static const size_t kInitialSchemaMapSize = 1024; + template + void AddRefSchema(SchemaType* schema, const ValueType& v) { + if (v.IsString()) { + GenericPointer pointer(v.GetString(), v.GetStringLength()); + if (pointer.IsValid()) + new (schemaRef_.template Push >()) SchemaEntry(pointer, schema); + } + } - SchemaType* root_; - SchemaType** schemas_; - size_t schemaCount_; - internal::Stack schemaMap_; // Stores SchemaEntry + static const size_t kInitialSchemaMapSize = 1024; + static const size_t kInitialSchemaRefSize = 1024; + + SchemaType* root_; //!< Root schema. + SchemaType** schemas_; //!< ALl schemas are owned by SchemaDocument + size_t schemaCount_; //!< Number of schemas owned + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref }; typedef GenericSchemaDocument > SchemaDocument; @@ -974,6 +1026,8 @@ public: static_cast(context->oneOfValidators.validators[i_])->method arg2;\ if (context->notValidator)\ static_cast(context->notValidator)->method arg2;\ + if (context->refValidator)\ + static_cast(context->refValidator)->method arg2;\ if (context->dependencyValidators.validators)\ for (SizeType i_ = 0; i_ < context->dependencyValidators.count; i_++)\ if (context->dependencyValidators.validators[i_])\ diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 7c93e21..3371c1d 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -129,6 +129,71 @@ TEST(SchemaValidator, Not) { 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) { Document sd; sd.Parse("{\"type\":\"string\"}"); @@ -669,7 +734,7 @@ TEST(SchemaValidator, TestSuite) { "additionalProperties.json", "allOf.json", "anyOf.json", - //"definitions.json", + "definitions.json", "dependencies.json", "enum.json", "items.json", @@ -687,7 +752,7 @@ TEST(SchemaValidator, TestSuite) { "pattern.json", "patternProperties.json", "properties.json", - //"ref.json", + "ref.json", //"refRemote.json", "required.json", "type.json",