diff options
author | calamity@chromium.org <calamity@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-02 15:05:27 +0000 |
---|---|---|
committer | calamity@chromium.org <calamity@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-02 15:05:27 +0000 |
commit | feba21e3dd6f25b0927817bdad749e47490e2798 (patch) | |
tree | da16581c8c1658b385d6230423cc726df04cf4b7 /tools/json_schema_compiler | |
parent | 7ae87817552c6e8608f62304354fd88cb921a31c (diff) | |
download | chromium_src-feba21e3dd6f25b0927817bdad749e47490e2798.zip chromium_src-feba21e3dd6f25b0927817bdad749e47490e2798.tar.gz chromium_src-feba21e3dd6f25b0927817bdad749e47490e2798.tar.bz2 |
json_schema_compiler: any, additionalProperties, functions on types
Add support and tests for more json types. Also fixed a number of API jsons.
BUG=
TEST=
Review URL: http://codereview.chromium.org/9491002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124643 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/json_schema_compiler')
26 files changed, 868 insertions, 154 deletions
diff --git a/tools/json_schema_compiler/any.cc b/tools/json_schema_compiler/any.cc new file mode 100644 index 0000000..b429567 --- /dev/null +++ b/tools/json_schema_compiler/any.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2012 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 "tools/json_schema_compiler/any.h" + +#include "base/logging.h" +#include "base/values.h" + +namespace json_schema_compiler { +namespace any { + +Any::Any() {} + +Any::~Any() {} + +void Any::Init(const base::Value& from_value) { + CHECK(!value_.get()); + value_.reset(from_value.DeepCopy()); +} + +const base::Value& Any::value() const { + CHECK(value_.get()); + return *value_; +} + +} // namespace any +} // namespace json_schema_compiler diff --git a/tools/json_schema_compiler/any.h b/tools/json_schema_compiler/any.h new file mode 100644 index 0000000..8169313 --- /dev/null +++ b/tools/json_schema_compiler/any.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 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 TOOLS_JSON_SCHEMA_COMPILER_ANY_H__ +#define TOOLS_JSON_SCHEMA_COMPILER_ANY_H__ +#pragma once + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class Value; +} + +namespace json_schema_compiler { +namespace any { + +// Represents an "any" type in JSON schema as a wrapped Value. +class Any { + public: + Any(); + ~Any(); + + // Initializes the Value in this Any. Fails if already initialized. + void Init(const base::Value& from_value); + + // Get the Value from this Any. + const base::Value& value() const; + + private: + scoped_ptr<base::Value> value_; + + DISALLOW_COPY_AND_ASSIGN(Any); +}; + +} // namespace any +} // namespace json_schema_compiler + +#endif // TOOLS_JSON_SCHEMA_COMPILER_ANY_H__ diff --git a/tools/json_schema_compiler/any_helper.py b/tools/json_schema_compiler/any_helper.py new file mode 100644 index 0000000..4502dab --- /dev/null +++ b/tools/json_schema_compiler/any_helper.py @@ -0,0 +1,32 @@ +# Copyright (c) 2012 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. + +ANY_NAMESPACE = 'json_schema_compiler::any' +ANY_CLASS = ANY_NAMESPACE + '::Any' + +class AnyHelper(object): + """A util class that generates code that uses + tools/json_schema_compiler/any.cc. + """ + def Init(self, any_prop, src, dst): + """Initialize |dst|.|any_prop| to |src|. + + src: Value* + dst: Type* + """ + if any_prop.optional: + return '%s->%s->Init(*%s)' % (dst, any_prop.name, src) + else: + return '%s->%s.Init(*%s)' % (dst, any_prop.name, src) + + def GetValue(self, any_prop, var): + """Get |var| as a const Value&. + + var: Any* or Any + """ + if any_prop.optional: + return '%s->value()' % var + else: + return '%s.value()' % var + diff --git a/tools/json_schema_compiler/api_gen_util.gyp b/tools/json_schema_compiler/api_gen_util.gyp index 54966cc..b1c220d 100644 --- a/tools/json_schema_compiler/api_gen_util.gyp +++ b/tools/json_schema_compiler/api_gen_util.gyp @@ -10,6 +10,7 @@ 'target_name': 'api_gen_util', 'type': 'static_library', 'sources': [ + 'any.cc', 'util.cc', ], 'dependencies': ['<(DEPTH)/base/base.gyp:base'], diff --git a/tools/json_schema_compiler/cc_generator.py b/tools/json_schema_compiler/cc_generator.py index bbda061..ce65ed2 100644 --- a/tools/json_schema_compiler/cc_generator.py +++ b/tools/json_schema_compiler/cc_generator.py @@ -3,8 +3,10 @@ # found in the LICENSE file. from model import PropertyType +import any_helper import code import cpp_util +import model import util_cc_helper class CCGenerator(object): @@ -17,6 +19,7 @@ class CCGenerator(object): self._cpp_type_generator.GetCppNamespaceName(self._namespace)) self._util_cc_helper = ( util_cc_helper.UtilCCHelper(self._cpp_type_generator)) + self._any_helper = any_helper.AnyHelper() def Generate(self): """Generates a code.Code object with the .cc for a single namespace. @@ -40,6 +43,7 @@ class CCGenerator(object): .Append('using base::Value;') .Append('using base::DictionaryValue;') .Append('using base::ListValue;') + .Append('using %s;' % any_helper.ANY_CLASS) .Append() .Concat(self._cpp_type_generator.GetRootNamespaceStart()) .Concat(self._cpp_type_generator.GetNamespaceStart()) @@ -62,7 +66,8 @@ class CCGenerator(object): .Append() ) for function in self._namespace.functions.values(): - (c.Concat(self._GenerateFunction(function)) + (c.Concat(self._GenerateFunction( + cpp_util.Classname(function.name), function)) .Append() ) (c.Concat(self._cpp_type_generator.GetNamespaceEnd()) @@ -78,25 +83,43 @@ class CCGenerator(object): classname = cpp_util.Classname(type_.name) c = code.Code() - (c.Concat(self._GeneratePropertyFunctions( - cpp_namespace, type_.properties.values())) - .Append('%(namespace)s::%(classname)s() {}') - .Append('%(namespace)s::~%(classname)s() {}') - .Append() - ) - if type_.from_json: - (c.Concat(self._GenerateTypePopulate(cpp_namespace, type_)) + if type_.functions: + # Types with functions are not instantiable in C++ because they are + # handled in pure Javascript and hence have no properties or + # additionalProperties. + if type_.properties: + raise NotImplementedError('\n'.join(model.GetModelHierarchy(type_)) + + '\nCannot generate both functions and properties on a type') + for function in type_.functions.values(): + (c.Concat( + self._GenerateFunction( + cpp_namespace + '::' + cpp_util.Classname(function.name), + function)) + .Append() + ) + else: + (c.Concat(self._GeneratePropertyFunctions( + cpp_namespace, type_.properties.values())) + .Append('%(namespace)s::%(classname)s() {}') + .Append('%(namespace)s::~%(classname)s() {}') .Append() ) - if type_.from_client: - c.Concat(self._GenerateTypeToValue(cpp_namespace, type_)) - c.Append() + if type_.from_json: + (c.Concat(self._GenerateTypePopulate(cpp_namespace, type_)) + .Append() + ) + if type_.from_client: + (c.Concat(self._GenerateTypeToValue(cpp_namespace, type_)) + .Append() + ) c.Substitute({'classname': classname, 'namespace': cpp_namespace}) return c def _GenerateTypePopulate(self, cpp_namespace, type_): """Generates the function for populating a type given a pointer to it. + + E.g for type "Foo", generates Foo::Populate() """ classname = cpp_util.Classname(type_.name) c = code.Code() @@ -112,7 +135,16 @@ class CCGenerator(object): for prop in type_.properties.values(): c.Concat(self._InitializePropertyToDefault(prop, 'out')) for prop in type_.properties.values(): - c.Concat(self._GenerateTypePopulateProperty(prop, 'dict', 'out')) + if prop.type_ == PropertyType.ADDITIONAL_PROPERTIES: + c.Append('out->additional_properties.MergeDictionary(dict);') + # remove all keys that are actual properties + for cur_prop in type_.properties.values(): + if prop != cur_prop: + c.Append('out->additional_properties' + '.RemoveWithoutPathExpansion("%s", NULL);' % cur_prop.name) + c.Append() + else: + c.Concat(self._GenerateTypePopulateProperty(prop, 'dict', 'out')) (c.Append('return true;') .Eblock('}') ) @@ -149,6 +181,8 @@ class CCGenerator(object): def _GenerateTypeToValue(self, cpp_namespace, type_): """Generates a function that serializes the type into a |DictionaryValue|. + + E.g. for type "Foo" generates Foo::ToValue() """ c = code.Code() (c.Sblock('scoped_ptr<DictionaryValue> %s::ToValue() const {' % @@ -157,51 +191,54 @@ class CCGenerator(object): .Append() ) for prop in type_.properties.values(): - if prop.optional: - if prop.type_ == PropertyType.ENUM: - c.Sblock('if (%s != %s)' % - (prop.unix_name, self._cpp_type_generator.GetEnumNoneValue(prop))) - else: - c.Sblock('if (%s.get())' % prop.unix_name) - c.Append('value->SetWithoutPathExpansion("%s", %s);' % ( - prop.name, - self._CreateValueFromProperty(prop, prop.unix_name))) - if prop.optional: - c.Eblock(); + if prop.type_ == PropertyType.ADDITIONAL_PROPERTIES: + c.Append('value->MergeDictionary(&%s);' % prop.unix_name) + else: + if prop.optional: + if prop.type_ == PropertyType.ENUM: + c.Sblock('if (%s != %s)' % + (prop.unix_name, + self._cpp_type_generator.GetEnumNoneValue(prop))) + else: + c.Sblock('if (%s.get())' % prop.unix_name) + c.Append('value->SetWithoutPathExpansion("%s", %s);' % ( + prop.name, + self._CreateValueFromProperty(prop, prop.unix_name))) + if prop.optional: + c.Eblock(); (c.Append() .Append('return value.Pass();') .Eblock('}') ) return c - def _GenerateFunction(self, function): + def _GenerateFunction(self, cpp_namespace, function): """Generates the definitions for function structs. """ - classname = cpp_util.Classname(function.name) c = code.Code() # Params::Populate function if function.params: - c.Concat(self._GeneratePropertyFunctions(classname + '::Params', + c.Concat(self._GeneratePropertyFunctions(cpp_namespace + '::Params', function.params)) - (c.Append('%(name)s::Params::Params() {}') - .Append('%(name)s::Params::~Params() {}') + (c.Append('%(cpp_namespace)s::Params::Params() {}') + .Append('%(cpp_namespace)s::Params::~Params() {}') .Append() - .Concat(self._GenerateFunctionParamsCreate(function)) + .Concat(self._GenerateFunctionParamsCreate(cpp_namespace, function)) .Append() ) # Result::Create function if function.callback: - c.Concat(self._GenerateFunctionResultCreate(function)) + c.Concat(self._GenerateFunctionResultCreate(cpp_namespace, function)) - c.Substitute({'name': classname}) + c.Substitute({'cpp_namespace': cpp_namespace}) return c def _GenerateCreateEnumValue(self, cpp_namespace, prop): - """Generates a function that returns the |StringValue| representation of an - enum. + """Generates CreateEnumValue() that returns the |StringValue| + representation of an enum. """ c = code.Code() c.Append('// static') @@ -233,15 +270,18 @@ class CCGenerator(object): return c def _CreateValueFromProperty(self, prop, var): - """Creates a Value given a single property. Generated code passes ownership + """Creates a Value given a property. Generated code passes ownership to caller. var: variable or variable* + + E.g for std::string, generate Value::CreateStringValue(var) """ if prop.type_ == PropertyType.CHOICES: - # CHOICES conversion not implemented because it's not used. If needed, - # write something to generate a function that returns a scoped_ptr<Value> - # and put it in _GeneratePropertyFunctions. + # CHOICES conversion not implemented. If needed, write something to + # generate a function that returns a scoped_ptr<Value> and put it in + # _GeneratePropertyFunctions, then use it here. Look at CreateEnumValue() + # for reference. raise NotImplementedError( 'Conversion of CHOICES to Value not implemented') if prop.type_ in (PropertyType.REF, PropertyType.OBJECT): @@ -249,6 +289,10 @@ class CCGenerator(object): return '%s->ToValue().release()' % var else: return '%s.ToValue().release()' % var + elif prop.type_ == PropertyType.ANY: + return '%s.DeepCopy()' % self._any_helper.GetValue(prop, var) + elif prop.type_ == PropertyType.ADDITIONAL_PROPERTIES: + return '%s.DeepCopy()' % var elif prop.type_ == PropertyType.ENUM: return 'CreateEnumValue(%s).release()' % var elif prop.type_ == PropertyType.ARRAY: @@ -291,19 +335,20 @@ class CCGenerator(object): }) return c - def _GenerateFunctionParamsCreate(self, function): + def _GenerateFunctionParamsCreate(self, cpp_namespace, function): """Generate function to create an instance of Params. The generated function takes a ListValue of arguments. + + E.g for function "Bar", generate Bar::Params::Create() """ - classname = cpp_util.Classname(function.name) c = code.Code() (c.Append('// static') - .Sblock('scoped_ptr<%(classname)s::Params> %(classname)s::Params::Create' - '(const ListValue& args) {') + .Sblock('scoped_ptr<%(cpp_namespace)s::Params> ' + '%(cpp_namespace)s::Params::Create(const ListValue& args) {') .Concat(self._GenerateParamsCheck(function, 'args')) .Append('scoped_ptr<Params> params(new Params());') ) - c.Substitute({'classname': classname}) + c.Substitute({'cpp_namespace': cpp_namespace}) for param in function.params: c.Concat(self._InitializePropertyToDefault(param, 'params')) @@ -353,7 +398,8 @@ class CCGenerator(object): c = code.Code() c.Sblock('{') - if check_type and prop.type_ != PropertyType.CHOICES: + if check_type and prop.type_ not in ( + PropertyType.CHOICES, PropertyType.ANY): (c.Append('if (!%(value_var)s->IsType(%(value_type)s))') .Append(' return %(failure_value)s;') ) @@ -389,6 +435,10 @@ class CCGenerator(object): 'if (!%(ctype)s::Populate(*dictionary, &%(dst)s->%(name)s))') .Append(' return %(failure_value)s;') ) + elif prop.type_ == PropertyType.ANY: + if prop.optional: + c.Append('%(dst)s->%(name)s.reset(new Any());') + c.Append(self._any_helper.Init(prop, value_var, dst) + ';') elif prop.type_ == PropertyType.ARRAY: # util_cc_helper deals with optional and required arrays (c.Append('ListValue* list = NULL;') @@ -442,7 +492,7 @@ class CCGenerator(object): 'dst': dst, 'failure_value': failure_value, } - if prop.type_ != PropertyType.CHOICES: + if prop.type_ not in (PropertyType.CHOICES, PropertyType.ANY): sub['ctype'] = self._cpp_type_generator.GetType(prop) sub['value_type'] = cpp_util.GetValueType(prop) c.Substitute(sub) @@ -459,20 +509,24 @@ class CCGenerator(object): param_namespace + '::' + cpp_util.Classname(param.name), param)) c.Append() + elif param.type_ == PropertyType.CHOICES: + c.Concat(self._GeneratePropertyFunctions( + param_namespace, param.choices.values())) elif param.type_ == PropertyType.ENUM: c.Concat(self._GenerateCreateEnumValue(param_namespace, param)) c.Append() return c - def _GenerateFunctionResultCreate(self, function): + def _GenerateFunctionResultCreate(self, cpp_namespace, function): """Generate function to create a Result given the return value. + + E.g for function "Bar", generate Bar::Result::Create """ - classname = cpp_util.Classname(function.name) c = code.Code() params = function.callback.params if not params: - (c.Append('Value* %s::Result::Create() {' % classname) + (c.Append('Value* %s::Result::Create() {' % cpp_namespace) .Append(' return Value::CreateNullValue();') .Append('}') ) @@ -480,22 +534,26 @@ class CCGenerator(object): expanded_params = self._cpp_type_generator.GetExpandedChoicesInParams( params) c.Concat(self._GeneratePropertyFunctions( - classname + '::Result', expanded_params)) + cpp_namespace + '::Result', expanded_params)) # If there is a single parameter, this is straightforward. However, if # the callback parameter is of 'choices', this generates a Create method # for each choice. This works because only 1 choice can be returned at a # time. for param in expanded_params: + if param.type_ == PropertyType.ANY: + # Generation of Value* Create(Value*) is redundant. + continue # We treat this argument as 'required' to avoid wrapping it in a # scoped_ptr if it's optional. param_copy = param.Copy() param_copy.optional = False - c.Sblock('Value* %(classname)s::Result::Create(const %(arg)s) {') + c.Sblock('Value* %(cpp_namespace)s::Result::Create(const %(arg)s) {') c.Append('return %s;' % self._CreateValueFromProperty(param_copy, param_copy.unix_name)) c.Eblock('}') - c.Substitute({'classname': classname, + c.Substitute({ + 'cpp_namespace': cpp_namespace, 'arg': cpp_util.GetParameterDeclaration( param_copy, self._cpp_type_generator.GetType(param_copy)) }) @@ -505,6 +563,8 @@ class CCGenerator(object): def _InitializePropertyToDefault(self, prop, dst): """Initialize a model.Property to its default value inside an object. + E.g for optional enum "state", generate dst->state = STATE_NONE; + dst: Type* """ c = code.Code() diff --git a/tools/json_schema_compiler/code.py b/tools/json_schema_compiler/code.py index c19029a..07f6574 100644 --- a/tools/json_schema_compiler/code.py +++ b/tools/json_schema_compiler/code.py @@ -14,11 +14,15 @@ class Code(object): self._indent_size = indent_size self._comment_length = comment_length - def Append(self, line=''): + def Append(self, line='', substitute=True): """Appends a line of code at the current indent level or just a newline if line is not specified. Trailing whitespace is stripped. + + substitute: indicated whether this line should be affected by + code.Substitute(). """ - self._code.append(((' ' * self._indent_level) + line).rstrip()) + self._code.append(Line(((' ' * self._indent_level) + line).rstrip(), + substitute=substitute)) return self def IsEmpty(self): @@ -40,9 +44,13 @@ class Code(object): for line in obj._code: try: # line % () will fail if any substitution tokens are left in line - self._code.append(((' ' * self._indent_level) + line % ()).rstrip()) + if line.substitute: + line.value %= () except TypeError: raise TypeError('Unsubstituted value when concatting\n' + line) + except ValueError: + raise ValueError('Stray % character when concatting\n' + line) + self.Append(line.value, line.substitute) return self @@ -66,16 +74,15 @@ class Code(object): self.Append(line) return self - # TODO(calamity): Make comment its own class or something and Render at - # self.Render() time - def Comment(self, comment): + def Comment(self, comment, comment_prefix='// '): """Adds the given string as a comment. Will split the comment if it's too long. Use mainly for variable length comments. Otherwise just use code.Append('// ...') for comments. + + Unaffected by code.Substitute(). """ - comment_symbol = '// ' - max_len = self._comment_length - self._indent_level - len(comment_symbol) + max_len = self._comment_length - self._indent_level - len(comment_prefix) while len(comment) >= max_len: line = comment[0:max_len] last_space = line.rfind(' ') @@ -84,8 +91,8 @@ class Code(object): comment = comment[last_space + 1:] else: comment = comment[max_len:] - self.Append(comment_symbol + line) - self.Append(comment_symbol + comment) + self.Append(comment_prefix + line, substitute=False) + self.Append(comment_prefix + comment, substitute=False) return self def Substitute(self, d): @@ -100,16 +107,24 @@ class Code(object): if not isinstance(d, dict): raise TypeError('Passed argument is not a dictionary: ' + d) for i, line in enumerate(self._code): - # Only need to check %s because arg is a dict and python will allow - # '%s %(named)s' but just about nothing else - if '%s' in self._code[i] or '%r' in self._code[i]: - raise TypeError('"%s" or "%r" found in substitution. ' - 'Named arguments only. Use "%" to escape') - self._code[i] = line % d + if self._code[i].substitute: + # Only need to check %s because arg is a dict and python will allow + # '%s %(named)s' but just about nothing else + if '%s' in self._code[i].value or '%r' in self._code[i].value: + raise TypeError('"%s" or "%r" found in substitution. ' + 'Named arguments only. Use "%" to escape') + self._code[i].value = line.value % d + self._code[i].substitute = False return self def Render(self): """Renders Code as a string. """ - return '\n'.join(self._code) + return '\n'.join([l.value for l in self._code]) +class Line(object): + """A line of code. + """ + def __init__(self, value, substitute=True): + self.value = value + self.substitute = substitute diff --git a/tools/json_schema_compiler/code_test.py b/tools/json_schema_compiler/code_test.py index 8431d95..a089f7fb 100644 --- a/tools/json_schema_compiler/code_test.py +++ b/tools/json_schema_compiler/code_test.py @@ -148,5 +148,17 @@ class CodeTest(unittest.TestCase): '// ' + 'x' * 23, c.Render()) + def testCommentWithSpecialCharacters(self): + c = Code() + c.Comment('20% of 80%s') + c.Substitute({}) + self.assertEquals('// 20% of 80%s', c.Render()) + d = Code() + d.Append('90') + d.Concat(c) + self.assertEquals('90\n' + '// 20% of 80%s', + d.Render()) + if __name__ == '__main__': unittest.main() diff --git a/tools/json_schema_compiler/compiler.py b/tools/json_schema_compiler/compiler.py index 7965d18..54ab62f 100644 --- a/tools/json_schema_compiler/compiler.py +++ b/tools/json_schema_compiler/compiler.py @@ -75,8 +75,8 @@ if __name__ == '__main__': # The output filename must match the input filename for gyp to deal with it # properly. out_file = namespace.name - type_generator = cpp_type_generator.CppTypeGenerator(root_namespace, - namespace, out_file) + type_generator = cpp_type_generator.CppTypeGenerator( + root_namespace, namespace, namespace.unix_name) for referenced_namespace in api_model.namespaces.values(): type_generator.AddNamespace( referenced_namespace, diff --git a/tools/json_schema_compiler/cpp_type_generator.py b/tools/json_schema_compiler/cpp_type_generator.py index cad64ae..44e7b59 100644 --- a/tools/json_schema_compiler/cpp_type_generator.py +++ b/tools/json_schema_compiler/cpp_type_generator.py @@ -4,6 +4,7 @@ from code import Code from model import PropertyType +import any_helper import cpp_util class CppTypeGenerator(object): @@ -134,8 +135,12 @@ class CppTypeGenerator(object): cpp_type = 'std::string' elif prop.type_ == PropertyType.ENUM: cpp_type = cpp_util.Classname(prop.name) - elif prop.type_ == PropertyType.ANY: + elif prop.type_ == PropertyType.ADDITIONAL_PROPERTIES: cpp_type = 'DictionaryValue' + elif prop.type_ == PropertyType.ANY: + cpp_type = any_helper.ANY_CLASS + elif prop.type_ == PropertyType.OBJECT: + cpp_type = cpp_util.Classname(prop.name) elif prop.type_ == PropertyType.ARRAY: if prop.item_type.type_ in ( PropertyType.REF, PropertyType.ANY, PropertyType.OBJECT): @@ -144,8 +149,6 @@ class CppTypeGenerator(object): cpp_type = 'std::vector<%s> ' cpp_type = cpp_type % self.GetType( prop.item_type, pad_for_generics=True) - elif prop.type_ == PropertyType.OBJECT: - cpp_type = cpp_util.Classname(prop.name) else: raise NotImplementedError(prop.type_) diff --git a/tools/json_schema_compiler/cpp_type_generator_test.py b/tools/json_schema_compiler/cpp_type_generator_test.py index 81ee04d..ac16262 100644 --- a/tools/json_schema_compiler/cpp_type_generator_test.py +++ b/tools/json_schema_compiler/cpp_type_generator_test.py @@ -62,11 +62,11 @@ class CppTypeGeneratorTest(unittest.TestCase): manager = CppTypeGenerator('', self.tabs, 'tabs_api') prop = self.tabs.functions['move'].params[0] self.assertEquals('TAB_IDS_ARRAY', - manager.GetEnumValue(prop, model.PropertyType.ARRAY)) + manager.GetEnumValue(prop, model.PropertyType.ARRAY.name)) self.assertEquals('TAB_IDS_INTEGER', - manager.GetEnumValue(prop, model.PropertyType.INTEGER)) + manager.GetEnumValue(prop, model.PropertyType.INTEGER.name)) self.assertEquals('TabIdsType', - manager.GetEnumType(prop)) + manager.GetChoicesEnumType(prop)) def testGetTypeSimple(self): manager = CppTypeGenerator('', self.tabs, 'tabs_api') diff --git a/tools/json_schema_compiler/cpp_util.py b/tools/json_schema_compiler/cpp_util.py index fafc155..3b65f5d 100644 --- a/tools/json_schema_compiler/cpp_util.py +++ b/tools/json_schema_compiler/cpp_util.py @@ -52,7 +52,6 @@ def GetValueType(prop): PropertyType.REF: 'Value::TYPE_DICTIONARY', PropertyType.OBJECT: 'Value::TYPE_DICTIONARY', PropertyType.ARRAY: 'Value::TYPE_LIST', - PropertyType.ANY: 'Value::TYPE_DICTIONARY', }[prop.type_] def GetParameterDeclaration(param, type_): diff --git a/tools/json_schema_compiler/h_generator.py b/tools/json_schema_compiler/h_generator.py index 3af7ccf..3c97bc6 100644 --- a/tools/json_schema_compiler/h_generator.py +++ b/tools/json_schema_compiler/h_generator.py @@ -5,6 +5,7 @@ from model import PropertyType import code import cpp_util +import model import os class HGenerator(object): @@ -38,6 +39,7 @@ class HGenerator(object): .Append('#include "base/memory/linked_ptr.h"') .Append('#include "base/memory/scoped_ptr.h"') .Append('#include "base/values.h"') + .Append('#include "tools/json_schema_compiler/any.h"') .Append() ) @@ -76,8 +78,7 @@ class HGenerator(object): (c.Concat(self._GenerateFunction(function)) .Append() ) - (c.Append() - .Concat(self._cpp_type_generator.GetNamespaceEnd()) + (c.Concat(self._cpp_type_generator.GetNamespaceEnd()) .Concat(self._cpp_type_generator.GetRootNamespaceEnd()) .Append() .Append('#endif // %s' % ifndef_name) @@ -109,6 +110,7 @@ class HGenerator(object): if prop.type_ == PropertyType.CHOICES: enum_name = self._cpp_type_generator.GetChoicesEnumType(prop) c.Append('%s %s_type;' % (enum_name, prop.unix_name)) + c.Append() for prop in self._cpp_type_generator.GetExpandedChoicesInParams(props): if prop.description: c.Comment(prop.description) @@ -123,33 +125,50 @@ class HGenerator(object): """ classname = cpp_util.Classname(type_.name) c = code.Code() - if type_.description: - c.Comment(type_.description) - (c.Sblock('struct %(classname)s {') - .Append('~%(classname)s();') - .Append('%(classname)s();') - .Append() - .Concat(self._GeneratePropertyStructures(type_.properties.values())) - .Concat(self._GenerateFields(type_.properties.values())) - ) - if type_.from_json: - (c.Comment('Populates a %s object from a Value. Returns' - ' whether |out| was successfully populated.' % classname) - .Append('static bool Populate(const Value& value, %(classname)s* out);') - .Append() + + if type_.functions: + # Types with functions are not instantiable in C++ because they are + # handled in pure Javascript and hence have no properties or + # additionalProperties. + if type_.properties: + raise NotImplementedError('\n'.join(model.GetModelHierarchy(type_)) + + '\nCannot generate both functions and properties on a type') + c.Sblock('namespace %(classname)s {') + for function in type_.functions.values(): + (c.Concat(self._GenerateFunction(function)) + .Append() + ) + c.Eblock('}') + else: + if type_.description: + c.Comment(type_.description) + (c.Sblock('struct %(classname)s {') + .Append('~%(classname)s();') + .Append('%(classname)s();') + .Append() + .Concat(self._GeneratePropertyStructures(type_.properties.values())) + .Concat(self._GenerateFields(type_.properties.values())) ) + if type_.from_json: + (c.Comment('Populates a %s object from a Value. Returns' + ' whether |out| was successfully populated.' % classname) + .Append( + 'static bool Populate(const Value& value, %(classname)s* out);') + .Append() + ) + + if type_.from_client: + (c.Comment('Returns a new DictionaryValue representing the' + ' serialized form of this %s object. Passes ' + 'ownership to caller.' % classname) + .Append('scoped_ptr<DictionaryValue> ToValue() const;') + ) - if type_.from_client: - (c.Comment('Returns a new DictionaryValue representing the' - ' serialized form of this %s object. Passes ' - 'ownership to caller.' % classname) - .Append('scoped_ptr<DictionaryValue> ToValue() const;') + (c.Eblock() + .Sblock(' private:') + .Append('DISALLOW_COPY_AND_ASSIGN(%(classname)s);') + .Eblock('};') ) - (c.Eblock() - .Sblock(' private:') - .Append('DISALLOW_COPY_AND_ASSIGN(%(classname)s);') - .Eblock('};') - ) c.Substitute({'classname': classname}) return c @@ -205,6 +224,7 @@ class HGenerator(object): self._cpp_type_generator.GetChoicesEnumType(prop), prop, [choice.type_.name for choice in prop.choices.values()])) + c.Concat(self._GeneratePropertyStructures(prop.choices.values())) elif prop.type_ == PropertyType.ENUM: enum_name = self._cpp_type_generator.GetType(prop) c.Concat(self._GenerateEnumDeclaration( @@ -234,6 +254,10 @@ class HGenerator(object): for param in self._cpp_type_generator.GetExpandedChoicesInParams(params): if param.description: c.Comment(param.description) + if param.type_ == PropertyType.ANY: + c.Comment("Value* Result::Create(Value*) not generated " + "because it's redundant.") + continue c.Append('Value* Create(const %s);' % cpp_util.GetParameterDeclaration( param, self._cpp_type_generator.GetType(param))) c.Eblock('};') diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py index d839ab9..de392d2 100644 --- a/tools/json_schema_compiler/model.py +++ b/tools/json_schema_compiler/model.py @@ -40,18 +40,22 @@ class Namespace(object): """ def __init__(self, json, source_file): self.name = json['namespace'] - self.unix_name = _UnixName(self.name) + self.unix_name = UnixName(self.name) self.source_file = source_file self.source_file_dir, self.source_file_filename = os.path.split(source_file) self.types = {} self.functions = {} + self.parent = None + # TODO(calamity): Implement properties on namespaces for shared structures + # or constants across a namespace (e.g Windows::WINDOW_ID_NONE). + for property_json in json.get('properties', []): + pass for type_json in json.get('types', []): - type_ = Type(type_json) + type_ = Type(self, type_json['id'], type_json) self.types[type_.name] = type_ for function_json in json.get('functions', []): if not function_json.get('nocompile', False): - function = Function(function_json) - self.functions[function.name] = function + self.functions[function_json['name']] = Function(self, function_json) class Type(object): """A Type defined in the json. @@ -59,23 +63,57 @@ class Type(object): Properties: - |name| the type name - |description| the description of the type (if provided) - - |properties| a map of property names to their model.Property + - |properties| a map of property unix_names to their model.Property + - |functions| a map of function names to their model.Function - |from_client| indicates that instances of the Type can originate from the users of generated code, such as top-level types and function results - |from_json| indicates that instances of the Type can originate from the JSON (as described by the schema), such as top-level types and function parameters """ - def __init__(self, json): - self.name = json['id'] + def __init__(self, parent, name, json): + if not ( + 'properties' in json or + 'additionalProperties' in json or + 'functions' in json): + raise ParseException(name + " has no properties or functions") + self.name = name self.description = json.get('description') self.from_json = True self.from_client = True self.properties = {} - for prop_name, prop_json in json['properties'].items(): - self.properties[prop_name] = Property(prop_name, prop_json, + self.functions = {} + self.parent = parent + for function_json in json.get('functions', []): + if not function_json.get('nocompile', False): + self.functions[function_json['name']] = Function(self, function_json) + props = [] + for prop_name, prop_json in json.get('properties', {}).items(): + # TODO(calamity): support functions (callbacks) as properties. The model + # doesn't support it yet because to h/cc generators don't -- this is + # because we'd need to hook it into a base::Callback or something. + # + # However, pragmatically it's not necessary to support them anyway, since + # the instances of functions-on-properties in the extension APIs are all + # handled in pure Javascript on the render process (and .: never reach + # C++ let alone the browser). + if prop_json.get('type') == 'function': + continue + props.append(Property(self, prop_name, prop_json, from_json=True, - from_client=True) + from_client=True)) + + additional_properties = json.get('additionalProperties') + if additional_properties: + props.append(Property(self, 'additionalProperties', additional_properties, + is_additional_properties=True)) + + for prop in props: + if prop.unix_name in self.properties: + raise ParseException( + self.properties[prop.unix_name].name + ' and ' + prop.name + + ' are both named ' + prop.unix_name) + self.properties[prop.unix_name] = prop class Callback(object): """A callback parameter to a Function. @@ -83,17 +121,18 @@ class Callback(object): Properties: - |params| the parameters to this callback. """ - def __init__(self, json): + def __init__(self, parent, json): params = json['parameters'] + self.parent = parent self.params = [] if len(params) == 0: return elif len(params) == 1: param = params[0] - self.params.append(Property(param['name'], param, + self.params.append(Property(self, param['name'], param, from_client=True)) else: - raise AssertionError("Callbacks can have at most a single parameter") + raise ParseException("Callbacks can have at most a single parameter") class Function(object): """A Function defined in the API. @@ -106,17 +145,19 @@ class Function(object): - |callback| the callback parameter to the function. There should be exactly one """ - def __init__(self, json): + def __init__(self, parent, json): self.name = json['name'] self.params = [] - self.description = json['description'] + self.description = json.get('description') self.callback = None + self.parent = parent for param in json['parameters']: if param.get('type') == 'function': - assert (not self.callback), self.name + " has more than one callback" - self.callback = Callback(param) + if self.callback: + raise ParseException(self.name + " has more than one callback") + self.callback = Callback(self, param) else: - self.params.append(Property(param['name'], param, + self.params.append(Property(self, param['name'], param, from_json=True)) class Property(object): @@ -135,9 +176,9 @@ class Property(object): ARRAY - |properties| the properties of an OBJECT parameter """ - def __init__(self, name, json, - from_json=False, - from_client=False): + + def __init__(self, parent, name, json, is_additional_properties=False, + from_json=False, from_client=False): """ Parameters: - |from_json| indicates that instances of the Type can originate from the @@ -147,11 +188,14 @@ class Property(object): users of generated code, such as top-level types and function results """ self.name = name - self._unix_name = _UnixName(self.name) + self._unix_name = UnixName(self.name) self._unix_name_used = False self.optional = json.get('optional', False) self.description = json.get('description') - if '$ref' in json: + self.parent = parent + if is_additional_properties: + self.type_ = PropertyType.ADDITIONAL_PROPERTIES + elif '$ref' in json: self.ref_type = json['$ref'] self.type_ = PropertyType.REF elif 'enum' in json: @@ -172,9 +216,9 @@ class Property(object): elif json_type == 'number': self.type_ = PropertyType.DOUBLE elif json_type == 'array': - self.item_type = Property(name + "Element", json['items'], - from_json, - from_client) + self.item_type = Property(self, name + "Element", json['items'], + from_json=from_json, + from_client=from_client) self.type_ = PropertyType.ARRAY elif json_type == 'object': self.type_ = PropertyType.OBJECT @@ -182,20 +226,20 @@ class Property(object): self.properties = {} self.from_json = from_json self.from_client = from_client - for key, val in json.get('properties', {}).items(): - self.properties[key] = Property(key, val, - from_json, - from_client) + type_ = Type(self, self.name, json) + self.properties = type_.properties + self.functions = type_.functions else: - raise NotImplementedError(json_type) + raise ParseException(self, 'type ' + json_type + ' not recognized') elif 'choices' in json: - assert len(json['choices']), 'Choices has no choices\n%s' % json + if not json['choices']: + raise ParseException('Choices has no choices') self.choices = {} self.type_ = PropertyType.CHOICES for choice_json in json['choices']: - choice = Property(self.name, choice_json, - from_json, - from_client) + choice = Property(self, self.name, choice_json, + from_json=from_json, + from_client=from_client) # A choice gets its unix_name set in # cpp_type_generator.GetExpandedChoicesInParams choice._unix_name = None @@ -203,7 +247,7 @@ class Property(object): choice.optional = True self.choices[choice.type_] = choice else: - raise NotImplementedError(json) + raise ParseException('Property has no type, $ref or choices') def GetUnixName(self): """Gets the property's unix_name. Raises AttributeError if not set. @@ -257,10 +301,31 @@ class PropertyType(object): CHOICES = _Info(False, "CHOICES") OBJECT = _Info(False, "OBJECT") ANY = _Info(False, "ANY") + ADDITIONAL_PROPERTIES = _Info(False, "ADDITIONAL_PROPERTIES") -def _UnixName(name): +def UnixName(name): """Returns the unix_style name for a given lowerCamelCase string. """ return '_'.join([x.lower() for x in re.findall('[A-Z][a-z_]*', name[0].upper() + name[1:])]) +class ParseException(Exception): + """Thrown when data in the model is invalid.""" + def __init__(self, parent, message): + hierarchy = GetModelHierarchy(parent) + hierarchy.append(message) + Exception.__init__( + self, 'Model parse exception at:\n' + '\n'.join(hierarchy)) + +def GetModelHierarchy(entity): + """Returns the hierarchy of the given model entity.""" + hierarchy = [] + while entity: + try: + hierarchy.append(entity.name) + except AttributeError: + hierarchy.append(repr(entity)) + entity = entity.parent + hierarchy.reverse() + return hierarchy + diff --git a/tools/json_schema_compiler/model_test.py b/tools/json_schema_compiler/model_test.py index 824e26a..3217498 100644 --- a/tools/json_schema_compiler/model_test.py +++ b/tools/json_schema_compiler/model_test.py @@ -37,11 +37,6 @@ class ModelTest(unittest.TestCase): self.assertEquals(["contains", "getAll", "remove", "request"], sorted(self.permissions.functions.keys())) - def testFunctionNoCallback(self): - del (self.permissions_json[0]['functions'][0]['parameters'][0]) - self.assertRaises(AssertionError, self.model.AddNamespace, - self.permissions_json[0], 'path/to/something.json') - def testFunctionNoCompile(self): # tabs.json has 2 functions marked as nocompile (connect, sendRequest) self.assertEquals(["captureVisibleTab", "create", "detectLanguage", @@ -57,9 +52,9 @@ class ModelTest(unittest.TestCase): self.assertEquals(['Window'], self.windows.types.keys()) def testHasProperties(self): - self.assertEquals(["active", "favIconUrl", "highlighted", "id", + self.assertEquals(["active", "fav_icon_url", "highlighted", "id", "incognito", "index", "pinned", "selected", "status", "title", "url", - "windowId"], + "window_id"], sorted(self.tabs.types['Tab'].properties.keys())) def testProperties(self): @@ -75,7 +70,7 @@ class ModelTest(unittest.TestCase): self.assertEquals(model.PropertyType.OBJECT, object_prop.type_) self.assertEquals( ["active", "highlighted", "pinned", "status", "title", "url", - "windowId", "windowType"], + "window_id", "window_type"], sorted(object_prop.properties.keys())) def testChoices(self): @@ -85,7 +80,7 @@ class ModelTest(unittest.TestCase): def testPropertyNotImplemented(self): (self.permissions_json[0]['types'][0] ['properties']['permissions']['type']) = 'something' - self.assertRaises(NotImplementedError, self.model.AddNamespace, + self.assertRaises(model.ParseException, self.model.AddNamespace, self.permissions_json[0], 'path/to/something.json') def testDescription(self): diff --git a/tools/json_schema_compiler/previewserver.py b/tools/json_schema_compiler/previewserver.py index c7ab262..384171e 100755 --- a/tools/json_schema_compiler/previewserver.py +++ b/tools/json_schema_compiler/previewserver.py @@ -196,7 +196,7 @@ updateEverything(); json_file_path) return type_generator = cpp_type_generator.CppTypeGenerator( - 'preview::api', namespace, cpp_util.Classname(filename).lower()) + 'preview::api', namespace, namespace.unix_name) # Get json file depedencies for dependency in api_defs[0].get('dependencies', []): diff --git a/tools/json_schema_compiler/test/additionalProperties.json b/tools/json_schema_compiler/test/additionalProperties.json new file mode 100644 index 0000000..03bbef8 --- /dev/null +++ b/tools/json_schema_compiler/test/additionalProperties.json @@ -0,0 +1,55 @@ +[ + { + "namespace": "additionalProperties", + "types": [ + { + "id": "AdditionalPropertiesType", + "type": "object", + "properties": { + "string": { + "type": "string", + "description": "Some string." + } + }, + "additionalProperties": { "type": "any" } + } + ], + "functions": [ + { + "name": "additionalProperties", + "type": "function", + "description": "Takes an object with additionalProperties", + "parameters": [ + { + "name": "paramObject", + "type": "object", + "properties": {}, + "additionalProperties": {"type": "any"} + } + ] + }, + { + "name": "returnAdditionalProperties", + "type": "function", + "description": "Returns an object with additionalProperties.", + "nodoc": "true", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "resultObject", + "type": "object", + "properties": { + "integer": {"type": "integer"} + }, + "additionalProperties": {"type": "any"} + } + ] + } + ] + } + ] + } +] diff --git a/tools/json_schema_compiler/test/additional_properties_unittest.cc b/tools/json_schema_compiler/test/additional_properties_unittest.cc new file mode 100644 index 0000000..b00836f --- /dev/null +++ b/tools/json_schema_compiler/test/additional_properties_unittest.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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 "tools/json_schema_compiler/test/additionalProperties.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using namespace test::api::additional_properties; + +TEST(JsonSchemaCompilerAdditionalPropertiesTest, + AdditionalPropertiesTypePopulate) { + { + scoped_ptr<ListValue> list_value(new ListValue()); + list_value->Append(Value::CreateStringValue("asdf")); + list_value->Append(Value::CreateIntegerValue(4)); + scoped_ptr<DictionaryValue> type_value(new DictionaryValue()); + type_value->SetString("string", "value"); + type_value->SetInteger("other", 9); + type_value->Set("another", list_value.release()); + scoped_ptr<AdditionalPropertiesType> type(new AdditionalPropertiesType()); + EXPECT_TRUE(AdditionalPropertiesType::Populate(*type_value, type.get())); + EXPECT_EQ("value", type->string); + EXPECT_TRUE(type_value->Remove("string", NULL)); + EXPECT_TRUE(type->additional_properties.Equals(type_value.get())); + } + { + scoped_ptr<DictionaryValue> type_value(new DictionaryValue()); + type_value->SetInteger("string", 3); + scoped_ptr<AdditionalPropertiesType> type(new AdditionalPropertiesType()); + EXPECT_FALSE(AdditionalPropertiesType::Populate(*type_value, type.get())); + } +} + +TEST(JsonSchemaCompilerAdditionalPropertiesTest, + AdditionalPropertiesParamsCreate) { + scoped_ptr<DictionaryValue> param_object_value(new DictionaryValue()); + param_object_value->SetString("str", "a"); + param_object_value->SetInteger("num", 1); + scoped_ptr<ListValue> params_value(new ListValue()); + params_value->Append(param_object_value->DeepCopy()); + scoped_ptr<AdditionalProperties::Params> params( + AdditionalProperties::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_TRUE(params->param_object.additional_properties.Equals( + param_object_value.get())); +} + +TEST(JsonSchemaCompilerAdditionalPropertiesTest, + ReturnAdditionalPropertiesResultCreate) { + scoped_ptr<DictionaryValue> result_object_value(new DictionaryValue()); + result_object_value->SetString("key", "value"); + scoped_ptr<ReturnAdditionalProperties::Result::ResultObject> result_object( + new ReturnAdditionalProperties::Result::ResultObject()); + result_object->integer = 5; + result_object->additional_properties.MergeDictionary( + result_object_value.get()); + scoped_ptr<Value> result( + ReturnAdditionalProperties::Result::Create(*result_object)); + DictionaryValue* result_dict = NULL; + EXPECT_TRUE(result->GetAsDictionary(&result_dict)); + + Value* int_temp_value = NULL; + int int_temp = 0; + EXPECT_TRUE(result_dict->Remove("integer", &int_temp_value)); + EXPECT_TRUE(int_temp_value->GetAsInteger(&int_temp)); + EXPECT_EQ(5, int_temp); + + EXPECT_TRUE(result_dict->Equals(result_object_value.get())); +} diff --git a/tools/json_schema_compiler/test/any.json b/tools/json_schema_compiler/test/any.json new file mode 100644 index 0000000..b19db82 --- /dev/null +++ b/tools/json_schema_compiler/test/any.json @@ -0,0 +1,54 @@ +[ + { + "namespace": "any", + "types": [ + { + "id": "AnyType", + "type": "object", + "properties": { + "any": { + "type": "any", + "description": "Any way you want it, that's the way you need it." + } + } + } + ], + "functions": [ + { + "name": "optionalAny", + "type": "function", + "description": "Takes an optional any param.", + "parameters": [ + { + "type": "any", + "name": "any", + "optional": true + }, + { + "type": "function", + "name": "callback", + "parameters": [] + } + ] + }, + { + "name": "returnAny", + "type": "function", + "description": "Returns any.", + "nodoc": "true", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "result", + "type": "any" + } + ] + } + ] + } + ] + } +] diff --git a/tools/json_schema_compiler/test/any_unittest.cc b/tools/json_schema_compiler/test/any_unittest.cc new file mode 100644 index 0000000..fc6ae4a --- /dev/null +++ b/tools/json_schema_compiler/test/any_unittest.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2012 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 "tools/json_schema_compiler/test/any.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using namespace test::api::any; + +TEST(JsonSchemaCompilerAnyTest, AnyTypePopulate) { + { + AnyType any_type; + scoped_ptr<DictionaryValue> any_type_value(new DictionaryValue()); + any_type_value->SetString("any", "value"); + EXPECT_TRUE(AnyType::Populate(*any_type_value, &any_type)); + scoped_ptr<Value> any_type_to_value(any_type.ToValue()); + EXPECT_TRUE(any_type_value->Equals(any_type_to_value.get())); + } + { + AnyType any_type; + scoped_ptr<DictionaryValue> any_type_value(new DictionaryValue()); + any_type_value->SetInteger("any", 5); + EXPECT_TRUE(AnyType::Populate(*any_type_value, &any_type)); + scoped_ptr<Value> any_type_to_value(any_type.ToValue()); + EXPECT_TRUE(any_type_value->Equals(any_type_to_value.get())); + } +} + +TEST(JsonSchemaCompilerAnyTest, OptionalAnyParamsCreate) { + { + scoped_ptr<ListValue> params_value(new ListValue()); + scoped_ptr<OptionalAny::Params> params( + OptionalAny::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_FALSE(params->any.get()); + } + { + scoped_ptr<ListValue> params_value(new ListValue()); + scoped_ptr<Value> param(Value::CreateStringValue("asdf")); + params_value->Append(param->DeepCopy()); + scoped_ptr<OptionalAny::Params> params( + OptionalAny::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_TRUE(params->any.get()); + EXPECT_TRUE(params->any->value().Equals(param.get())); + } + { + scoped_ptr<ListValue> params_value(new ListValue()); + scoped_ptr<Value> param(Value::CreateBooleanValue(true)); + params_value->Append(param->DeepCopy()); + scoped_ptr<OptionalAny::Params> params( + OptionalAny::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_TRUE(params->any.get()); + EXPECT_TRUE(params->any.get()); + EXPECT_TRUE(params->any->value().Equals(param.get())); + } +} diff --git a/tools/json_schema_compiler/test/arrays.json b/tools/json_schema_compiler/test/arrays.json index 26e5825..9734ff8 100644 --- a/tools/json_schema_compiler/test/arrays.json +++ b/tools/json_schema_compiler/test/arrays.json @@ -63,6 +63,23 @@ ] }, { + "name": "anyArray", + "type": "function", + "description": "Takes some Items.", + "parameters": [ + { + "name": "anys", + "type": "array", + "items": {"type": "any"} + }, + { + "name": "callback", + "type": "function", + "parameters": [] + } + ] + }, + { "name": "refArray", "type": "function", "description": "Takes some Items.", diff --git a/tools/json_schema_compiler/test/arrays_unittest.cc b/tools/json_schema_compiler/test/arrays_unittest.cc index 405fbb1..5676e25 100644 --- a/tools/json_schema_compiler/test/arrays_unittest.cc +++ b/tools/json_schema_compiler/test/arrays_unittest.cc @@ -93,6 +93,22 @@ TEST(JsonSchemaCompilerArrayTest, IntegerArrayParamsCreate) { EXPECT_EQ(8, params->nums[2]); } +TEST(JsonSchemaCompilerArrayTest, AnyArrayParamsCreate) { + scoped_ptr<ListValue> params_value(new ListValue()); + scoped_ptr<ListValue> any_array(new ListValue()); + any_array->Append(Value::CreateIntegerValue(1)); + any_array->Append(Value::CreateStringValue("test")); + any_array->Append(CreateItemValue(2)); + params_value->Append(any_array.release()); + scoped_ptr<AnyArray::Params> params( + AnyArray::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_EQ((size_t) 3, params->anys.size()); + int int_temp = 0; + EXPECT_TRUE(params->anys[0]->value().GetAsInteger(&int_temp)); + EXPECT_EQ(1, int_temp); +} + TEST(JsonSchemaCompilerArrayTest, RefArrayParamsCreate) { scoped_ptr<ListValue> params_value(new ListValue()); scoped_ptr<ListValue> item_array(new ListValue()); diff --git a/tools/json_schema_compiler/test/functionsOnTypes.json b/tools/json_schema_compiler/test/functionsOnTypes.json new file mode 100644 index 0000000..e8c8220 --- /dev/null +++ b/tools/json_schema_compiler/test/functionsOnTypes.json @@ -0,0 +1,74 @@ +[ + { + "namespace": "functionsOnTypes", + "types": [ + { + "id": "StorageArea", + "type": "object", + "functions": [ + { + "name": "get", + "type": "function", + "description": "Gets one or more items from storage.", + "parameters": [ + { + "name": "keys", + "choices": [ + { "type": "string" }, + { + "type": "object", + "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.", + "properties": {}, + "additionalProperties": { "type": "any" } + } + ], + "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object). An empty list or object will return an empty result object. Pass in <code>null</code> to get the entire contents of storage.", + "optional": true + }, + { + "name": "callback", + "type": "function", + "description": "Callback with storage items, or on failure (in which case lastError will be set).", + "parameters": [ + { + "name": "items", + "type": "object", + "properties": {}, + "additionalProperties": { "type": "any" }, + "description": "Object with items in their key-value mappings." + } + ] + } + ] + } + ] + }, + { + "id": "ChromeSetting", + "type": "object", + "description": "An interface which allows access to a Chrome browser setting.", + "functions": [ + { + "name": "get", + "type": "function", + "description": "Gets the value of a setting.", + "parameters": [ + { + "name": "details", + "type": "object", + "description": "What setting to consider.", + "properties": { + "incognito": { + "type": "boolean", + "optional": true, + "description": "Whether to return the setting that applies to the incognito session (default false)." + } + } + } + ] + } + ] + } + ] + } +] diff --git a/tools/json_schema_compiler/test/functions_on_types_unittest.cc b/tools/json_schema_compiler/test/functions_on_types_unittest.cc new file mode 100644 index 0000000..41a276c --- /dev/null +++ b/tools/json_schema_compiler/test/functions_on_types_unittest.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2012 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 "tools/json_schema_compiler/test/functionsOnTypes.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using namespace test::api::functions_on_types; + +TEST(JsonSchemaCompilerFunctionsOnTypesTest, StorageAreaGetParamsCreate) { + { + scoped_ptr<ListValue> params_value(new ListValue()); + scoped_ptr<StorageArea::Get::Params> params( + StorageArea::Get::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_EQ(StorageArea::Get::Params::KEYS_NONE, params->keys_type); + } + { + scoped_ptr<ListValue> params_value(new ListValue()); + params_value->Append(Value::CreateIntegerValue(9)); + scoped_ptr<StorageArea::Get::Params> params( + StorageArea::Get::Params::Create(*params_value)); + EXPECT_FALSE(params.get()); + } + { + scoped_ptr<ListValue> params_value(new ListValue()); + params_value->Append(Value::CreateStringValue("test")); + scoped_ptr<StorageArea::Get::Params> params( + StorageArea::Get::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_EQ(StorageArea::Get::Params::KEYS_STRING, params->keys_type); + EXPECT_EQ("test", *params->keys_string); + } + { + scoped_ptr<DictionaryValue> keys_object_value(new DictionaryValue()); + keys_object_value->SetInteger("integer", 5); + keys_object_value->SetString("string", "string"); + scoped_ptr<ListValue> params_value(new ListValue()); + params_value->Append(keys_object_value->DeepCopy()); + scoped_ptr<StorageArea::Get::Params> params( + StorageArea::Get::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_EQ(StorageArea::Get::Params::KEYS_OBJECT, params->keys_type); + EXPECT_TRUE( + keys_object_value->Equals(¶ms->keys_object->additional_properties)); + } +} + +TEST(JsonSchemaCompilerFunctionsOnTypesTest, StorageAreaGetResultCreate) { + scoped_ptr<StorageArea::Get::Result::Items> items( + new StorageArea::Get::Result::Items()); + items->additional_properties.SetDouble("asdf", 0.1); + items->additional_properties.SetString("sdfg", "zxcv"); + scoped_ptr<Value> result_value(StorageArea::Get::Result::Create(*items)); + EXPECT_TRUE(result_value->Equals(&items->additional_properties)); +} + +TEST(JsonSchemaCompilerFunctionsOnTypesTest, ChromeSettingGetParamsCreate) { + scoped_ptr<DictionaryValue> details_value(new DictionaryValue()); + details_value->SetBoolean("incognito", true); + scoped_ptr<ListValue> params_value(new ListValue()); + params_value->Append(details_value.release()); + scoped_ptr<ChromeSetting::Get::Params> params( + ChromeSetting::Get::Params::Create(*params_value)); + EXPECT_TRUE(params.get()); + EXPECT_TRUE(*params->details.incognito); +} diff --git a/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp b/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp index 8f32acb..58e50b7 100644 --- a/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp +++ b/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp @@ -10,10 +10,13 @@ 'variables': { 'chromium_code': 1, 'json_schema_files': [ + 'any.json', + 'additionalProperties.json', 'arrays.json', 'choices.json', 'crossref.json', 'enums.json', + 'functionsOnTypes.json', 'objects.json', 'simple_api.json', ], diff --git a/tools/json_schema_compiler/util.cc b/tools/json_schema_compiler/util.cc index 468df83..9d8abd5 100644 --- a/tools/json_schema_compiler/util.cc +++ b/tools/json_schema_compiler/util.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "tools/json_schema_compiler/any.h" #include "tools/json_schema_compiler/util.h" #include "base/values.h" @@ -26,11 +27,21 @@ bool GetItemFromList(const ListValue& from, int index, std::string* out) { } bool GetItemFromList(const ListValue& from, int index, + linked_ptr<any::Any>* out) { + Value* value = NULL; + if (!from.Get(index, &value)) + return false; + scoped_ptr<any::Any> any_object(new any::Any()); + any_object->Init(*value); + *out = linked_ptr<any::Any>(any_object.release()); + return true; +} + +bool GetItemFromList(const ListValue& from, int index, linked_ptr<base::DictionaryValue>* out) { DictionaryValue* dict = NULL; - if (!from.GetDictionary(index, &dict)) { + if (!from.GetDictionary(index, &dict)) return false; - } *out = linked_ptr<DictionaryValue>(dict->DeepCopy()); return true; } @@ -51,6 +62,10 @@ void AddItemToList(const linked_ptr<base::DictionaryValue>& from, base::ListValue* out) { out->Append(static_cast<Value*>(from->DeepCopy())); } +void AddItemToList(const linked_ptr<any::Any>& from, + base::ListValue* out) { + out->Append(from->value().DeepCopy()); +} } // namespace api_util } // namespace extensions diff --git a/tools/json_schema_compiler/util.h b/tools/json_schema_compiler/util.h index f71c93c..c3e3b1d 100644 --- a/tools/json_schema_compiler/util.h +++ b/tools/json_schema_compiler/util.h @@ -14,6 +14,11 @@ #include "base/values.h" namespace json_schema_compiler { + +namespace any { +class Any; +} + namespace util { // Creates a new item at |out| from |from|[|index|]. These are used by template @@ -24,6 +29,8 @@ bool GetItemFromList(const ListValue& from, int index, double* out); bool GetItemFromList(const ListValue& from, int index, std::string* out); bool GetItemFromList(const ListValue& from, int index, linked_ptr<base::DictionaryValue>* out); +bool GetItemFromList(const ListValue& from, int index, + linked_ptr<any::Any>* out); // This template is used for types generated by tools/json_schema_compiler. template<class T> @@ -119,6 +126,8 @@ void AddItemToList(const double from, base::ListValue* out); void AddItemToList(const std::string& from, base::ListValue* out); void AddItemToList(const linked_ptr<base::DictionaryValue>& from, base::ListValue* out); +void AddItemToList(const linked_ptr<any::Any>& from, + base::ListValue* out); // This template is used for types generated by tools/json_schema_compiler. template<class T> @@ -166,7 +175,7 @@ scoped_ptr<Value> CreateValueFromOptionalArray( return scoped_ptr<Value>(); } -} // namespace api_util -} // namespace extensions +} // namespace util +} // namespace json_schema_compiler #endif // TOOLS_JSON_SCHEMA_COMPILER_UTIL_H__ |