3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
19 """Describe ProtoRPC Messages in JSON Schema.
21 Add protorpc.message subclasses to MessageTypeToJsonSchema and get a JSON
22 Schema description of all the messages.
28 from protorpc
import message_types
29 from protorpc
import messages
31 __all__
= ['MessageTypeToJsonSchema']
34 class MessageTypeToJsonSchema(object):
35 """Describe ProtoRPC messages in JSON Schema.
37 Add protorpc.message subclasses to MessageTypeToJsonSchema and get a JSON
38 Schema description of all the messages. MessageTypeToJsonSchema handles
39 all the types of fields that can appear in a message.
48 __FIELD_TO_SCHEMA_TYPE_MAP
= {
49 messages
.IntegerField
: {messages
.Variant
.INT32
: ('integer', 'int32'),
50 messages
.Variant
.INT64
: ('string', 'int64'),
51 messages
.Variant
.UINT32
: ('integer', 'uint32'),
52 messages
.Variant
.UINT64
: ('string', 'uint64'),
53 messages
.Variant
.SINT32
: ('integer', 'int32'),
54 messages
.Variant
.SINT64
: ('string', 'int64'),
55 None: ('integer', 'int64')},
56 messages
.FloatField
: {messages
.Variant
.FLOAT
: ('number', 'float'),
57 messages
.Variant
.DOUBLE
: ('number', 'double'),
58 None: ('number', 'float')},
59 messages
.BooleanField
: ('boolean', None),
60 messages
.BytesField
: ('string', 'byte'),
61 message_types
.DateTimeField
: ('string', 'date-time'),
62 messages
.StringField
: ('string', None),
63 messages
.MessageField
: ('object', None),
64 messages
.EnumField
: ('string', None),
67 __DEFAULT_SCHEMA_TYPE
= ('string', None)
74 self
.__normalized
_names
= {}
76 def add_message(self
, message_type
):
80 message_type: protorpc.message.Message class to be parsed.
83 string, The JSON Schema id.
86 KeyError if the Schema id for this message_type would collide with the
87 Schema id of a different message_type that was already added.
89 name
= self
.__normalized
_name
(message_type
)
90 if name
not in self
.__schemas
:
92 self
.__schemas
[name
] = None
93 schema
= self
.__message
_to
_schema
(message_type
)
94 self
.__schemas
[name
] = schema
97 def ref_for_message_type(self
, message_type
):
98 """Returns the JSON Schema id for the given message.
101 message_type: protorpc.message.Message class to be parsed.
104 string, The JSON Schema id.
107 KeyError: if the message hasn't been parsed via add_message().
109 name
= self
.__normalized
_name
(message_type
)
110 if name
not in self
.__schemas
:
111 raise KeyError('Message has not been parsed: %s', name
)
115 """Returns the JSON Schema of all the messages.
118 object: JSON Schema description of all messages.
120 return self
.__schemas
.copy()
122 def __normalized_name(self
, message_type
):
123 """Normalized schema name.
125 Generate a normalized schema name, taking the class name and stripping out
126 everything but alphanumerics, and camel casing the remaining words.
127 A normalized schema name is a name that matches [a-zA-Z][a-zA-Z0-9]*
130 message_type: protorpc.message.Message class being parsed.
133 A string, the normalized schema name.
136 KeyError if a collision is found between normalized names.
140 name
= message_type
.definition_name()
142 split_name
= re
.split(r
'[^0-9a-zA-Z]', name
)
143 normalized
= ''.join(
144 part
[0].upper() + part
[1:] for part
in split_name
if part
)
146 previous
= self
.__normalized
_names
.get(normalized
)
149 raise KeyError('Both %s and %s normalize to the same schema name: %s' %
150 (name
, previous
, normalized
))
152 self
.__normalized
_names
[normalized
] = name
156 def __message_to_schema(self
, message_type
):
157 """Parse a single message into JSON Schema.
159 Will recursively descend the message structure
160 and also parse other messages references via MessageFields.
163 message_type: protorpc.messages.Message class to parse.
166 An object representation of the schema.
168 name
= self
.__normalized
_name
(message_type
)
173 if message_type
.__doc
__:
174 schema
['description'] = message_type
.__doc
__
176 for field
in message_type
.all_fields():
183 if type(field
) == messages
.MessageField
:
184 field_type
= field
.type().__class
__
185 type_info
['$ref'] = self
.add_message(field_type
)
186 if field_type
.__doc
__:
187 descriptor
['description'] = field_type
.__doc
__
189 schema_type
= self
.__FIELD
_TO
_SCHEMA
_TYPE
_MAP
.get(
190 type(field
), self
.__DEFAULT
_SCHEMA
_TYPE
)
193 if isinstance(schema_type
, dict):
194 variant_map
= schema_type
195 variant
= getattr(field
, 'variant', None)
196 if variant
in variant_map
:
197 schema_type
= variant_map
[variant
]
200 schema_type
= variant_map
[None]
201 type_info
['type'] = schema_type
[0]
203 type_info
['format'] = schema_type
[1]
205 if type(field
) == messages
.EnumField
:
206 sorted_enums
= sorted([enum_info
for enum_info
in field
.type],
207 key
=lambda enum_info
: enum_info
.number
)
208 type_info
['enum'] = [enum_info
.name
for enum_info
in sorted_enums
]
211 descriptor
['required'] = True
214 if type(field
) == messages
.EnumField
:
215 descriptor
['default'] = str(field
.default
)
217 descriptor
['default'] = field
.default
220 descriptor
['items'] = type_info
221 descriptor
['type'] = 'array'
223 descriptor
.update(type_info
)
225 properties
[field
.name
] = descriptor
227 schema
['properties'] = properties