diff options
author | joaodasilva@chromium.org <joaodasilva@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-16 10:51:26 +0000 |
---|---|---|
committer | joaodasilva@chromium.org <joaodasilva@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-16 10:51:26 +0000 |
commit | 3a597ccf9f20e7c97bf9884e106c806266d3f074 (patch) | |
tree | 3245ab1254f285497cd467b9e88c109c278886d4 /components | |
parent | 79286f1279a80699b84ab1c326c97a4bd1982780 (diff) | |
download | chromium_src-3a597ccf9f20e7c97bf9884e106c806266d3f074.zip chromium_src-3a597ccf9f20e7c97bf9884e106c806266d3f074.tar.gz chromium_src-3a597ccf9f20e7c97bf9884e106c806266d3f074.tar.bz2 |
Added new policy Schema classes.
This implementation obsoletes PolicySchema and its existing users will be refactored.
This implementation has the advantage of allowing compile-time static data to be generated and quickly loaded at runtime. That will allow building the Chrome policies schema without impact on startup.
BUG=270667
TBR=joi@chromium.org
Review URL: https://chromiumcodereview.appspot.com/23851022
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@223326 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r-- | components/components_tests.gypi | 1 | ||||
-rw-r--r-- | components/policy.gypi | 3 | ||||
-rw-r--r-- | components/policy/core/common/schema.cc | 276 | ||||
-rw-r--r-- | components/policy/core/common/schema.h | 150 | ||||
-rw-r--r-- | components/policy/core/common/schema_internal.h | 45 | ||||
-rw-r--r-- | components/policy/core/common/schema_unittest.cc | 338 |
6 files changed, 813 insertions, 0 deletions
diff --git a/components/components_tests.gypi b/components/components_tests.gypi index 910d2e6..312960b0 100644 --- a/components/components_tests.gypi +++ b/components/components_tests.gypi @@ -112,6 +112,7 @@ ['configuration_policy==1', { 'sources': [ 'policy/core/common/policy_schema_unittest.cc', + 'policy/core/common/schema_unittest.cc', ], }], ], diff --git a/components/policy.gypi b/components/policy.gypi index 5295ba4..acd6ccf 100644 --- a/components/policy.gypi +++ b/components/policy.gypi @@ -22,6 +22,9 @@ 'sources': [ 'policy/core/common/policy_schema.cc', 'policy/core/common/policy_schema.h', + 'policy/core/common/schema.cc', + 'policy/core/common/schema.h', + 'policy/core/common/schema_internal.h', 'policy/policy_export.h', ], }, { # configuration_policy==0 diff --git a/components/policy/core/common/schema.cc b/components/policy/core/common/schema.cc new file mode 100644 index 0000000..cbfde46 --- /dev/null +++ b/components/policy/core/common/schema.cc @@ -0,0 +1,276 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/schema.h" + +#include <algorithm> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "components/json_schema/json_schema_constants.h" +#include "components/json_schema/json_schema_validator.h" +#include "components/policy/core/common/schema_internal.h" + +namespace policy { + +namespace { + +bool SchemaTypeToValueType(const std::string& type_string, + base::Value::Type* type) { + // Note: "any" is not an accepted type. + static const struct { + const char* schema_type; + base::Value::Type value_type; + } kSchemaToValueTypeMap[] = { + { json_schema_constants::kArray, base::Value::TYPE_LIST }, + { json_schema_constants::kBoolean, base::Value::TYPE_BOOLEAN }, + { json_schema_constants::kInteger, base::Value::TYPE_INTEGER }, + { json_schema_constants::kNull, base::Value::TYPE_NULL }, + { json_schema_constants::kNumber, base::Value::TYPE_DOUBLE }, + { json_schema_constants::kObject, base::Value::TYPE_DICTIONARY }, + { json_schema_constants::kString, base::Value::TYPE_STRING }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSchemaToValueTypeMap); ++i) { + if (kSchemaToValueTypeMap[i].schema_type == type_string) { + *type = kSchemaToValueTypeMap[i].value_type; + return true; + } + } + return false; +} + +} // namespace + +Schema::Iterator::Iterator(const internal::PropertiesNode* properties) + : it_(properties->begin), + end_(properties->end) {} + +Schema::Iterator::Iterator(const Iterator& iterator) + : it_(iterator.it_), + end_(iterator.end_) {} + +Schema::Iterator::~Iterator() {} + +Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) { + it_ = iterator.it_; + end_ = iterator.end_; + return *this; +} + +bool Schema::Iterator::IsAtEnd() const { + return it_ == end_; +} + +void Schema::Iterator::Advance() { + ++it_; +} + +const char* Schema::Iterator::key() const { + return it_->key; +} + +Schema Schema::Iterator::schema() const { + return Schema(it_->schema); +} + +Schema::Schema(const internal::SchemaNode* schema) : schema_(schema) {} + +Schema::Schema(const Schema& schema) : schema_(schema.schema_) {} + +Schema& Schema::operator=(const Schema& schema) { + schema_ = schema.schema_; + return *this; +} + +base::Value::Type Schema::type() const { + CHECK(valid()); + return schema_->type; +} + +Schema::Iterator Schema::GetPropertiesIterator() const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); + return Iterator( + static_cast<const internal::PropertiesNode*>(schema_->extra)); +} + +namespace { + +bool CompareKeys(const internal::PropertyNode& node, const std::string& key) { + return node.key < key; +} + +} // namespace + +Schema Schema::GetKnownProperty(const std::string& key) const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); + const internal::PropertiesNode* properties_node = + static_cast<const internal::PropertiesNode*>(schema_->extra); + const internal::PropertyNode* it = std::lower_bound( + properties_node->begin, properties_node->end, key, CompareKeys); + if (it != properties_node->end && it->key == key) + return Schema(it->schema); + return Schema(NULL); +} + +Schema Schema::GetAdditionalProperties() const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_DICTIONARY, type()); + return Schema( + static_cast<const internal::PropertiesNode*>(schema_->extra)->additional); +} + +Schema Schema::GetProperty(const std::string& key) const { + Schema schema = GetKnownProperty(key); + return schema.valid() ? schema : GetAdditionalProperties(); +} + +Schema Schema::GetItems() const { + CHECK(valid()); + CHECK_EQ(base::Value::TYPE_LIST, type()); + return Schema(static_cast<const internal::SchemaNode*>(schema_->extra)); +} + +SchemaOwner::SchemaOwner(const internal::SchemaNode* root) : root_(root) {} + +SchemaOwner::~SchemaOwner() { + for (size_t i = 0; i < property_nodes_.size(); ++i) + delete[] property_nodes_[i]; +} + +// static +scoped_ptr<SchemaOwner> SchemaOwner::Wrap(const internal::SchemaNode* schema) { + return scoped_ptr<SchemaOwner>(new SchemaOwner(schema)); +} + +// static +scoped_ptr<SchemaOwner> SchemaOwner::Parse(const std::string& content, + std::string* error) { + // Validate as a generic JSON schema. + scoped_ptr<base::DictionaryValue> dict = + JSONSchemaValidator::IsValidSchema(content, error); + if (!dict) + return scoped_ptr<SchemaOwner>(); + + // Validate the main type. + std::string string_value; + if (!dict->GetString(json_schema_constants::kType, &string_value) || + string_value != json_schema_constants::kObject) { + *error = + "The main schema must have a type attribute with \"object\" value."; + return scoped_ptr<SchemaOwner>(); + } + + // Checks for invalid attributes at the top-level. + if (dict->HasKey(json_schema_constants::kAdditionalProperties) || + dict->HasKey(json_schema_constants::kPatternProperties)) { + *error = "\"additionalProperties\" and \"patternProperties\" are not " + "supported at the main schema."; + return scoped_ptr<SchemaOwner>(); + } + + scoped_ptr<SchemaOwner> impl(new SchemaOwner(NULL)); + impl->root_ = impl->Parse(*dict, error); + if (!impl->root_) + impl.reset(); + return impl.PassAs<SchemaOwner>(); +} + +const internal::SchemaNode* SchemaOwner::Parse( + const base::DictionaryValue& schema, + std::string* error) { + std::string type_string; + if (!schema.GetString(json_schema_constants::kType, &type_string)) { + *error = "The schema type must be declared."; + return NULL; + } + + base::Value::Type type = base::Value::TYPE_NULL; + if (!SchemaTypeToValueType(type_string, &type)) { + *error = "Type not supported: " + type_string; + return NULL; + } + + if (type == base::Value::TYPE_DICTIONARY) + return ParseDictionary(schema, error); + if (type == base::Value::TYPE_LIST) + return ParseList(schema, error); + + internal::SchemaNode* node = new internal::SchemaNode; + node->type = type; + node->extra = NULL; + schema_nodes_.push_back(node); + return node; +} + +const internal::SchemaNode* SchemaOwner::ParseDictionary( + const base::DictionaryValue& schema, + std::string* error) { + internal::PropertiesNode* properties_node = new internal::PropertiesNode; + properties_node->begin = NULL; + properties_node->end = NULL; + properties_node->additional = NULL; + properties_nodes_.push_back(properties_node); + + const base::DictionaryValue* dict = NULL; + const base::DictionaryValue* properties = NULL; + if (schema.GetDictionary(json_schema_constants::kProperties, &properties)) { + internal::PropertyNode* property_nodes = + new internal::PropertyNode[properties->size()]; + property_nodes_.push_back(property_nodes); + + size_t index = 0; + for (base::DictionaryValue::Iterator it(*properties); + !it.IsAtEnd(); it.Advance(), ++index) { + // This should have been verified by the JSONSchemaValidator. + CHECK(it.value().GetAsDictionary(&dict)); + const internal::SchemaNode* sub_schema = Parse(*dict, error); + if (!sub_schema) + return NULL; + std::string* key = new std::string(it.key()); + keys_.push_back(key); + property_nodes[index].key = key->c_str(); + property_nodes[index].schema = sub_schema; + } + CHECK_EQ(properties->size(), index); + properties_node->begin = property_nodes; + properties_node->end = property_nodes + index; + } + + if (schema.GetDictionary(json_schema_constants::kAdditionalProperties, + &dict)) { + const internal::SchemaNode* sub_schema = Parse(*dict, error); + if (!sub_schema) + return NULL; + properties_node->additional = sub_schema; + } + + internal::SchemaNode* schema_node = new internal::SchemaNode; + schema_node->type = base::Value::TYPE_DICTIONARY; + schema_node->extra = properties_node; + schema_nodes_.push_back(schema_node); + return schema_node; +} + +const internal::SchemaNode* SchemaOwner::ParseList( + const base::DictionaryValue& schema, + std::string* error) { + const base::DictionaryValue* dict = NULL; + if (!schema.GetDictionary(json_schema_constants::kItems, &dict)) { + *error = "Arrays must declare a single schema for their items."; + return NULL; + } + const internal::SchemaNode* items_schema_node = Parse(*dict, error); + if (!items_schema_node) + return NULL; + + internal::SchemaNode* schema_node = new internal::SchemaNode; + schema_node->type = base::Value::TYPE_LIST; + schema_node->extra = items_schema_node; + schema_nodes_.push_back(schema_node); + return schema_node; +} + +} // namespace policy diff --git a/components/policy/core/common/schema.h b/components/policy/core/common/schema.h new file mode 100644 index 0000000..a81b7ad --- /dev/null +++ b/components/policy/core/common/schema.h @@ -0,0 +1,150 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_ +#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/values.h" +#include "components/policy/policy_export.h" + +namespace policy { +namespace internal { + +struct POLICY_EXPORT SchemaNode; +struct POLICY_EXPORT PropertyNode; +struct POLICY_EXPORT PropertiesNode; + +} // namespace internal + +// Describes the expected type of one policy. Also recursively describes the +// types of inner elements, for structured types. +// Objects of this class refer to external, immutable data and are cheap to +// copy. +// Use the SchemaOwner class to parse a schema and get Schema objects. +class POLICY_EXPORT Schema { + public: + explicit Schema(const internal::SchemaNode* schema); + Schema(const Schema& schema); + + Schema& operator=(const Schema& schema); + + // Returns true if this Schema is valid. Schemas returned by the methods below + // may be invalid, and in those cases the other methods must not be used. + bool valid() const { return schema_ != NULL; } + + base::Value::Type type() const; + + // Used to iterate over the known properties of TYPE_DICTIONARY schemas. + class POLICY_EXPORT Iterator { + public: + explicit Iterator(const internal::PropertiesNode* properties); + Iterator(const Iterator& iterator); + ~Iterator(); + + Iterator& operator=(const Iterator& iterator); + + // The other methods must not be called if the iterator is at the end. + bool IsAtEnd() const; + + // Advances the iterator to the next property. + void Advance(); + + // Returns the name of the current property. + const char* key() const; + + // Returns the Schema for the current property. This Schema is always valid. + Schema schema() const; + + private: + const internal::PropertyNode* it_; + const internal::PropertyNode* end_; + }; + + // These methods should be called only if type() == TYPE_DICTIONARY, + // otherwise invalid memory will be read. A CHECK is currently enforcing this. + + // Returns an iterator that goes over the named properties of this schema. + // The returned iterator is at the beginning. + Iterator GetPropertiesIterator() const; + + // Returns the Schema for the property named |key|. If |key| is not a known + // property name then the returned Schema is not valid. + Schema GetKnownProperty(const std::string& key) const; + + // Returns the Schema for additional properties. If additional properties are + // not allowed for this Schema then the Schema returned is not valid. + Schema GetAdditionalProperties() const; + + // Returns the Schema for |key| if it is a known property, otherwise returns + // the Schema for additional properties. + Schema GetProperty(const std::string& key) const; + + // Returns the Schema for items of an array. + // This method should be called only if type() == TYPE_LIST, + // otherwise invalid memory will be read. A CHECK is currently enforcing this. + Schema GetItems() const; + + private: + const internal::SchemaNode* schema_; +}; + +// Owns schemas for policies of a given component. +class POLICY_EXPORT SchemaOwner { + public: + ~SchemaOwner(); + + // The returned Schema is valid only during the lifetime of the SchemaOwner + // that created it. It may be obtained multiple times. + Schema schema() const { return Schema(root_); } + + // Returns a SchemaOwner that references static data. This can be used by + // the embedder to pass structures generated at compile time, which can then + // be quickly loaded at runtime. + // Note: PropertiesNodes must have their PropertyNodes sorted by key. + static scoped_ptr<SchemaOwner> Wrap(const internal::SchemaNode* schema); + + // Parses the JSON schema in |schema| and returns a SchemaOwner that owns + // the internal representation. If |schema| is invalid then NULL is returned + // and |error| contains a reason for the failure. + static scoped_ptr<SchemaOwner> Parse(const std::string& schema, + std::string* error); + + private: + explicit SchemaOwner(const internal::SchemaNode* root); + + // Parses the JSON schema in |schema| and returns the root SchemaNode if + // successful, otherwise returns NULL. Any intermediate objects built by + // this method are appended to the ScopedVectors. + const internal::SchemaNode* Parse(const base::DictionaryValue& schema, + std::string* error); + + // Helper for Parse(). + const internal::SchemaNode* ParseDictionary( + const base::DictionaryValue& schema, + std::string* error); + + // Helper for Parse(). + const internal::SchemaNode* ParseList(const base::DictionaryValue& schema, + std::string* error); + + const internal::SchemaNode* root_; + ScopedVector<internal::SchemaNode> schema_nodes_; + // Note: |property_nodes_| contains PropertyNode[] elements and must be + // cleared manually to properly use delete[]. + std::vector<internal::PropertyNode*> property_nodes_; + ScopedVector<internal::PropertiesNode> properties_nodes_; + ScopedVector<std::string> keys_; + + DISALLOW_COPY_AND_ASSIGN(SchemaOwner); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_ diff --git a/components/policy/core/common/schema_internal.h b/components/policy/core/common/schema_internal.h new file mode 100644 index 0000000..025a437 --- /dev/null +++ b/components/policy/core/common/schema_internal.h @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_ +#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_ + +#include "base/values.h" +#include "components/policy/policy_export.h" + +namespace policy { +namespace internal { + +// These types are used internally by the SchemaOwner parser, and by the +// compile-time code generator. They shouldn't be used directly. + +struct POLICY_EXPORT SchemaNode { + base::Value::Type type; + + // If |type| is TYPE_LIST then this is a SchemaNode* describing the + // element type. + // + // If |type| is TYPE_DICTIONARY then this is a PropertiesNode* that can + // contain any number of named properties and optionally a SchemaNode* for + // additional properties. + // + // This is NULL if |type| has any other values. + const void* extra; +}; + +struct POLICY_EXPORT PropertyNode { + const char* key; + const SchemaNode* schema; +}; + +struct POLICY_EXPORT PropertiesNode { + const PropertyNode* begin; + const PropertyNode* end; + const SchemaNode* additional; +}; + +} // namespace internal +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_ diff --git a/components/policy/core/common/schema_unittest.cc b/components/policy/core/common/schema_unittest.cc new file mode 100644 index 0000000..06fb3bf --- /dev/null +++ b/components/policy/core/common/schema_unittest.cc @@ -0,0 +1,338 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/schema.h" + +#include "components/policy/core/common/schema_internal.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace policy { + +namespace { + +#define OBJECT_TYPE "\"type\":\"object\"" + +const internal::SchemaNode kTypeBoolean = { base::Value::TYPE_BOOLEAN, NULL, }; +const internal::SchemaNode kTypeInteger = { base::Value::TYPE_INTEGER, NULL, }; +const internal::SchemaNode kTypeNumber = { base::Value::TYPE_DOUBLE, NULL, }; +const internal::SchemaNode kTypeString = { base::Value::TYPE_STRING, NULL, }; + +bool ParseFails(const std::string& content) { + std::string error; + scoped_ptr<SchemaOwner> schema = SchemaOwner::Parse(content, &error); + if (schema) + EXPECT_TRUE(schema->schema().valid()); + else + EXPECT_FALSE(error.empty()); + return !schema; +} + +} // namespace + +TEST(SchemaTest, MinimalSchema) { + EXPECT_FALSE(ParseFails( + "{" + OBJECT_TYPE + "}")); +} + +TEST(SchemaTest, InvalidSchemas) { + EXPECT_TRUE(ParseFails("")); + EXPECT_TRUE(ParseFails("omg")); + EXPECT_TRUE(ParseFails("\"omg\"")); + EXPECT_TRUE(ParseFails("123")); + EXPECT_TRUE(ParseFails("[]")); + EXPECT_TRUE(ParseFails("null")); + EXPECT_TRUE(ParseFails("{}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"additionalProperties\": { \"type\":\"object\" }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"patternProperties\": { \"a+b*\": { \"type\": \"object\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"bogus\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": [\"string\", \"number\"] } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"any\" } }" + "}")); +} + +TEST(SchemaTest, ValidSchema) { + std::string error; + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE "," + "\"properties\": {" + " \"Boolean\": { \"type\": \"boolean\" }," + " \"Integer\": { \"type\": \"integer\" }," + " \"Null\": { \"type\": \"null\" }," + " \"Number\": { \"type\": \"number\" }," + " \"String\": { \"type\": \"string\" }," + " \"Array\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }," + " \"ArrayOfObjects\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"string\" }," + " \"two\": { \"type\": \"integer\" }" + " }" + " }" + " }," + " \"ArrayOfArray\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }" + " }," + " \"Object\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"boolean\" }," + " \"two\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + " }" + "}" + "}", &error); + ASSERT_TRUE(policy_schema) << error; + ASSERT_TRUE(policy_schema->schema().valid()); + + Schema schema = policy_schema->schema(); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + EXPECT_FALSE(schema.GetProperty("invalid").valid()); + + Schema sub = schema.GetProperty("Boolean"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, sub.type()); + + sub = schema.GetProperty("Integer"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_INTEGER, sub.type()); + + sub = schema.GetProperty("Null"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_NULL, sub.type()); + + sub = schema.GetProperty("Number"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_DOUBLE, sub.type()); + + sub = schema.GetProperty("String"); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, sub.type()); + + sub = schema.GetProperty("Array"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, sub.type()); + + sub = schema.GetProperty("ArrayOfObjects"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_DICTIONARY, sub.type()); + Schema subsub = sub.GetProperty("one"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, subsub.type()); + subsub = sub.GetProperty("two"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub.type()); + subsub = sub.GetProperty("invalid"); + EXPECT_FALSE(subsub.valid()); + + sub = schema.GetProperty("ArrayOfArray"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_LIST, sub.type()); + sub = sub.GetItems(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, sub.type()); + + sub = schema.GetProperty("Object"); + ASSERT_TRUE(sub.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, sub.type()); + subsub = sub.GetProperty("one"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, subsub.type()); + subsub = sub.GetProperty("two"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub.type()); + subsub = sub.GetProperty("undeclared"); + ASSERT_TRUE(subsub.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, subsub.type()); + + struct { + const char* expected_key; + base::Value::Type expected_type; + } kExpectedProperties[] = { + { "Array", base::Value::TYPE_LIST }, + { "ArrayOfArray", base::Value::TYPE_LIST }, + { "ArrayOfObjects", base::Value::TYPE_LIST }, + { "Boolean", base::Value::TYPE_BOOLEAN }, + { "Integer", base::Value::TYPE_INTEGER }, + { "Null", base::Value::TYPE_NULL }, + { "Number", base::Value::TYPE_DOUBLE }, + { "Object", base::Value::TYPE_DICTIONARY }, + { "String", base::Value::TYPE_STRING }, + }; + Schema::Iterator it = schema.GetPropertiesIterator(); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExpectedProperties); ++i) { + ASSERT_FALSE(it.IsAtEnd()); + EXPECT_STREQ(kExpectedProperties[i].expected_key, it.key()); + ASSERT_TRUE(it.schema().valid()); + EXPECT_EQ(kExpectedProperties[i].expected_type, it.schema().type()); + it.Advance(); + } + EXPECT_TRUE(it.IsAtEnd()); +} + +TEST(SchemaTest, Lookups) { + std::string error; + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE + "}", &error); + ASSERT_TRUE(policy_schema) << error; + Schema schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + // This empty schema should never find named properties. + EXPECT_FALSE(schema.GetKnownProperty("").valid()); + EXPECT_FALSE(schema.GetKnownProperty("xyz").valid()); + EXPECT_TRUE(schema.GetPropertiesIterator().IsAtEnd()); + + policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE "," + "\"properties\": {" + " \"Boolean\": { \"type\": \"boolean\" }" + "}" + "}", &error); + ASSERT_TRUE(policy_schema) << error; + schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + EXPECT_FALSE(schema.GetKnownProperty("").valid()); + EXPECT_FALSE(schema.GetKnownProperty("xyz").valid()); + EXPECT_TRUE(schema.GetKnownProperty("Boolean").valid()); + + policy_schema = SchemaOwner::Parse( + "{" + OBJECT_TYPE "," + "\"properties\": {" + " \"bb\" : { \"type\": \"null\" }," + " \"aa\" : { \"type\": \"boolean\" }," + " \"abab\" : { \"type\": \"string\" }," + " \"ab\" : { \"type\": \"number\" }," + " \"aba\" : { \"type\": \"integer\" }" + "}" + "}", &error); + ASSERT_TRUE(policy_schema) << error; + schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + EXPECT_FALSE(schema.GetKnownProperty("").valid()); + EXPECT_FALSE(schema.GetKnownProperty("xyz").valid()); + + struct { + const char* expected_key; + base::Value::Type expected_type; + } kExpectedKeys[] = { + { "aa", base::Value::TYPE_BOOLEAN }, + { "ab", base::Value::TYPE_DOUBLE }, + { "aba", base::Value::TYPE_INTEGER }, + { "abab", base::Value::TYPE_STRING }, + { "bb", base::Value::TYPE_NULL }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExpectedKeys); ++i) { + Schema sub = schema.GetKnownProperty(kExpectedKeys[i].expected_key); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(kExpectedKeys[i].expected_type, sub.type()); + } +} + +TEST(SchemaTest, WrapSimpleNode) { + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Wrap(&kTypeString); + ASSERT_TRUE(policy_schema); + Schema schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + EXPECT_EQ(base::Value::TYPE_STRING, schema.type()); +} + +TEST(SchemaTest, WrapDictionary) { + const internal::SchemaNode kList = { + base::Value::TYPE_LIST, + &kTypeString, + }; + + const internal::PropertyNode kPropertyNodes[] = { + { "Boolean", &kTypeBoolean }, + { "Integer", &kTypeInteger }, + { "List", &kList }, + { "Number", &kTypeNumber }, + { "String", &kTypeString }, + }; + + const internal::PropertiesNode kProperties = { + kPropertyNodes, + kPropertyNodes + arraysize(kPropertyNodes), + NULL, + }; + + const internal::SchemaNode root = { + base::Value::TYPE_DICTIONARY, + &kProperties, + }; + + scoped_ptr<SchemaOwner> policy_schema = SchemaOwner::Wrap(&root); + ASSERT_TRUE(policy_schema); + Schema schema = policy_schema->schema(); + ASSERT_TRUE(schema.valid()); + EXPECT_EQ(base::Value::TYPE_DICTIONARY, schema.type()); + + Schema::Iterator it = schema.GetPropertiesIterator(); + for (size_t i = 0; i < arraysize(kPropertyNodes); ++i) { + ASSERT_FALSE(it.IsAtEnd()); + EXPECT_STREQ(kPropertyNodes[i].key, it.key()); + Schema sub = it.schema(); + ASSERT_TRUE(sub.valid()); + EXPECT_EQ(kPropertyNodes[i].schema->type, sub.type()); + it.Advance(); + } + EXPECT_TRUE(it.IsAtEnd()); +} + +} // namespace policy |