Chameleon swagger support

Change-Id: I63b8dc7b31d5e87aa0e5153da302537d90ff733e
diff --git a/.gitignore b/.gitignore
index 988402a..156a805 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 
 # PyCharm
 .idea
+exportToHTML
 
 # Emacs
 *~
diff --git a/Dockerfile.chameleon b/Dockerfile.chameleon
index ea3e53a..fdf8c8c 100644
--- a/Dockerfile.chameleon
+++ b/Dockerfile.chameleon
@@ -23,7 +23,8 @@
 
 # Install protoc version 3.0.0; this is not yet the supported
 # version on xenial, so we need to "backport" it
-RUN apt-get install -y zlib1g-dev wget && \
+RUN apt-get update && \
+    apt-get install -y zlib1g-dev wget && \
     wget http://ftp.us.debian.org/debian/pool/main/p/protobuf/libprotoc10_3.0.0-7_amd64.deb && \
     wget http://ftp.us.debian.org/debian/pool/main/p/protobuf/libprotobuf-lite10_3.0.0-7_amd64.deb && \
     wget http://ftp.us.debian.org/debian/pool/main/p/protobuf/libprotobuf-dev_3.0.0-7_amd64.deb && \
diff --git a/chameleon/grpc_client/grpc_client.py b/chameleon/grpc_client/grpc_client.py
index e9ce5c6..790bab3 100644
--- a/chameleon/grpc_client/grpc_client.py
+++ b/chameleon/grpc_client/grpc_client.py
@@ -190,8 +190,11 @@
         :return: None
         """
         google_api_dir = os.path.abspath(os.path.join(
-            os.path.dirname(__file__),
-            '../protos/third_party'
+            os.path.dirname(__file__), '../protos/third_party'
+        ))
+
+        chameleon_base_dir = os.path.abspath(os.path.join(
+            os.path.dirname(__file__), '../..'
         ))
 
         for fname in [f for f in os.listdir(self.work_dir)
@@ -208,11 +211,14 @@
                 '--grpc_python_out=. '
                 '--plugin=protoc-gen-gw=%s/gw_gen.py '
                 '--gw_out=. '
+                '--plugin=protoc-gen-swagger=%s/swagger_gen.py '
+                '--swagger_out=. '
                 '%s' % (
                     self.work_dir,
                     ':'.join([os.environ['PATH'], self.plugin_dir]),
+                    ':'.join([google_api_dir, chameleon_base_dir]),
                     google_api_dir,
-                    google_api_dir,
+                    self.plugin_dir,
                     self.plugin_dir,
                     fname)
             )
diff --git a/chameleon/protoc_plugins/descriptor.desc b/chameleon/protoc_plugins/descriptor.desc
new file mode 100644
index 0000000..ac12c36
--- /dev/null
+++ b/chameleon/protoc_plugins/descriptor.desc
Binary files differ
diff --git a/chameleon/protoc_plugins/descriptor_parser.py b/chameleon/protoc_plugins/descriptor_parser.py
new file mode 100644
index 0000000..c23f497
--- /dev/null
+++ b/chameleon/protoc_plugins/descriptor_parser.py
@@ -0,0 +1,164 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os
+from collections import OrderedDict
+
+from google.protobuf import descriptor_pb2
+from google.protobuf.descriptor import FieldDescriptor, Descriptor
+from google.protobuf.message import Message
+
+
+class InvalidDescriptorError(Exception): pass
+
+
+class DescriptorParser(object):
+    """
+    Used to parse protobuf FileDescriptor objects into native Python
+    data structures (nested dict/list/intrinsic values. Two of the typical
+    sources of FileDescriptor objects are:
+    1. CodeGeneratorRequest, used as binary input to any protoc plugin,
+       contains a list of these FileDescriptor objects (under the
+       proto_file attribute)
+    2. FileDescriptorSet, as saved by protoc when using the -o option.
+
+    An important feature of the parser is that it can process the source
+    code annotations and can fold comments into the relevant defintions
+    present in the proto file.
+
+    Usage (in a protoc plugin):
+    >>> request = plugin.CodeGeneratorRequest()
+    >>> request.ParseFromString(sys.stdin.read())
+    >>> parser = DescriptorParser()
+    >>> for proto_file in request.proto_file:
+    >>>     parsed_data = parser.parse_file_descriptor()
+    >>>     print json.dumps(parsed_data, indent=4)
+    """
+
+    meta = None
+
+    def __init__(self):
+        if DescriptorParser.meta is None:
+            DescriptorParser.meta = self.load_meta_descriptor()
+
+    def load_meta_descriptor(self):
+        """
+        Load the protobuf version of descriptor.proto to use it in
+        decoding protobuf paths.
+        """
+        fpath = os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                             'descriptor.desc'))
+        with open(fpath, 'r') as f:
+            blob = f.read()
+        proto = descriptor_pb2.FileDescriptorSet()
+        proto.ParseFromString(blob)
+        assert len(proto.file) == 1
+        return proto.file[0]
+
+    parser_table = {
+        unicode: lambda x: x,
+        int: lambda x: x,
+        bool: lambda x: x,
+    }
+
+    def parse(self, o, type_tag_name=None):
+        if isinstance(o, Message):
+            return self.parse_message(o, type_tag_name)
+        else:
+            return self.parser_table[type(o)](o)
+
+    def parse_message(self, m, type_tag_name=None):
+        assert isinstance(m, Message)
+        d = OrderedDict()
+        for field, value in m.ListFields():
+            assert isinstance(field, FieldDescriptor)
+            if field.label in (1, 2):
+                d[field.name] = self.parse(value, type_tag_name)
+            elif field.label == 3:
+                d[field.name] = [self.parse(x, type_tag_name) for x in
+                                 value]
+            else:
+                raise InvalidDescriptorError()
+
+        if type_tag_name is not None:
+            d[type_tag_name] = m.DESCRIPTOR.full_name.strip('.')
+
+        return d
+
+    def parse_file_descriptor(self, descriptor,
+                              type_tag_name=None,
+                              fold_comments=False):
+
+        d = self.parse(descriptor, type_tag_name=type_tag_name)
+
+        if fold_comments:
+            locations = d.get('source_code_info', {}).get('location', [])
+            for location in locations:
+                path = location.get('path', [])
+                comments = ''.join([
+                    location.get('leading_comments', '').strip(' '),
+                    location.get('trailing_comments', '').strip(' '),
+                    ''.join(block.strip(' ') for block
+                            in
+                            location.get('leading_detached_comments', ''))
+                ]).strip()
+
+                # ignore locations with no comments
+                if not comments:
+                    continue
+
+                # we ignore path with odd number of entries, since these do
+                # not address our schema nodes, but rather the meta schema
+                if (len(path) % 2 == 0):
+                    node = self.find_node_by_path(
+                        path, self.meta.DESCRIPTOR, d)
+                    assert isinstance(node, dict)
+                    node['_description'] = comments
+
+            # remove source_code_info
+            del d['source_code_info']
+
+        return d
+
+    def parse_file_descriptors(self, descriptors,
+                              type_tag_name=None,
+                              fold_comments=False):
+        return [self.parse_file_descriptor(descriptor,
+                                           type_tag_name=type_tag_name,
+                                           fold_comments=fold_comments)
+                for descriptor in descriptors]
+
+    def find_node_by_path(self, path, meta, o):
+        # stop recursion when path is empty
+        if not path:
+            return o
+
+        # sanity check
+        assert len(path) >= 2
+        assert isinstance(meta, Descriptor)
+        assert isinstance(o, dict)
+
+        # find field name, then actual field
+        field_number = path.pop(0)
+        field_def = meta.fields_by_number[field_number]
+        field = o[field_def.name]
+
+        # field must be a list, extract entry with given index
+        assert isinstance(field, list)  # expected to be a list field
+        index = path.pop(0)
+        child_o = field[index]
+
+        child_meta = field_def.message_type
+        return self.find_node_by_path(path, child_meta, child_o)
diff --git a/chameleon/protoc_plugins/gw_gen.py b/chameleon/protoc_plugins/gw_gen.py
index e6dac24..77f74fa 100755
--- a/chameleon/protoc_plugins/gw_gen.py
+++ b/chameleon/protoc_plugins/gw_gen.py
@@ -14,19 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import os
 import sys
 
-from google.protobuf import descriptor as _descriptor
 from google.protobuf.compiler import plugin_pb2 as plugin
-from google.protobuf.descriptor import FieldDescriptor
-from google.protobuf.descriptor_pb2 import ServiceDescriptorProto, MethodOptions
-from google.protobuf.message import Message
+from google.protobuf.descriptor_pb2 import ServiceDescriptorProto, \
+    MethodOptions
 from jinja2 import Template
 from simplejson import dumps
 
-# without this import, http method annotations would not be recognized:
-from google.api import annotations_pb2, http_pb2
+from chameleon.protos.third_party.google.api import annotations_pb2, http_pb2
+_ = annotations_pb2, http_pb2  # to keep import line from being optimized out
 
 
 template = Template("""
diff --git a/voltha/northbound/grpc/grpc_introspect.py b/chameleon/protoc_plugins/protobuf_introspect.py
similarity index 98%
rename from voltha/northbound/grpc/grpc_introspect.py
rename to chameleon/protoc_plugins/protobuf_introspect.py
index 366b725..408a6da 100755
--- a/voltha/northbound/grpc/grpc_introspect.py
+++ b/chameleon/protoc_plugins/protobuf_introspect.py
@@ -15,9 +15,11 @@
 # limitations under the License.
 #
 
-"""Load a protobuf description file an make sense of it"""
+"""
+Load a protobuf description file or protoc CodeGeneratorRequest an make
+sense of it
+"""
 
-# This is very experimental
 import os
 import inspect
 from collections import OrderedDict
diff --git a/chameleon/protoc_plugins/swagger_gen.py b/chameleon/protoc_plugins/swagger_gen.py
new file mode 100755
index 0000000..0342d87
--- /dev/null
+++ b/chameleon/protoc_plugins/swagger_gen.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import sys
+
+from google.protobuf.compiler import plugin_pb2 as plugin
+from simplejson import dumps
+
+# without this import, http method annotations would not be recognized:
+from google.api import annotations_pb2, http_pb2
+
+from chameleon.protoc_plugins.descriptor_parser import DescriptorParser
+from swagger_template import native_descriptors_to_swagger
+
+
+def generate_code(request, response):
+
+    assert isinstance(request, plugin.CodeGeneratorRequest)
+
+    parser = DescriptorParser()
+    native_data = parser.parse_file_descriptors(request.proto_file,
+                                                type_tag_name='_type',
+                                                fold_comments=True)
+    swagger = native_descriptors_to_swagger(native_data)
+
+    # generate the native decoded schema as json
+    # f = response.file.add()
+    # f.name = proto_file.name.replace('.proto', '.native.json')
+    # f.content = dumps(data)
+
+    # generate the real swagger.json file
+    f = response.file.add()
+    f.name = 'swagger.json'
+    f.content = dumps(swagger)
+
+
+if __name__ == '__main__':
+
+    if len(sys.argv) >= 2:
+        # read input from file, to allow troubleshooting
+        with open(sys.argv[1], 'r') as f:
+            data = f.read()
+    else:
+        # read input from stdin
+        data = sys.stdin.read()
+
+    # parse request
+    request = plugin.CodeGeneratorRequest()
+    request.ParseFromString(data)
+
+    # create response object
+    response = plugin.CodeGeneratorResponse()
+
+    # generate the output and the response
+    generate_code(request, response)
+
+    # serialize the response
+    output = response.SerializeToString()
+
+    # write response to stdout
+    sys.stdout.write(output)
diff --git a/chameleon/protoc_plugins/swagger_template.py b/chameleon/protoc_plugins/swagger_template.py
new file mode 100644
index 0000000..d93c8da
--- /dev/null
+++ b/chameleon/protoc_plugins/swagger_template.py
@@ -0,0 +1,468 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import re
+from collections import OrderedDict
+from copy import copy
+
+from google.protobuf.descriptor import FieldDescriptor
+
+re_path_param = re.compile(r'/{([^{]+)}')
+re_segment = re.compile(r'/(?P<absolute>[^{}/]+)|(?P<symbolic>{[^}]+})')
+
+
+class DuplicateMethodAndPathError(Exception): pass
+class ProtobufCompilationFailedError(Exception): pass
+class InvalidPathArgumentError(Exception): pass
+
+
+def native_descriptors_to_swagger(native_descriptors):
+    """
+    Generate a swagger data dict from the native descriptors extracted
+    from protobuf file(s).
+    :param native_descriptors:
+        Dict as extracted from proto file descriptors.
+        See DescriptorParser and its parse_file_descriptors() method.
+    :return: dict ready to be serialized to JSON as swagger.json file.
+    """
+
+    # gather all top-level and nested message type definitions and build map
+    message_types_dict = gather_all_message_types(native_descriptors)
+    message_type_names = set(message_types_dict.iterkeys())
+
+    # create similar map for all top-level and nested enum definitions
+    enum_types_dict = gather_all_enum_types(native_descriptors)
+    enum_type_names = set(enum_types_dict.iterkeys())
+
+    # make sure none clashes and generate set of all names (for sanity checks)
+    assert not message_type_names.intersection(enum_type_names)
+    all_type_names = message_type_names.union(enum_type_names)
+    all_types = {}
+    all_types.update(message_types_dict)
+    all_types.update(enum_types_dict)
+
+    # gather all method definitions and collect all referenced input/output
+    # types
+    types_referenced, methods_dict = gather_all_methods(native_descriptors)
+
+    # process all directly and indirectly referenced types into JSON schema
+    # type definitions
+    definitions = generate_definitions(types_referenced, all_types)
+
+    # process all method and generate the swagger path entries
+    paths = generate_paths(methods_dict, definitions)
+
+    # static part
+    # last descriptor is assumed to be the top-most one
+    root_descriptor = native_descriptors[-1]
+    swagger = {
+        'swagger': "2.0",
+        'info': {
+            'title': root_descriptor['name'],
+            'version': "version not set"
+        },
+        'schemes': ["http", "https"],
+        'consumes': ["application/json"],
+        'produces': ["application/json"],
+        'paths': paths,
+        'definitions': definitions
+    }
+
+    return swagger
+
+
+def gather_all_message_types(descriptors):
+    return dict(
+        (full_name, message_type)
+        for full_name, message_type
+        in iterate_message_types(descriptors)
+    )
+
+
+def gather_all_enum_types(descriptors):
+    return dict(
+        (full_name, enum_type)
+        for full_name, enum_type
+        in iterate_enum_types(descriptors)
+    )
+
+
+def gather_all_methods(descriptors):
+    types_referenced = set()
+    methods = OrderedDict()
+    for full_name, service, method in iterate_methods(descriptors):
+        methods[full_name] = (service, method)
+        types_referenced.add(method['input_type'].strip('.'))
+        types_referenced.add(method['output_type'].strip('.'))
+    return types_referenced, methods
+
+
+def iterate_methods(descriptors):
+    for descriptor in descriptors:
+        package = descriptor['package']
+        for service in descriptor.get('service', []):
+            service_prefix = package + '.' + service['name']
+            for method in service.get('method', []):
+                # skip methods that do not have http options
+                options = method['options']
+                if options.has_key('http'):
+                    full_name = service_prefix + '.' + method['name']
+                    yield full_name, service, method
+
+
+def iterate_for_type_in(message_types, prefix):
+    for message_type in message_types:
+        full_name = prefix + '.' + message_type['name']
+        yield full_name, message_type
+        for nested_full_name, nested in iterate_for_type_in(
+                message_type.get('nested_type', []), full_name):
+            yield nested_full_name, nested
+
+
+def iterate_message_types(descriptors):
+    for descriptor in descriptors:
+        package = descriptor['package']
+        top_types = descriptor.get('message_type', [])
+        for full_name, message_type in iterate_for_type_in(top_types, package):
+            yield full_name, message_type
+
+
+def iterate_enum_types(descriptors):
+    for descriptor in descriptors:
+        package = descriptor['package']
+        for enum in descriptor.get('enum_type', []):
+            enum_full_name = package + '.' + enum['name']
+            yield enum_full_name, enum
+        top_types = descriptor.get('message_type', [])
+        for full_name, message_type in iterate_for_type_in(top_types, package):
+            for enum in message_type.get('enum_type', []):
+                enum_full_name = full_name + '.' + enum['name']
+                yield enum_full_name, enum
+
+
+def generate_definitions(types_referenced, types):
+    """Walk all the referenced types and for each, generate a JSON schema
+       definition. These may also refer to other types, so keep the needed
+       set up-to-date.
+    """
+    definitions = {}
+    wanted = copy(types_referenced)
+    while wanted:
+        full_name = wanted.pop()
+        type = types[full_name]
+        definition, types_referenced = make_definition(type, types)
+        definitions[full_name] = definition
+        for type_referenced in types_referenced:
+            if not definitions.has_key(type_referenced):
+                wanted.add(type_referenced)
+    return definitions
+
+
+def make_definition(type, types):
+    if type['_type'] == 'google.protobuf.EnumDescriptorProto':
+        return make_enum_definition(type), set()
+    else:
+        return make_object_definition(type, types)
+
+
+def make_enum_definition(type):
+
+    def make_value_desc(enum_value):
+        txt = ' - {}'.format(enum_value['name'])
+        description = enum_value.get('_description', '')
+        if description:
+            txt += ': {}'.format(description)
+        return txt
+
+    string_values = [v['name'] for v in type['value']]
+    default = type['value'][0]['name']
+    description = (
+        (type.get('_description', '') or type['name'])
+        + '\nValid values:\n'
+        + '\n'.join(make_value_desc(v) for v in type['value'])
+    )
+
+    definition = {
+        'type': 'string',
+        'enum': string_values,
+        'default': default,
+        'description': description
+    }
+
+    return definition
+
+
+def make_object_definition(type, types):
+
+    definition = {
+        'type': 'object'
+    }
+
+    referenced = set()
+    properties = {}
+    for field in type.get('field', []):
+        field_name, property, referenced_by_field = make_property(field, types)
+        properties[field_name] = property
+        referenced.update(referenced_by_field)
+
+    if properties:
+        definition['properties'] = properties
+
+    if type.has_key('_description'):
+        definition['description'] = type['_description']
+
+    return definition, referenced
+
+
+def make_property(field, types):
+
+    referenced = set()
+
+    repeated = field['label'] == FieldDescriptor.LABEL_REPEATED
+
+    def check_if_map_entry(type_name):
+        type = types[type_name]
+        if type.get('options', {}).get('map_entry', False):
+            _, property, __ = make_property(type['field'][1], types)
+            return property
+
+    if field['type'] == FieldDescriptor.TYPE_MESSAGE:
+
+        type_name = field['type_name'].strip('.')
+
+        maybe_map_value_type = check_if_map_entry(type_name)
+        if maybe_map_value_type:
+            # map-entries are inlined
+            repeated = False
+            property = {
+                'type': 'object',
+                'additionalProperties': maybe_map_value_type
+            }
+
+        elif type_name == 'google.protobuf.Timestamp':
+            # time-stamp is mapped back to JSON schema date-time string
+            property = {
+                'type': 'string',
+                'format': 'date-time'
+            }
+
+        else:
+            # normal nested object field
+            property = {
+                '$ref': '#/definitions/{}'.format(type_name)
+            }
+            referenced.add(type_name)
+
+    elif field['type'] == FieldDescriptor.TYPE_ENUM:
+        type_name = field['type_name'].strip('.')
+        property = {
+            '$ref': '#/definitions/{}'.format(type_name)
+        }
+        referenced.add(type_name)
+
+    elif field['type'] == FieldDescriptor.TYPE_GROUP:
+        raise NotImplementedError()
+
+    else:
+        _type, format = TYPE_MAP[field['type']]
+        property = {
+            'type': _type,
+            'format': format
+        }
+
+    if repeated:
+        property = {
+            'type': 'array',
+            'items': property
+        }
+
+    if field.has_key('_description'):
+        property['description'] = field['_description']
+
+    return field['name'], property, referenced
+
+
+def generate_paths(methods_dict, definitions):
+
+    paths = {}
+
+    def _iterate():
+        for full_name, (service, method) in methods_dict.iteritems():
+            http_option = method['options']['http']
+            yield service, method, http_option
+            for binding in http_option.get('additional_bindings', []):
+                yield service, method, binding
+
+    def prune_path(path):
+        """rid '=<stuff>' pattern from path symbolic segments"""
+        segments = re_segment.findall(path)
+        pruned_segments = []
+        for absolute, symbolic in segments:
+            if symbolic:
+                full_symbol = symbolic[1:-1]
+                pruned_symbol = full_symbol.split('=', 2)[0]
+                pruned_segments.append('{' + pruned_symbol + '}')
+            else:
+                pruned_segments.append(absolute)
+
+        return '/' + '/'.join(pruned_segments)
+
+    def lookup_input_type(input_type_name):
+        return definitions[input_type_name.strip('.')]
+
+    def lookup_type(input_type, field_name):
+        local_field_name, _, rest = field_name.partition('.')
+        properties = input_type['properties']
+        if not properties.has_key(local_field_name):
+            raise InvalidPathArgumentError(
+                'Input type has no field {}'.format(field_name))
+        field = properties[local_field_name]
+        if rest:
+            field_type = field.get('type', 'object')
+            assert field_type == 'object', (
+                'Nested field name "%s" refers to field that of type "%s" '
+                '(.%s should be nested object field)'
+                % (field_name, field_type, local_field_name))
+            ref = field['$ref']
+            assert ref.startswith('#/definitions/')
+            type_name = ref.replace('#/definitions/', '')
+            nested_input_type = lookup_input_type(type_name)
+            return lookup_type(nested_input_type, rest)
+        else:
+            return field['type'], field['format']
+
+    def make_entry(service, method, http):
+        parameters = []
+        verb = None
+        for verb_candidate in ('get', 'delete', 'patch', 'post', 'put'):
+            if verb_candidate in http:
+                verb, path = verb_candidate, http[verb_candidate]
+                break
+        if 'custom' in http:
+            assert verb is None
+            verb = http['custom']['kind']
+            path = http['custom']['path']
+        assert verb is not None
+        path = prune_path(path)
+
+        # for each symbolic segment in path, add a path parameter entry
+        input_type = lookup_input_type(method['input_type'])
+        for segment in re_path_param.findall(path):
+            symbol = segment.split('=')[0]
+            _type, format = lookup_type(input_type, symbol)
+            parameters.append({
+                'in': 'path',
+                'name': symbol,
+                'required': True,
+                'type': _type,
+                'format': format
+            })
+
+        if 'body' in http:
+            if 'body' in http:  # TODO validate if body lists fields
+                parameters.append({
+                    'in': 'body',
+                    'name': 'body',
+                    'required': True,
+                    'schema': {'$ref': '#/definitions/{}'.format(
+                        method['input_type'].strip('.'))}
+                })
+
+        entry = {
+            'operationId': method['name'],
+            'tags': [service['name'],],
+            'responses': {
+                '200': {  # TODO: code is 201 and 209 in POST/DELETE?
+                    'description': unicode(""),  # TODO: ever filled by proto?
+                    'schema': {
+                        '$ref': '#/definitions/{}'.format(
+                        method['output_type'].strip('.'))
+                    }
+                },
+                # TODO shall we prefill with standard error (verb specific),
+                # such as 400, 403, 404, 409, 509, 500, 503 etc.
+            }
+        }
+
+        if parameters:
+            entry['parameters'] = parameters
+
+        summary, description = extract_summary_and_description(method)
+        if summary:
+            entry['summary'] = summary
+        if description:
+            entry['description'] = description
+
+        return path, verb, entry
+
+    for service, method, http in _iterate():
+        path, verb, entry = make_entry(service, method, http)
+        path_dict = paths.setdefault(path, {})
+        if verb in path_dict:
+            raise DuplicateMethodAndPathError(
+                'There is already a {} method defined for path ({})'.format(
+                verb, path))
+        path_dict[verb] = entry
+
+    return paths
+
+
+def extract_summary_and_description(obj):
+    """
+    Break raw _description field (if present) into a summary line and/or
+    detailed description text as follows:
+    * if text is a single line (not counting white-spaces), then it is a
+      summary and there is no detailed description.
+    * if text starts with a non-empty line followied by an empty line followed
+      by at least one non-empty line, that the 1s line is the summary and the
+      lines after the empty line is the description.
+    * in all other cases the text is considered a description and no summary
+      is generated.
+    """
+    assert isinstance(obj, dict)
+    summary, description = None, None
+    text = obj.get('_description', '')
+    if text:
+        s, blank, d = (text.split('\n', 2) + ['', ''])[:3]  # so we can demux
+        if not blank.strip():
+            summary = s
+            if d.strip():
+                description = d
+        else:
+            description = text
+
+    return summary, description
+
+
+TYPE_MAP = {
+        FieldDescriptor.TYPE_BOOL: ('boolean', 'boolean'),
+        FieldDescriptor.TYPE_BYTES: ('string', 'byte'),
+        FieldDescriptor.TYPE_DOUBLE: ('number', 'double'),
+        FieldDescriptor.TYPE_ENUM: ('string', 'string'),
+        FieldDescriptor.TYPE_FIXED32: ('integer', 'int64'),
+        FieldDescriptor.TYPE_FIXED64: ('string', 'uint64'),
+        FieldDescriptor.TYPE_FLOAT: ('number', 'float'),
+        FieldDescriptor.TYPE_INT32: ('integer', 'int32'),
+        FieldDescriptor.TYPE_INT64: ('string', 'int64'),
+        FieldDescriptor.TYPE_SFIXED32: ('integer', 'int32'),
+        FieldDescriptor.TYPE_SFIXED64: ('string', 'int64'),
+        FieldDescriptor.TYPE_STRING: ('string', 'string'),
+        FieldDescriptor.TYPE_SINT32: ('integer', 'int32'),
+        FieldDescriptor.TYPE_SINT64: ('string', 'int64'),
+        FieldDescriptor.TYPE_UINT32: ('integer', 'int64'),
+        FieldDescriptor.TYPE_UINT64: ('string', 'uint64'),
+        # FieldDescriptor.TYPE_MESSAGE:
+        # FieldDescriptor.TYPE_GROUP:
+}
diff --git a/chameleon/protos/third_party/__init__.py b/chameleon/protos/third_party/__init__.py
index e69de29..19192c4 100644
--- a/chameleon/protos/third_party/__init__.py
+++ b/chameleon/protos/third_party/__init__.py
@@ -0,0 +1,50 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+This helps loading http_pb2 and annotations_pb2.
+Without this, the Python importer will not be able to process the lines:
+from google.api import http_pb2 or
+from google.api import annotations_pb2
+(Without importing these, the protobuf loader will not recognize http options
+in the protobuf definitions.)
+"""
+
+from importlib import import_module
+import os
+import sys
+
+
+class GoogleApiImporter(object):
+
+    def find_module(self, full_name, path=None):
+        if full_name == 'google.api':
+            self.path = [os.path.dirname(__file__)]
+            return self
+
+    def load_module(self, name):
+        if name in sys.modules:
+            return sys.modules[name]
+        full_name = 'chameleon.protos.third_party.' + name
+        import_module(full_name)
+        module = sys.modules[full_name]
+        sys.modules[name] = module
+        return module
+
+
+sys.meta_path.append(GoogleApiImporter())
+from google.api import http_pb2, annotations_pb2
+_ = http_pb2, annotations_pb2
diff --git a/chameleon/protos/third_party/google/api/__init__.py b/chameleon/protos/third_party/google/api/__init__.py
index e69de29..4484f93 100644
--- a/chameleon/protos/third_party/google/api/__init__.py
+++ b/chameleon/protos/third_party/google/api/__init__.py
@@ -0,0 +1 @@
+import http_pb2, annotations_pb2
diff --git a/tests/utests/__init__.py b/tests/utests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/utests/__init__.py
diff --git a/tests/utests/chameleon/__init__.py b/tests/utests/chameleon/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/utests/chameleon/__init__.py
diff --git a/tests/utests/chameleon/protoc_plugins/__init__.py b/tests/utests/chameleon/protoc_plugins/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/__init__.py
diff --git a/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.native.json b/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.native.json
new file mode 100644
index 0000000..c9b6800
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.native.json
@@ -0,0 +1 @@
+{"name": "a_bit_of_everything.proto", "package": "grpc.gateway.examples.examplepb", "dependency": ["google/api/annotations.proto", "google/protobuf/empty.proto", "examples/sub/message.proto", "examples/sub2/message.proto", "google/protobuf/timestamp.proto"], "message_type": [{"name": "ABitOfEverything", "field": [{"name": "single_nested", "number": 25, "label": 1, "type": 11, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.Nested", "json_name": "singleNested", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "uuid", "number": 1, "label": 1, "type": 9, "json_name": "uuid", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "nested", "number": 2, "label": 3, "type": 11, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.Nested", "json_name": "nested", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "float_value", "number": 3, "label": 1, "type": 2, "json_name": "floatValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "double_value", "number": 4, "label": 1, "type": 1, "json_name": "doubleValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "int64_value", "number": 5, "label": 1, "type": 3, "json_name": "int64Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "uint64_value", "number": 6, "label": 1, "type": 4, "json_name": "uint64Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "int32_value", "number": 7, "label": 1, "type": 5, "json_name": "int32Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "fixed64_value", "number": 8, "label": 1, "type": 6, "json_name": "fixed64Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "fixed32_value", "number": 9, "label": 1, "type": 7, "json_name": "fixed32Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "bool_value", "number": 10, "label": 1, "type": 8, "json_name": "boolValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "string_value", "number": 11, "label": 1, "type": 9, "json_name": "stringValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "uint32_value", "number": 13, "label": 1, "type": 13, "json_name": "uint32Value", "_type": "google.protobuf.FieldDescriptorProto", "_description": "TODO(yugui) add bytes_value"}, {"name": "enum_value", "number": 14, "label": 1, "type": 14, "type_name": "grpc.gateway.examples.examplepb.NumericEnum", "json_name": "enumValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "sfixed32_value", "number": 15, "label": 1, "type": 15, "json_name": "sfixed32Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "sfixed64_value", "number": 16, "label": 1, "type": 16, "json_name": "sfixed64Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "sint32_value", "number": 17, "label": 1, "type": 17, "json_name": "sint32Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "sint64_value", "number": 18, "label": 1, "type": 18, "json_name": "sint64Value", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "repeated_string_value", "number": 19, "label": 3, "type": 9, "json_name": "repeatedStringValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "oneof_empty", "number": 20, "label": 1, "type": 11, "type_name": "google.protobuf.Empty", "oneof_index": 0, "json_name": "oneofEmpty", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "oneof_string", "number": 21, "label": 1, "type": 9, "oneof_index": 0, "json_name": "oneofString", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "map_value", "number": 22, "label": 3, "type": 11, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.MapValueEntry", "json_name": "mapValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "mapped_string_value", "number": 23, "label": 3, "type": 11, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.MappedStringValueEntry", "json_name": "mappedStringValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "mapped_nested_value", "number": 24, "label": 3, "type": 11, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.MappedNestedValueEntry", "json_name": "mappedNestedValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "nonConventionalNameValue", "number": 26, "label": 1, "type": 9, "json_name": "nonConventionalNameValue", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "timestamp_value", "number": 27, "label": 1, "type": 11, "type_name": "google.protobuf.Timestamp", "json_name": "timestampValue", "_type": "google.protobuf.FieldDescriptorProto"}], "nested_type": [{"name": "Nested", "field": [{"name": "name", "number": 1, "label": 1, "type": 9, "json_name": "name", "_type": "google.protobuf.FieldDescriptorProto", "_description": "name is nested field."}, {"name": "amount", "number": 2, "label": 1, "type": 13, "json_name": "amount", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "ok", "number": 3, "label": 1, "type": 14, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.Nested.DeepEnum", "json_name": "ok", "_type": "google.protobuf.FieldDescriptorProto"}], "enum_type": [{"name": "DeepEnum", "value": [{"name": "FALSE", "number": 0, "_type": "google.protobuf.EnumValueDescriptorProto", "_description": "FALSE is false."}, {"name": "TRUE", "number": 1, "_type": "google.protobuf.EnumValueDescriptorProto", "_description": "TRUE is true."}], "_type": "google.protobuf.EnumDescriptorProto", "_description": "DeepEnum is one or zero."}], "_type": "google.protobuf.DescriptorProto", "_description": "Nested is nested type."}, {"name": "MapValueEntry", "field": [{"name": "key", "number": 1, "label": 1, "type": 9, "json_name": "key", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "value", "number": 2, "label": 1, "type": 14, "type_name": "grpc.gateway.examples.examplepb.NumericEnum", "json_name": "value", "_type": "google.protobuf.FieldDescriptorProto"}], "options": {"map_entry": true, "_type": "google.protobuf.MessageOptions"}, "_type": "google.protobuf.DescriptorProto"}, {"name": "MappedStringValueEntry", "field": [{"name": "key", "number": 1, "label": 1, "type": 9, "json_name": "key", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "value", "number": 2, "label": 1, "type": 9, "json_name": "value", "_type": "google.protobuf.FieldDescriptorProto"}], "options": {"map_entry": true, "_type": "google.protobuf.MessageOptions"}, "_type": "google.protobuf.DescriptorProto"}, {"name": "MappedNestedValueEntry", "field": [{"name": "key", "number": 1, "label": 1, "type": 9, "json_name": "key", "_type": "google.protobuf.FieldDescriptorProto"}, {"name": "value", "number": 2, "label": 1, "type": 11, "type_name": "grpc.gateway.examples.examplepb.ABitOfEverything.Nested", "json_name": "value", "_type": "google.protobuf.FieldDescriptorProto"}], "options": {"map_entry": true, "_type": "google.protobuf.MessageOptions"}, "_type": "google.protobuf.DescriptorProto"}], "oneof_decl": [{"name": "oneof_value", "_type": "google.protobuf.OneofDescriptorProto"}], "_type": "google.protobuf.DescriptorProto", "_description": "Intentionaly complicated message type to cover much features of Protobuf.\n NEXT ID: 27"}], "enum_type": [{"name": "NumericEnum", "value": [{"name": "ZERO", "number": 0, "_type": "google.protobuf.EnumValueDescriptorProto", "_description": "ZERO means 0"}, {"name": "ONE", "number": 1, "_type": "google.protobuf.EnumValueDescriptorProto", "_description": "ONE means 1"}], "_type": "google.protobuf.EnumDescriptorProto", "_description": "NumericEnum is one or zero."}], "service": [{"name": "ABitOfEverythingService", "method": [{"name": "Create", "input_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "output_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "options": {"http": {"post": "/v1/example/a_bit_of_everything/{float_value}/{double_value}/{int64_value}/separator/{uint64_value}/{int32_value}/{fixed64_value}/{fixed32_value}/{bool_value}/{string_value=strprefix/*}/{uint32_value}/{sfixed32_value}/{sfixed64_value}/{sint32_value}/{sint64_value}/{nonConventionalNameValue}", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "CreateBody", "input_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "output_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "options": {"http": {"post": "/v1/example/a_bit_of_everything", "body": "*", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "Lookup", "input_type": "sub2.IdMessage", "output_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "options": {"http": {"get": "/v1/example/a_bit_of_everything/{uuid}", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "Update", "input_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "output_type": "google.protobuf.Empty", "options": {"http": {"put": "/v1/example/a_bit_of_everything/{uuid}", "body": "*", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "Delete", "input_type": "sub2.IdMessage", "output_type": "google.protobuf.Empty", "options": {"http": {"delete": "/v1/example/a_bit_of_everything/{uuid}", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "Echo", "input_type": "grpc.gateway.examples.sub.StringMessage", "output_type": "grpc.gateway.examples.sub.StringMessage", "options": {"http": {"get": "/v1/example/a_bit_of_everything/echo/{value}", "additional_bindings": [{"post": "/v2/example/echo", "body": "value", "_type": "google.api.HttpRule"}, {"get": "/v2/example/echo", "_type": "google.api.HttpRule"}], "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "DeepPathEcho", "input_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "output_type": "grpc.gateway.examples.examplepb.ABitOfEverything", "options": {"http": {"post": "/v1/example/a_bit_of_everything/{single_nested.name}", "body": "*", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}, {"name": "Timeout", "input_type": "google.protobuf.Empty", "output_type": "google.protobuf.Empty", "options": {"http": {"get": "/v2/example/timeout", "_type": "google.api.HttpRule"}, "_type": "google.protobuf.MethodOptions"}, "_type": "google.protobuf.MethodDescriptorProto"}], "_type": "google.protobuf.ServiceDescriptorProto"}], "options": {"go_package": "examplepb", "_type": "google.protobuf.FileOptions"}, "syntax": "proto3", "_type": "google.protobuf.FileDescriptorProto"}
diff --git a/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.proto b/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.proto
new file mode 100644
index 0000000..bb375bb
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.proto
@@ -0,0 +1,122 @@
+syntax = "proto3";
+package examples.example;
+
+import "google/api/annotations.proto";
+import "google/protobuf/empty.proto";
+import "sub.proto";
+import "sub2.proto";
+import "google/protobuf/timestamp.proto";
+
+// Intentionaly complicated message type to cover much features of Protobuf.
+// NEXT ID: 27
+message ABitOfEverything {
+	// Nested is nested type.
+	message Nested {
+		// name is nested field.
+		string name = 1;
+		uint32 amount = 2;
+		// DeepEnum is one or zero.
+		enum DeepEnum {
+			// FALSE is false.
+			FALSE = 0;
+			// TRUE is true.
+			TRUE = 1;
+		}
+		DeepEnum ok = 3;
+	}
+	Nested single_nested = 25;
+
+	string uuid = 1;
+	repeated Nested nested = 2;
+	float float_value = 3;
+	double double_value = 4;
+	int64 int64_value = 5;
+	uint64 uint64_value = 6;
+	int32 int32_value = 7;
+	fixed64 fixed64_value = 8;
+	fixed32 fixed32_value = 9;
+	bool bool_value = 10;
+	string string_value = 11;
+	// TODO(yugui) add bytes_value
+	uint32 uint32_value = 13;
+	NumericEnum enum_value = 14;
+	sfixed32 sfixed32_value = 15;
+	sfixed64 sfixed64_value = 16;
+	sint32 sint32_value = 17;
+	sint64 sint64_value = 18;
+	repeated string repeated_string_value = 19;
+	oneof oneof_value {
+		google.protobuf.Empty oneof_empty = 20;
+		string oneof_string = 21;
+	}
+
+	map<string, NumericEnum> map_value = 22;
+	map<string, string> mapped_string_value = 23;
+	map<string, Nested> mapped_nested_value = 24;
+
+	string nonConventionalNameValue = 26;
+
+	google.protobuf.Timestamp timestamp_value = 27;
+}
+
+// NumericEnum is one or zero.
+enum NumericEnum {
+	// ZERO means 0
+	ZERO = 0;
+	// ONE means 1
+	ONE  = 1;
+}
+
+service ABitOfEverythingService {
+	rpc Create(ABitOfEverything) returns (ABitOfEverything) {
+		// TODO add enum_value
+		option (google.api.http) = {
+			post: "/v1/example/a_bit_of_everything/{float_value}/{double_value}/{int64_value}/separator/{uint64_value}/{int32_value}/{fixed64_value}/{fixed32_value}/{bool_value}/{string_value=strprefix/*}/{uint32_value}/{sfixed32_value}/{sfixed64_value}/{sint32_value}/{sint64_value}/{nonConventionalNameValue}"
+		};
+	}
+	rpc CreateBody(ABitOfEverything) returns (ABitOfEverything) {
+		option (google.api.http) = {
+			post: "/v1/example/a_bit_of_everything"
+			body: "*"
+		};
+	}
+	rpc Lookup(sub2.IdMessage) returns (ABitOfEverything) {
+		option (google.api.http) = {
+			get: "/v1/example/a_bit_of_everything/{uuid}"
+		};
+	}
+	rpc Update(ABitOfEverything) returns (google.protobuf.Empty) {
+		option (google.api.http) = {
+			put: "/v1/example/a_bit_of_everything/{uuid}"
+			body: "*"
+		};
+	}
+	rpc Delete(sub2.IdMessage) returns (google.protobuf.Empty) {
+		option (google.api.http) = {
+			delete: "/v1/example/a_bit_of_everything/{uuid}"
+		};
+	}
+	rpc Echo(examples.sub.StringMessage) returns (examples.sub.StringMessage) {
+		option (google.api.http) = {
+			get: "/v1/example/a_bit_of_everything/echo/{value}"
+			additional_bindings {
+				post: "/v2/example/echo"
+				body: "value"
+			}
+			additional_bindings {
+				get: "/v2/example/echo"
+			}
+		};
+	}
+	rpc DeepPathEcho(ABitOfEverything) returns (ABitOfEverything) {
+		option (google.api.http) = {
+			post: "/v1/example/a_bit_of_everything/{single_nested.name}"
+			body: "*"
+		};
+	}
+	rpc Timeout(google.protobuf.Empty) returns (google.protobuf.Empty) {
+		option (google.api.http) = {
+			get: "/v2/example/timeout",
+		};
+	}
+}
diff --git a/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.swagger.json b/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.swagger.json
new file mode 100644
index 0000000..35bbca5
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/a_bit_of_everything.swagger.json
@@ -0,0 +1,541 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "test.proto",
+    "version": "version not set"
+  },
+  "schemes": [
+    "http",
+    "https"
+  ],
+  "consumes": [
+    "application/json"
+  ],
+  "produces": [
+    "application/json"
+  ],
+  "paths": {
+    "/v1/example/a_bit_of_everything": {
+      "post": {
+        "operationId": "CreateBody",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "body",
+            "in": "body",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    },
+    "/v1/example/a_bit_of_everything/echo/{value}": {
+      "get": {
+        "operationId": "Echo",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.sub.StringMessage"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    },
+    "/v1/example/a_bit_of_everything/{float_value}/{double_value}/{int64_value}/separator/{uint64_value}/{int32_value}/{fixed64_value}/{fixed32_value}/{bool_value}/{string_value}/{uint32_value}/{sfixed32_value}/{sfixed64_value}/{sint32_value}/{sint64_value}/{nonConventionalNameValue}": {
+      "post": {
+        "operationId": "Create",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "float_value",
+            "in": "path",
+            "required": true,
+            "type": "number",
+            "format": "float"
+          },
+          {
+            "name": "double_value",
+            "in": "path",
+            "required": true,
+            "type": "number",
+            "format": "double"
+          },
+          {
+            "name": "int64_value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "int64"
+          },
+          {
+            "name": "uint64_value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "uint64"
+          },
+          {
+            "name": "int32_value",
+            "in": "path",
+            "required": true,
+            "type": "integer",
+            "format": "int32"
+          },
+          {
+            "name": "fixed64_value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "uint64"
+          },
+          {
+            "name": "fixed32_value",
+            "in": "path",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          },
+          {
+            "name": "bool_value",
+            "in": "path",
+            "required": true,
+            "type": "boolean",
+            "format": "boolean"
+          },
+          {
+            "name": "string_value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          },
+          {
+            "name": "uint32_value",
+            "in": "path",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          },
+          {
+            "name": "sfixed32_value",
+            "in": "path",
+            "required": true,
+            "type": "integer",
+            "format": "int32"
+          },
+          {
+            "name": "sfixed64_value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "int64"
+          },
+          {
+            "name": "sint32_value",
+            "in": "path",
+            "required": true,
+            "type": "integer",
+            "format": "int32"
+          },
+          {
+            "name": "sint64_value",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "int64"
+          },
+          {
+            "name": "nonConventionalNameValue",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    },
+    "/v1/example/a_bit_of_everything/{single_nested.name}": {
+      "post": {
+        "operationId": "DeepPathEcho",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "single_nested.name",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          },
+          {
+            "name": "body",
+            "in": "body",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    },
+    "/v1/example/a_bit_of_everything/{uuid}": {
+      "get": {
+        "operationId": "Lookup",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "uuid",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      },
+      "delete": {
+        "operationId": "Delete",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/google.protobuf.Empty"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "uuid",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      },
+      "put": {
+        "operationId": "Update",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/google.protobuf.Empty"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "uuid",
+            "in": "path",
+            "required": true,
+            "type": "string",
+            "format": "string"
+          },
+          {
+            "name": "body",
+            "in": "body",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/examples.example.ABitOfEverything"
+            }
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    },
+    "/v2/example/echo": {
+      "get": {
+        "operationId": "Echo",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.sub.StringMessage"
+            }
+          }
+        },
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      },
+      "post": {
+        "operationId": "Echo",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/examples.sub.StringMessage"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "body",
+            "in": "body",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/examples.sub.StringMessage"
+            }
+          }
+        ],
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    },
+    "/v2/example/timeout": {
+      "get": {
+        "operationId": "Timeout",
+        "responses": {
+          "200": {
+            "description": "",
+            "schema": {
+              "$ref": "#/definitions/google.protobuf.Empty"
+            }
+          }
+        },
+        "tags": [
+          "ABitOfEverythingService"
+        ]
+      }
+    }
+  },
+  "definitions": {
+    "examples.example.ABitOfEverything.Nested": {
+      "type": "object",
+      "properties": {
+        "amount": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "name": {
+          "type": "string",
+          "format": "string",
+          "description": "name is nested field."
+        },
+        "ok": {
+          "$ref": "#/definitions/examples.example.ABitOfEverything.Nested.DeepEnum"
+        }
+      },
+      "description": "Nested is nested type."
+    },
+    "examples.example.ABitOfEverything.Nested.DeepEnum": {
+      "type": "string",
+      "enum": [
+        "FALSE",
+        "TRUE"
+      ],
+      "default": "FALSE",
+      "description": "DeepEnum is one or zero.\nValid values:\n - FALSE: FALSE is false.\n - TRUE: TRUE is true."
+    },
+    "examples.example.ABitOfEverything": {
+      "type": "object",
+      "properties": {
+        "bool_value": {
+          "type": "boolean",
+          "format": "boolean"
+        },
+        "double_value": {
+          "type": "number",
+          "format": "double"
+        },
+        "enum_value": {
+          "$ref": "#/definitions/examples.example.NumericEnum"
+        },
+        "fixed32_value": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "fixed64_value": {
+          "type": "string",
+          "format": "uint64"
+        },
+        "float_value": {
+          "type": "number",
+          "format": "float"
+        },
+        "int32_value": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "int64_value": {
+          "type": "string",
+          "format": "int64"
+        },
+        "map_value": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/examples.example.NumericEnum"
+          }
+        },
+        "mapped_nested_value": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/examples.example.ABitOfEverything.Nested"
+          }
+        },
+        "mapped_string_value": {
+          "type": "object",
+          "additionalProperties": {
+            "type": "string",
+            "format": "string"
+          }
+        },
+        "nested": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/examples.example.ABitOfEverything.Nested"
+          }
+        },
+        "nonConventionalNameValue": {
+          "type": "string",
+          "format": "string"
+        },
+        "oneof_empty": {
+          "$ref": "#/definitions/google.protobuf.Empty"
+        },
+        "oneof_string": {
+          "type": "string",
+          "format": "string"
+        },
+        "repeated_string_value": {
+          "type": "array",
+          "items": {
+            "type": "string",
+            "format": "string"
+          }
+        },
+        "sfixed32_value": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "sfixed64_value": {
+          "type": "string",
+          "format": "int64"
+        },
+        "single_nested": {
+          "$ref": "#/definitions/examples.example.ABitOfEverything.Nested"
+        },
+        "sint32_value": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "sint64_value": {
+          "type": "string",
+          "format": "int64"
+        },
+        "string_value": {
+          "type": "string",
+          "format": "string"
+        },
+        "timestamp_value": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "uint32_value": {
+          "type": "integer",
+          "format": "int64",
+          "description": "TODO(yugui) add bytes_value"
+        },
+        "uint64_value": {
+          "type": "string",
+          "format": "uint64"
+        },
+        "uuid": {
+          "type": "string",
+          "format": "string"
+        }
+      },
+      "description": "Intentionaly complicated message type to cover much features of Protobuf.\n NEXT ID: 27"
+    },
+    "examples.example.NumericEnum": {
+      "type": "string",
+      "enum": [
+        "ZERO",
+        "ONE"
+      ],
+      "default": "ZERO",
+      "description": "NumericEnum is one or zero.\nValid values:\n - ZERO: ZERO means 0\n - ONE: ONE means 1"
+    },
+    "google.protobuf.Empty": {
+      "type": "object",
+      "description": "A generic empty message that you can re-use to avoid defining duplicated\n empty messages in your APIs. A typical example is to use it as the request\n or the response type of an API method. For instance:\n\n     service Foo {\n       rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);\n     }\n\n The JSON representation for `Empty` is empty JSON object `{}`."
+    },
+    "sub2.IdMessage": {
+      "type": "object",
+      "properties": {
+        "uuid": {
+          "type": "string",
+          "format": "string"
+        }
+      }
+    },
+    "examples.sub.StringMessage": {
+      "type": "object",
+      "properties": {
+        "value": {
+          "type": "string",
+          "format": "string"
+        }
+      }
+    }
+  }
+}
diff --git a/tests/utests/chameleon/protoc_plugins/descriptor_parser_test.py b/tests/utests/chameleon/protoc_plugins/descriptor_parser_test.py
new file mode 100644
index 0000000..ada7e54
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/descriptor_parser_test.py
@@ -0,0 +1,270 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from unittest import TestCase
+
+from chameleon.protoc_plugins.descriptor_parser import DescriptorParser
+from tests.utests.chameleon.protoc_plugins.test_utils import \
+    generate_plugin_request, json_rt
+from tests.utests.chameleon.protoc_plugins.test_utils import unindent
+
+
+class DescriptorParserTests(TestCase):
+
+    maxDiff = 10000
+
+    def test_empty(self):
+
+        proto = unindent("""
+        syntax = "proto3";
+        package test;
+        """)
+
+        expected = dict(
+            syntax='proto3',
+            name='test.proto',
+            package='test',
+            source_code_info=dict(
+                location=[
+                    dict(span=[1, 0, 2, 13]),
+                    dict(span=[1, 0, 18], path=[12]),
+                    dict(span=[2, 8, 12], path=[2])
+                ]
+            )
+        )
+
+        request = generate_plugin_request(proto)
+        assert len(request.proto_file) == 1
+        parser = DescriptorParser()
+        native_data = parser.parse_file_descriptor(request.proto_file[0])
+        self.assertEqual(native_data, expected)
+
+    def test_message_with_comment_folding(self):
+
+        proto = unindent("""
+        syntax = "proto3";
+        package test;
+
+        // Sample message
+        message SampleMessage {
+          string name = 1; // inline comment
+
+          // prefix comment
+          repeated int32 number = 2;
+
+          bool bool = 3;
+          // suffix comment
+        }
+        """)
+
+        expected = {
+            u'syntax': u'proto3',
+            u'name':  u'test.proto',
+            u'package': u'test',
+            u'message_type': [
+                {
+                    u'_description': u'Sample message',
+                    u'name': u'SampleMessage',
+                    u'field': [{
+                        u'_description': u'inline comment',
+                        u'json_name': u'name',
+                        u'name': u'name',
+                        u'label': 1,
+                        u'number': 1,
+                        u'type': 9
+                    }, {
+                        u'_description': u'prefix comment',
+                        u'json_name': u'number',
+                        u'name': u'number',
+                        u'label': 3,
+                        u'number': 2,
+                        u'type': 5
+                    }, {
+                        u'_description': u'suffix comment',
+                        u'json_name': u'bool',
+                        u'name': u'bool',
+                        u'label': 1,
+                        u'number': 3,
+                        u'type': 8
+                    }],
+                }
+            ]
+        }
+
+        request = generate_plugin_request(proto)
+        assert len(request.proto_file) == 1
+        parser = DescriptorParser()
+        native_data = parser.parse_file_descriptor(request.proto_file[0],
+                                                   fold_comments=True)
+        self.assertEqual(json_rt(native_data), expected)
+
+    def test_message_with_comment_folding_and_type_marking(self):
+
+        proto = unindent("""
+        syntax = "proto3";
+        package test;
+
+        // Sample message
+        message SampleMessage {
+          string name = 1; // inline comment
+
+          // prefix comment
+          repeated int32 number = 2;
+
+          bool bool = 3;
+          // suffix comment
+        }
+        """)
+
+        expected = {
+            u'syntax': u'proto3',
+            u'name':  u'test.proto',
+            u'package': u'test',
+            u'_type': u'google.protobuf.FileDescriptorProto',
+            u'message_type': [
+                {
+                    u'_type': u'google.protobuf.DescriptorProto',
+                    u'_description': u'Sample message',
+                    u'name': u'SampleMessage',
+                    u'field': [{
+                        u'_type': u'google.protobuf.FieldDescriptorProto',
+                        u'_description': u'inline comment',
+                        u'json_name': u'name',
+                        u'name': u'name',
+                        u'label': 1,
+                        u'number': 1,
+                        u'type': 9
+                    }, {
+                        u'_type': u'google.protobuf.FieldDescriptorProto',
+                        u'_description': u'prefix comment',
+                        u'json_name': u'number',
+                        u'name': u'number',
+                        u'label': 3,
+                        u'number': 2,
+                        u'type': 5
+                    }, {
+                        u'_type': u'google.protobuf.FieldDescriptorProto',
+                        u'_description': u'suffix comment',
+                        u'json_name': u'bool',
+                        u'name': u'bool',
+                        u'label': 1,
+                        u'number': 3,
+                        u'type': 8
+                    }],
+                }
+            ]
+        }
+
+        request = generate_plugin_request(proto)
+        assert len(request.proto_file) == 1
+        parser = DescriptorParser()
+        native_data = parser.parse_file_descriptor(request.proto_file[0],
+                                                   type_tag_name='_type',
+                                                   fold_comments=True)
+        self.assertEqual(json_rt(native_data), expected)
+
+    def test_http_annotations_carry_over(self):
+
+        proto = unindent("""
+        syntax = "proto3";
+        package test;
+        import "google/api/annotations.proto";
+        message Null {}
+        service Test {
+          rpc Call(Null) returns(Null) {
+            option (google.api.http) = {
+              get: "/some/path"
+            };
+          }
+        }
+        """)
+
+        expected = {
+            u'syntax': u'proto3',
+            u'name': u'test.proto',
+            u'package': u'test',
+            u'dependency': [u'google/api/annotations.proto'],
+            u'message_type': [{u'name': u'Null'}],
+            u'service': [{
+                u'name': u'Test',
+                u'method': [{
+                    u'name': u'Call',
+                    u'input_type': u'.test.Null',
+                    u'output_type': u'.test.Null',
+                    u'options': {
+                        u'http': {
+                            u'get': u'/some/path'
+                        }
+                    }
+                }]
+            }]
+        }
+
+        request = generate_plugin_request(proto)
+        assert len(request.proto_file) == 4
+        parser = DescriptorParser()
+        native_data = parser.parse_file_descriptor(request.proto_file[3],
+                                                   fold_comments=True)
+        self.assertEqual(json_rt(native_data), expected)
+
+    def test_http_annotations_carryover_and_all_components(self):
+
+        proto = unindent("""
+        syntax = "proto3";
+        package test;
+        import "google/api/annotations.proto";
+        message Null {}
+        service Test {
+          rpc Call(Null) returns(Null) {
+            option (google.api.http) = {
+              get: "/some/path"
+            };
+          }
+        }
+        """)
+
+        expected = {
+            u'syntax': 'proto3',
+            u'name': u'test.proto',
+            u'package': u'test',
+            u'dependency': [u'google/api/annotations.proto'],
+            u'message_type': [{u'name': u'Null'}],
+            u'service': [{
+                u'name': u'Test',
+                u'method': [{
+                    u'name': u'Call',
+                    u'input_type': u'.test.Null',
+                    u'output_type': u'.test.Null',
+                    u'options': {
+                        u'http': {
+                            u'get': u'/some/path'
+                        }
+                    }
+                }]
+            }]
+        }
+
+        request = generate_plugin_request(proto)
+        assert len(request.proto_file) == 4
+        parser = DescriptorParser()
+        native_data = parser.parse_file_descriptors(request.proto_file,
+                                                    fold_comments=True)
+        self.assertEqual([d['name'] for d in native_data], [
+            u'google/api/http.proto',
+            u'google/protobuf/descriptor.proto',
+            u'google/api/annotations.proto',
+            u'test.proto'
+        ])
+        self.assertEqual(json_rt(native_data[3]), expected)
diff --git a/tests/utests/chameleon/protoc_plugins/empty.native.json b/tests/utests/chameleon/protoc_plugins/empty.native.json
new file mode 100644
index 0000000..e8ac6ba
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/empty.native.json
@@ -0,0 +1 @@
+{"name": "google/protobuf/empty.proto", "package": "google.protobuf", "message_type": [{"name": "Empty", "_type": "google.protobuf.DescriptorProto", "_description": "A generic empty message that you can re-use to avoid defining duplicated\n empty messages in your APIs. A typical example is to use it as the request\n or the response type of an API method. For instance:\n\n     service Foo {\n       rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);\n     }\n\n The JSON representation for `Empty` is empty JSON object `{}`."}], "options": {"java_package": "com.google.protobuf", "java_outer_classname": "EmptyProto", "java_multiple_files": true, "go_package": "github.com/golang/protobuf/ptypes/empty", "java_generate_equals_and_hash": true, "cc_enable_arenas": true, "objc_class_prefix": "GPB", "csharp_namespace": "Google.Protobuf.WellKnownTypes", "_type": "google.protobuf.FileOptions"}, "syntax": "proto3", "_type": "google.protobuf.FileDescriptorProto"}
\ No newline at end of file
diff --git a/tests/utests/chameleon/protoc_plugins/empty.swagger.json b/tests/utests/chameleon/protoc_plugins/empty.swagger.json
new file mode 100644
index 0000000..dee6c59
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/empty.swagger.json
@@ -0,0 +1 @@
+{"info": {"version": "version not set", "title": "google/protobuf/empty.proto"}, "paths": {}, "schemes": ["http", "https"], "produces": ["application/json"], "definitions": {"google.protobuf.Empty": {"type": "object", "description": "A generic empty message that you can re-use to avoid defining duplicated\n empty messages in your APIs. A typical example is to use it as the request\n or the response type of an API method. For instance:\n\n     service Foo {\n       rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);\n     }\n\n The JSON representation for `Empty` is empty JSON object `{}`.", "properties": {}}}, "swagger": "2.0", "consumes": ["application/json"]}
\ No newline at end of file
diff --git a/tests/utests/chameleon/protoc_plugins/null_plugin.py b/tests/utests/chameleon/protoc_plugins/null_plugin.py
new file mode 100755
index 0000000..9443304
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/null_plugin.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Protoc plugin that simply emits the binary content of the CodeGeneratorRequest
+it is called with for each <name>.proto file. The name of the file the content
+is saved is protoc.request.
+"""
+import base64
+import sys
+
+from google.protobuf.compiler import plugin_pb2
+
+if __name__ == '__main__':
+    response = plugin_pb2.CodeGeneratorResponse()
+    f = response.file.add()
+    f.name = 'protoc.request'
+    f.content = base64.encodestring(sys.stdin.read())
+    sys.stdout.write(response.SerializeToString())
diff --git a/tests/utests/chameleon/protoc_plugins/sub.native.json b/tests/utests/chameleon/protoc_plugins/sub.native.json
new file mode 100644
index 0000000..460b84d
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/sub.native.json
@@ -0,0 +1 @@
+{"name": "examples/sub/message.proto", "package": "grpc.gateway.examples.sub", "message_type": [{"name": "StringMessage", "field": [{"name": "value", "number": 1, "label": 2, "type": 9, "json_name": "value", "_type": "google.protobuf.FieldDescriptorProto"}], "_type": "google.protobuf.DescriptorProto"}], "options": {"go_package": "sub", "_type": "google.protobuf.FileOptions"}, "_type": "google.protobuf.FileDescriptorProto"}
\ No newline at end of file
diff --git a/tests/utests/chameleon/protoc_plugins/sub.proto b/tests/utests/chameleon/protoc_plugins/sub.proto
new file mode 100644
index 0000000..d3f2462
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/sub.proto
@@ -0,0 +1,6 @@
+syntax = "proto2";
+package examples.sub;
+
+message StringMessage {
+	required string value = 1;
+}
diff --git a/tests/utests/chameleon/protoc_plugins/sub.swagger.json b/tests/utests/chameleon/protoc_plugins/sub.swagger.json
new file mode 100644
index 0000000..5a2f659
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/sub.swagger.json
@@ -0,0 +1 @@
+{"info": {"version": "version not set", "title": "examples/sub/message.proto"}, "paths": {}, "produces": ["application/json"], "definitions": {"grpc.gateway.examples.sub.StringMessage": {"description": "", "properties": {"value": {"format": "string", "type": "string"}}, "type": "object"}}, "swagger": "2.0", "consumes": ["application/json"], "schemes": ["http", "https"]}
diff --git a/tests/utests/chameleon/protoc_plugins/sub2.native.json b/tests/utests/chameleon/protoc_plugins/sub2.native.json
new file mode 100644
index 0000000..9ddf68f
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/sub2.native.json
@@ -0,0 +1 @@
+{"name": "examples/sub2/message.proto", "package": "sub2", "message_type": [{"name": "IdMessage", "field": [{"name": "uuid", "number": 1, "label": 1, "type": 9, "json_name": "uuid", "_type": "google.protobuf.FieldDescriptorProto"}], "_type": "google.protobuf.DescriptorProto"}], "options": {"go_package": "github.com/grpc-ecosystem/grpc-gateway/examples/sub2", "_type": "google.protobuf.FileOptions"}, "syntax": "proto3", "_type": "google.protobuf.FileDescriptorProto"}
\ No newline at end of file
diff --git a/tests/utests/chameleon/protoc_plugins/sub2.proto b/tests/utests/chameleon/protoc_plugins/sub2.proto
new file mode 100644
index 0000000..7b98db0
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/sub2.proto
@@ -0,0 +1,6 @@
+syntax = "proto3";
+package sub2;
+
+message IdMessage {
+	string uuid = 1;
+}
diff --git a/tests/utests/chameleon/protoc_plugins/sub2.swagger.json b/tests/utests/chameleon/protoc_plugins/sub2.swagger.json
new file mode 100644
index 0000000..575e709
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/sub2.swagger.json
@@ -0,0 +1 @@
+{"info": {"version": "version not set", "title": "examples/sub2/message.proto"}, "paths": {}, "produces": ["application/json"], "definitions": {"sub2.IdMessage": {"description": "", "type": "object", "properties": {"uuid": {"format": "string", "type": "string"}}}}, "swagger": "2.0", "consumes": ["application/json"], "schemes": ["http", "https"]}
diff --git a/tests/utests/chameleon/protoc_plugins/swagger_template_test.py b/tests/utests/chameleon/protoc_plugins/swagger_template_test.py
new file mode 100644
index 0000000..1344430
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/swagger_template_test.py
@@ -0,0 +1,1052 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import json
+import os
+from unittest import TestCase
+
+from chameleon.protoc_plugins.descriptor_parser import DescriptorParser
+from chameleon.protoc_plugins.swagger_template \
+    import native_descriptors_to_swagger, DuplicateMethodAndPathError, \
+    ProtobufCompilationFailedError, InvalidPathArgumentError
+from tests.utests.chameleon.protoc_plugins.test_utils import unindent, \
+    json_rt, generate_plugin_request, load_file
+
+
+class SwaggerTemplateTests(TestCase):
+
+    maxDiff = 10000
+
+    def gen_swagger(self, proto):
+        request = generate_plugin_request(proto)
+        parser = DescriptorParser()
+        native_data = parser.parse_file_descriptors(request.proto_file,
+                                                    type_tag_name='_type',
+                                                    fold_comments=True)
+        swagger = native_descriptors_to_swagger(native_data)
+        return swagger
+
+    def test_empty_def(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+        """)
+
+        expected_swagger = {
+            u'swagger': u'2.0',
+            u'info': {
+                u'title': u'test.proto',
+                u'version': u'version not set'
+            },
+            u'schemes': [u"http", u"https"],
+            u'consumes': [u"application/json"],
+            u'produces': [u"application/json"],
+            u'paths': {},
+            u'definitions': {}
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger), expected_swagger)
+
+    def test_empty_message_with_service(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {}
+            service TestService {
+              rpc Get(Null) returns(Null) {
+                option (google.api.http) = {
+                  get: "/test"
+                };
+              }
+            }
+        """)
+
+        expected_swagger = {
+            u'swagger': u'2.0',
+            u'info': {
+                u'title': u'test.proto',
+                u'version': u"version not set"
+            },
+            u'schemes': [u"http", u"https"],
+            u'consumes': [u"application/json"],
+            u'produces': [u"application/json"],
+            u'paths': {
+                u'/test': {
+                    u'get': {
+                        u'operationId': u'Get',
+                        u'responses': {
+                            u'200': {
+                                u'description': u'',
+                                u'schema': {
+                                    u'$ref': u'#/definitions/test.Null'
+                                }
+                            }
+                        },
+                        u'tags': [u'TestService']
+                    }
+                }
+            },
+            u'definitions': {
+                u'test.Null': {
+                    u'type': u'object'
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger), expected_swagger)
+
+    def test_simple_annotated_message_with_simple_annotated_service(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+
+            // Simple Message
+            message Simple {
+                string str = 1; // a string attribute
+                int32 int = 2; // an int32 attribute
+            }
+
+            // Service to get things done
+            service TestService {
+
+              /* Get simple answer
+               *
+               * Returns the true answer to all of life's persistent questions.
+               */
+              rpc Get(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/test"
+                };
+              }
+            }
+        """)
+
+        expected_swagger = {
+            u'swagger': u'2.0',
+            u'info': {
+                u'title': u'test.proto',
+                u'version': u"version not set"
+            },
+            u'schemes': [u"http", u"https"],
+            u'consumes': [u"application/json"],
+            u'produces': [u"application/json"],
+            u'paths': {
+                u'/test': {
+                    u'get': {
+                        u'summary': u'Get simple answer',
+                        u'description':
+                            u' Returns the true answer to all of life\'s '
+                            u'persistent questions.',
+                        u'operationId': u'Get',
+                        u'responses': {
+                            u'200': {
+                                u'description': u'',
+                                u'schema': {
+                                    u'$ref': u'#/definitions/test.Simple'
+                                }
+                            }
+                        },
+                        u'tags': [u'TestService']
+                    }
+                }
+            },
+            u'definitions': {
+                u'test.Simple': {
+                    u'description': u'Simple Message',
+                    u'type': u'object',
+                    u'properties': {
+                        u'int': {
+                            u'description': u'an int32 attribute',
+                            u'type': u'integer',
+                            u'format': u'int32'
+                        },
+                        u'str': {
+                            u'description': u'a string attribute',
+                            u'type': u'string',
+                            u'format': u'string'
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger), expected_swagger)
+
+    def test_method_input_params_in_body(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+
+            // Simple Message
+            message Simple {
+                string str = 1; // a string attribute
+                int32 int = 2; // an int32 attribute
+            }
+
+            // Service to get things done
+            service TestService {
+
+              /* Get simple answer
+               *
+               * Returns the true answer to all of life's persistent questions.
+               */
+              rpc Get(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/test"
+                };
+              }
+
+              /*
+               * Make up an answer (notice the leading blank line)
+               *
+               * Define the ultimate answer
+               */
+              rpc MakeUp(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  post: "/test"
+                  body: "*"
+                };
+              }
+            }
+        """)
+
+        expected_swagger = {
+            u'swagger': u'2.0',
+            u'info': {
+                u'title': u'test.proto',
+                u'version': u"version not set"
+            },
+            u'schemes': [u"http", u"https"],
+            u'consumes': [u"application/json"],
+            u'produces': [u"application/json"],
+            u'paths': {
+                u'/test': {
+                    u'get': {
+                        u'summary': u'Get simple answer',
+                        u'description':
+                            u' Returns the true answer to all of life\'s '
+                            u'persistent questions.',
+                        u'operationId': u'Get',
+                        u'responses': {
+                            u'200': {
+                                u'description': u'',
+                                u'schema': {
+                                    u'$ref': u'#/definitions/test.Simple'
+                                }
+                            }
+                        },
+                        u'tags': [u'TestService']
+                    },
+                    u'post': {
+                        u'summary': u'Make up an answer (notice the leading '
+                                    u'blank line)',
+                        u'description': u' Define the ultimate answer',
+                        u'operationId': u'MakeUp',
+                        u'parameters': [{
+                            u'name': u'body',
+                            u'in': u'body',
+                            u'required': True,
+                            u'schema': {u'$ref': u'#/definitions/test.Simple'}
+                        }],
+                        u'responses': {
+                            u'200': {
+                                u'description': u'',
+                                u'schema': {
+                                    u'$ref': u'#/definitions/test.Simple'
+                                }
+                            }
+                        },
+                        u'tags': [u'TestService']
+                    }
+                }
+            },
+            u'definitions': {
+                u'test.Simple': {
+                    u'description': u'Simple Message',
+                    u'type': u'object',
+                    u'properties': {
+                        u'int': {
+                            u'description': u'an int32 attribute',
+                            u'type': u'integer',
+                            u'format': u'int32'
+                        },
+                        u'str': {
+                            u'description': u'a string attribute',
+                            u'type': u'string',
+                            u'format': u'string'
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger), expected_swagger)
+
+    def test_catch_repeating_verbs_for_same_path(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {}
+            service TestService {
+              rpc Get(Null) returns(Null) {
+                option (google.api.http) = {
+                  get: "/test"
+                };
+              }
+              rpc MakeUp(Null) returns(Null) {
+                option (google.api.http) = {
+                  get: "/test"
+                  body: "*"
+                };
+              }
+            }
+        """)
+
+        with self.assertRaises(DuplicateMethodAndPathError):
+            self.gen_swagger(proto)
+
+    def test_catch_unresolved_message_type_reference(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {}
+            service TestService {
+              rpc Get(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/test"
+                };
+              }
+              rpc MakeUp(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/test"
+                  body: "*"
+                };
+              }
+            }
+        """)
+
+        with self.assertRaises(ProtobufCompilationFailedError):
+            self.gen_swagger(proto)
+
+    def test_path_parameter_handling(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Simple {
+                string str = 1;
+                int32 int = 2;
+            }
+            service TestService {
+              rpc Get(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/test/{str}/{int}"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_path = {
+            u'/test/{str}/{int}': {
+                u'get': {
+                    u'operationId': u'Get',
+                    u'parameters': [{
+                        u'name': u'str',
+                        u'in': u'path',
+                        u'type': u'string',
+                        u'format': u'string',
+                        u'required': True
+                    }, {
+                        u'name': u'int',
+                        u'in': u'path',
+                        u'type': u'integer',
+                        u'format': u'int32',
+                        u'required': True
+                    }],
+                    u'responses': {
+                        u'200': {
+                            u'description': u'',
+                            u'schema': {
+                                u'$ref': u'#/definitions/test.Simple'
+                            }
+                        }
+                    },
+                    u'tags': [u'TestService']
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['paths']), expected_swagger_path)
+
+    def test_path_parameter_error_handling(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Simple {
+                string str = 1;
+                int32 int = 2;
+            }
+            service TestService {
+              rpc Get(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/test/{str}/{xxxxx}/{int}"
+                };
+              }
+            }
+        """)
+
+        with self.assertRaises(InvalidPathArgumentError):
+            self.gen_swagger(proto)
+
+    def test_alternative_bindings(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Simple {
+                string str = 1;
+                int32 int = 2;
+            }
+            service TestService {
+              rpc Get(Simple) returns(Simple) {
+                option (google.api.http) = {
+                  get: "/v1/test/{str}/{int}"
+                  additional_bindings {
+                    post: "/v2/test"
+                    body: "*"
+                  }
+                  additional_bindings {
+                    get: "/v2/test/{int}/{str}"
+                  }
+                };
+              }
+            }
+        """)
+
+        expected_swagger_path = {
+            u'/v1/test/{str}/{int}': {
+                u'get': {
+                    u'operationId': u'Get',
+                    u'parameters': [{
+                        u'name': u'str',
+                        u'in': u'path',
+                        u'type': u'string',
+                        u'format': u'string',
+                        u'required': True
+                    }, {
+                        u'name': u'int',
+                        u'in': u'path',
+                        u'type': u'integer',
+                        u'format': u'int32',
+                        u'required': True
+                    }],
+                    u'responses': {
+                        u'200': {
+                            u'description': u'',
+                            u'schema': {
+                                u'$ref': u'#/definitions/test.Simple'
+                            }
+                        }
+                    },
+                    u'tags': [u'TestService']
+                }
+            },
+            u'/v2/test': {
+                u'post': {
+                    u'operationId': u'Get',
+                    u'parameters': [{
+                        u'in': u'body',
+                        u'name': u'body',
+                        u'required': True,
+                        u'schema': {u'$ref': u'#/definitions/test.Simple'}
+                    }],
+                    u'responses': {
+                        u'200': {
+                            u'description': u'',
+                            u'schema': {u'$ref': u'#/definitions/test.Simple'}
+                        }
+                    },
+                    u'tags': [u'TestService']
+                }
+            },
+            u'/v2/test/{int}/{str}': {
+                u'get': {
+                    u'operationId': u'Get',
+                    u'parameters': [{
+                        u'format': u'int32',
+                        u'in': u'path',
+                        u'name': u'int',
+                        u'required': True,
+                        u'type': u'integer'
+                    }, {
+                        u'format': u'string',
+                        u'in': u'path',
+                        u'name': u'str',
+                        u'required': True,
+                        u'type': u'string'
+                    }],
+                    u'responses': {
+                        u'200': {
+                            u'description': u'',
+                            u'schema': {u'$ref': u'#/definitions/test.Simple'}
+                        }
+                    },
+                    u'tags': [u'TestService']
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['paths']), expected_swagger_path)
+
+    def test_google_null(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            import "google/protobuf/empty.proto";
+            service TestService {
+              rpc Get(google.protobuf.Empty) returns(google.protobuf.Empty) {
+                option (google.api.http) = {
+                  get: "/echo"
+                };
+              }
+            }
+        """)
+
+        expected_swagger = {
+            u'swagger': u'2.0',
+            u'info': {
+                u'title': u'test.proto',
+                u'version': u"version not set"
+            },
+            u'schemes': [u"http", u"https"],
+            u'consumes': [u"application/json"],
+            u'produces': [u"application/json"],
+            u'paths': {
+                u'/echo': {
+                    u'get': {
+                        u'operationId': u'Get',
+                        u'responses': {
+                            u'200': {
+                                u'description': u'',
+                                u'schema': {
+                                    u'$ref':
+                                        u'#/definitions/google.protobuf.Empty'
+                                }
+                            }
+                        },
+                        u'tags': [u'TestService']
+                    }
+                }
+            },
+            u'definitions': {
+                u'google.protobuf.Empty': {
+                    u'description': u'A generic empty message that you can '
+                                    u're-use to avoid defining duplicated\n '
+                                    u'empty messages in your APIs. A typical '
+                                    u'example is to use it as the request\n '
+                                    u'or the response type of an API method. '
+                                    u'For instance:\n\n     service Foo {\n  '
+                                    u'     rpc Bar(google.protobuf.Empty) '
+                                    u'returns (google.protobuf.Empty);\n     '
+                                    u'}\n\n The JSON representation for '
+                                    u'`Empty` is empty JSON object `{}`.',
+                    u'type': u'object'
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger), expected_swagger)
+
+
+    def test_nested_type_definitions(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            import "google/protobuf/empty.proto";
+            message Null {}
+            message Outer {
+              message Inner {
+                message Innermost {
+                  bool healthy = 1;
+                  string illness = 2;
+                }
+                Innermost innermost = 1;
+                string other = 2;
+              }
+              string name = 1;
+              Inner inner = 2;
+            }
+            service TestService {
+              rpc Get(Null) returns(Outer) {
+                option (google.api.http) = {
+                  get: "/test"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.Outer': {
+                u'type': u'object',
+                u'properties': {
+                    u'inner': {
+                        u'$ref': u'#/definitions/test.Outer.Inner'
+                    },
+                    u'name': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    }
+                }
+            },
+            u'test.Outer.Inner': {
+                u'type': u'object',
+                u'properties': {
+                    u'innermost': {
+                        u'$ref': u'#/definitions/test.Outer.Inner.Innermost'
+                    },
+                    u'other': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    }
+                }
+            },
+            u'test.Outer.Inner.Innermost': {
+                u'type': u'object',
+                u'properties': {
+                    u'healthy': {
+                        u'type': u'boolean',
+                        u'format': u'boolean'
+                    },
+                    u'illness': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_enum(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            // Detailed weather state
+            enum WeatherState {
+              GOOD = 0;  // Weather is good
+              BAD = 1;  // Weather is bad
+            }
+            message Forecast {
+              WeatherState forecast = 1;
+            }
+            service ForecastService {
+              rpc GetForecast(Null) returns(Forecast) {
+                option (google.api.http) = {
+                  get: "/forecast"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.WeatherState': {
+                u'default': u'GOOD',
+                u'description': u'Detailed weather state\n'
+                                u'Valid values:\n'
+                                u' - GOOD: Weather is good\n'
+                                u' - BAD: Weather is bad',
+                u'type': u'string',
+                u'enum': [u'GOOD', u'BAD']
+            },
+            u'test.Forecast': {
+                u'type': u'object',
+                u'properties': {
+                    u'forecast': {u'$ref': u'#/definitions/test.WeatherState'}
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_nested_enum(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            message Forecast {
+              // Detailed weather state
+              enum WeatherState {
+                GOOD = 0;  // Weather is good
+                BAD = 1;  // Weather is bad
+              }
+              WeatherState forecast = 1;
+            }
+            service ForecastService {
+              rpc GetForecast(Null) returns(Forecast) {
+                option (google.api.http) = {
+                  get: "/forecast"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.Forecast.WeatherState': {
+                u'default': u'GOOD',
+                u'description': u'Detailed weather state\n'
+                                u'Valid values:\n'
+                                u' - GOOD: Weather is good\n'
+                                u' - BAD: Weather is bad',
+                u'type': u'string',
+                u'enum': [u'GOOD', u'BAD']
+            },
+            u'test.Forecast': {
+                u'type': u'object',
+                u'properties': {
+                    u'forecast': {
+                        u'$ref': u'#/definitions/test.Forecast.WeatherState'}
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_array_of_simple_types(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            message Invitations {
+              string event = 1;
+              repeated string names = 2;
+              repeated int32 ages = 3;
+            }
+            service RsvpService {
+              rpc Get(Null) returns(Invitations) {
+                option (google.api.http) = {
+                  get: "/invitations"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.Invitations': {
+                u'type': u'object',
+                u'properties': {
+                    u'event': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    },
+                    u'names': {
+                        u'type': u'array',
+                        u'items': {
+                            u'type': u'string',
+                            u'format': u'string'
+                        }
+                    },
+                    u'ages': {
+                        u'type': u'array',
+                        u'items': {
+                            u'type': u'integer',
+                            u'format': u'int32'
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_array_of_object_type(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            message Invitations {
+              message Address {
+                string street = 1;
+                string city = 2;
+              }
+              string event = 1;
+              repeated Null nulles = 2;
+              repeated Address addresses = 3;
+            }
+            service RsvpService {
+              rpc Get(Null) returns(Invitations) {
+                option (google.api.http) = {
+                  get: "/invitations"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.Invitations.Address': {
+                u'type': u'object',
+                u'properties': {
+                    u'street': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    },
+                    u'city': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    }
+                }
+            },
+            u'test.Invitations': {
+                u'type': u'object',
+                u'properties': {
+                    u'event': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    },
+                    u'nulles': {
+                        u'type': u'array',
+                        u'items': {
+                            u'$ref': u'#/definitions/test.Null'
+                        }
+                    },
+                    u'addresses': {
+                        u'type': u'array',
+                        u'items': {
+                            u'$ref': u'#/definitions/test.Invitations.Address'
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_recursively_nested_values(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            message TreeNode {
+              string name = 1;
+              repeated TreeNode children = 2;
+            }
+            service RsvpService {
+              rpc Get(Null) returns(TreeNode) {
+                option (google.api.http) = {
+                  get: "/invitations"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.TreeNode': {
+                u'type': u'object',
+                u'properties': {
+                    u'name': {
+                        u'type': u'string',
+                        u'format': u'string'
+                    },
+                    u'children': {
+                        u'type': u'array',
+                        u'items': {
+                            u'$ref': u'#/definitions/test.TreeNode'
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_map_fields(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            message Maps {
+              map<string, string> string_map = 1;
+              map<string, int32> int32_map = 2;
+              map<string, Null> object_map = 3;
+            }
+            service RsvpService {
+              rpc Get(Null) returns(Maps) {
+                option (google.api.http) = {
+                  get: "/maps"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.Maps': {
+                u'type': u'object',
+                u'properties': {
+                    u'string_map': {
+                        u'type': u'object',
+                        u'additionalProperties': {
+                            u'type': u'string',
+                            u'format': u'string'
+                        }
+                    },
+                    u'int32_map': {
+                        u'type': u'object',
+                        u'additionalProperties': {
+                            u'type': u'integer',
+                            u'format': u'int32'
+                        }
+                    },
+                    u'object_map': {
+                        u'type': u'object',
+                        u'additionalProperties': {
+                            u'$ref': u'#/definitions/test.Null',
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_array_and_map_of_enum(self):
+
+        proto = unindent("""
+            syntax = "proto3";
+            package test;
+            import "google/api/annotations.proto";
+            message Null {};
+            enum State {
+              GOOD = 0;
+              BAD = 1;
+            }
+            message Map {
+              map<string, State> enum_map = 1;
+              repeated State states = 2;
+            }
+            service RsvpService {
+              rpc Get(Null) returns(Map) {
+                option (google.api.http) = {
+                  get: "/maps"
+                };
+              }
+            }
+        """)
+
+        expected_swagger_definitions = {
+            u'test.Null': {u'type': u'object'},
+            u'test.State': {
+                u'default': u'GOOD',
+                u'description': u'State\n'
+                                u'Valid values:\n'
+                                u' - GOOD\n'
+                                u' - BAD',
+                u'type': u'string',
+                u'enum': [u'GOOD', u'BAD']
+            },
+            u'test.Map': {
+                u'type': u'object',
+                u'properties': {
+                    u'enum_map': {
+                        u'type': u'object',
+                        u'additionalProperties': {
+                            u'$ref': u'#/definitions/test.State',
+                        }
+                    },
+                    u'states': {
+                        u'type': u'array',
+                        u'items': {
+                            u'$ref': u'#/definitions/test.State'
+                        }
+                    }
+                }
+            }
+        }
+
+        swagger = self.gen_swagger(proto)
+        self.assertEqual(json_rt(swagger['definitions']),
+                         expected_swagger_definitions)
+
+    def test_kitchen_sink(self):
+
+        proto = load_file(
+            os.path.dirname(__file__) + '/a_bit_of_everything.proto')
+
+        swagger = self.gen_swagger(proto)
+
+        expected_swagger = json.loads(load_file(
+            os.path.dirname(__file__) + '/a_bit_of_everything.swagger.json')
+        )
+
+        self.maxDiff = 100000
+        self.assertEqual(json_rt(swagger), expected_swagger)
diff --git a/tests/utests/chameleon/protoc_plugins/test_utils.py b/tests/utests/chameleon/protoc_plugins/test_utils.py
new file mode 100644
index 0000000..6b59f1e
--- /dev/null
+++ b/tests/utests/chameleon/protoc_plugins/test_utils.py
@@ -0,0 +1,110 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import base64
+import json
+import os
+import sys
+from commands import getstatusoutput
+from google.protobuf.compiler.plugin_pb2 import CodeGeneratorRequest
+
+from chameleon.protoc_plugins.swagger_template import \
+    ProtobufCompilationFailedError
+from chameleon.protos import third_party
+
+this_dir = os.path.abspath(os.path.dirname(__file__))
+third_party_dir = os.path.dirname(third_party.__file__)
+
+
+def unindent(str):
+    """eat leading space in front of lines based on the smallest one"""
+    min_leading_spaces = len(str)
+    lines = str.splitlines()
+    for line in lines:
+        if line:
+            min_leading_spaces = min(len(line) - len(line.lstrip(' ')),
+                                     min_leading_spaces)
+    return '\n'.join(l[min_leading_spaces:] for l in lines)
+
+
+def mkdir(path):
+    """equivalent of command line mkdir -p <path>"""
+    if os.path.exists(path):
+        assert os.path.isdir(path)
+        return
+    head, tail = os.path.split(os.path.abspath(path))
+    if not os.path.exists(head):
+        mkdir(path)
+    assert os.path.isdir(head)
+    os.mkdir(path)
+
+
+def save_file(path, content, mode=0644):
+    """save content into file of path"""
+    with file(path, 'w') as f:
+        f.write(content)
+    os.chmod(path, mode)
+
+
+def load_file(path, read_mode='r'):
+    """load content from file of path"""
+    with file(path, read_mode) as f:
+        content = f.read()
+    return content
+
+
+def generate_plugin_request(proto):
+    """save proto file and run protoc to generate a plugin request protobuf"""
+
+    workdir = '/tmp/chameleon_tests'
+
+    mkdir(workdir)
+    save_file(os.path.join(workdir, 'test.proto'), proto)
+    cmd = (
+        'cd {this_dir} && '
+        'env PATH={extended_path} '
+        'python -m grpc.tools.protoc '
+        '-I{workdir} '
+        '-I{third_party_dir} '
+        '-I{this_dir} '
+        '--plugin=protoc-gen-null=null_plugin.py '
+        '--null_out={workdir} '
+        '{workdir}/test.proto'
+            .format(
+            extended_path=os.path.dirname(sys.executable),
+            python=sys.executable,
+            this_dir=this_dir,
+            workdir=workdir,
+            third_party_dir=third_party_dir
+        ))
+
+    code, output = getstatusoutput(cmd)
+    if code != 0:
+        raise ProtobufCompilationFailedError(output)
+
+    content = base64.decodestring(
+        load_file(os.path.join(workdir, 'protoc.request'), 'rb'))
+    request = CodeGeneratorRequest()
+    request.ParseFromString(content)
+
+    return request
+
+
+def json_rt(data):
+    """
+    JSON round-trip is to simply get rid of OrderedDict, to allow cleaner
+    testing.
+    """
+    return json.loads(json.dumps(data))