Commit feba21e3 authored by calamity@chromium.org's avatar calamity@chromium.org

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
parent 7ae87817
......@@ -17,6 +17,9 @@
'rule_name': 'genapi',
'extension': 'json',
'inputs': [
'<(api_gen_dir)/any.cc',
'<(api_gen_dir)/any.h',
'<(api_gen_dir)/any_helper.py',
'<(api_gen_dir)/cc_generator.py',
'<(api_gen_dir)/code.py',
'<(api_gen_dir)/compiler.py',
......
......@@ -2137,10 +2137,13 @@
'tools/convert_dict/convert_dict_unittest.cc',
'../testing/gtest_mac_unittest.mm',
'../third_party/cld/encodings/compact_lang_det/compact_lang_det_unittest_small.cc',
'../tools/json_schema_compiler/test/additional_properties_unittest.cc',
'../tools/json_schema_compiler/test/any_unittest.cc',
'../tools/json_schema_compiler/test/arrays_unittest.cc',
'../tools/json_schema_compiler/test/choices_unittest.cc',
'../tools/json_schema_compiler/test/crossref_unittest.cc',
'../tools/json_schema_compiler/test/enums_unittest.cc',
'../tools/json_schema_compiler/test/functions_on_types_unittest.cc',
'../tools/json_schema_compiler/test/objects_unittest.cc',
'../tools/json_schema_compiler/test/simple_api_unittest.cc',
'../ui/views/test/test_views_delegate.cc',
......
......@@ -11,7 +11,8 @@
{
"name": "propertyNames",
"type": "array",
"item": {"type": "string", "description": "Chrome OS Property name"}
"description": "Chrome OS Property names",
"items": {"type": "string"}
},
{
"name": "callback",
......
[
{
"namespace": "devtools.inspectedWindow",
"nocompile": true,
"types": [
{
"id": "Resource",
......@@ -188,6 +189,7 @@
},
{
"namespace": "devtools.panels",
"nocompile": true,
"types": [
{
"id": "ElementsPanel",
......@@ -496,6 +498,7 @@
},
{
"namespace": "devtools.network",
"nocompile": true,
"types": [
{
"id": "Request",
......@@ -575,6 +578,7 @@
},
{
"namespace": "experimental.devtools.console",
"nocompile": true,
"functions": [
{
"name": "addMessage",
......@@ -667,6 +671,7 @@
},
{
"namespace": "experimental.devtools.audits",
"nocompile": true,
"functions": [
{
"name": "addCategory",
......
......@@ -149,10 +149,8 @@
{
"name": "control",
"description": "Details of the currently focused control, or null if nothing is focused.",
"choices": [
{ "$ref": "AccessibilityObject" },
{ "type": "null" }
]
"$ref": "AccessibilityObject",
"optional": true
}
]
}
......
......@@ -68,7 +68,8 @@
"type": "function",
"name": "callback",
"optional": true,
"description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>."
"description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>.",
"parameters": []
}
]
},
......@@ -101,7 +102,8 @@
"type": "function",
"name": "callback",
"optional": true,
"description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>."
"description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>.",
"parameters": []
}
]
},
......@@ -127,7 +129,8 @@
"type": "function",
"name": "callback",
"optional": true,
"description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>."
"description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>.",
"parameters": []
}
]
}
......
......@@ -208,7 +208,12 @@
"description": "Sets a preference value with the store login.",
"parameters": [
{ "name": "login", "type": "string" },
{ "name": "callback", "type": "function", "optional": "true" }
{
"name": "callback",
"type": "function",
"optional": "true",
"parameters": []
}
]
},
{
......
// 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
// 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__
# 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
......@@ -10,6 +10,7 @@
'target_name': 'api_gen_util',
'type': 'static_library',
'sources': [
'any.cc',
'util.cc',
],
'dependencies': ['<(DEPTH)/base/base.gyp:base'],
......
This diff is collapsed.
......@@ -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
......@@ -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()
......@@ -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,
......
......@@ -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_)
......
......@@ -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')
......
......@@ -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_):
......
......@@ -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('};')
......
......@@ -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
......@@ -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):
......
......@@ -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', []):
......
[
{
"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"}
}
]
}
]
}
]
}
]
// 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()));
}
[
{
"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"
}
]
}
]
}
]
}
]
// 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()));
}
}
......@@ -62,6 +62,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",
......
......@@ -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());
......
[
{
"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)."
}
}
}
]
}
]
}
]
}
]
// 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(&params->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);
}
......@@ -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',
],
......
......@@ -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"
......@@ -25,12 +26,22 @@ bool GetItemFromList(const ListValue& from, int index, std::string* out) {
return from.GetString(index, 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
......@@ -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__
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment