Chameleon swagger support
Change-Id: I63b8dc7b31d5e87aa0e5153da302537d90ff733e
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))