blob: 01a9bc09f1b29a81300ebcee62f17fc9b9dd7ab6 [file] [log] [blame]
Hung-Te Lin1990b742017-08-09 17:34:57 +08001# Copyright 2012 The Chromium OS Authors. All rights reserved.
Ricky Liang1b72ccf2013-03-01 10:23:01 +08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Peter Shih86430492018-02-26 14:51:58 +08005# pylint: disable=protected-access,redefined-builtin
Ricky Liang1b72ccf2013-03-01 10:23:01 +08006
7"""A function to create a schema tree from the given schema expression.
8
9For example:
10
11 1. This is the schema of the encoded_fields in component database.
12
13 Dict('encoded_fields', Scalar('encoded_field', str),
14 Dict('encoded_indices', Scalar('encoded_index', int),
15 Dict('component_classes', Scalar('component_class', str),
16 AnyOf('component_names', [
17 Scalar('component_name', str),
18 List('list_of_component_names', Scalar('component_name', str)),
19 Scalar('none', type(None))
20 ])
21 )
22 )
23 )
24
25 2. This is the schema of the pattern in component database.
26
27 List('pattern',
28 Dict('pattern_field', key_type=Scalar('encoded_index', str),
29 value_type=Scalar('bit_offset', int))
30 )
31
32 3. This is the schema of the components in component database.
33
34 Dict('components', Scalar('component_class', str),
35 Dict('component_names', Scalar('component_name', str),
36 FixedDict('component_attributes',
37 items={
38 'value': AnyOf('probed_value', [
39 Scalar('probed_value', str),
40 List('list_of_probed_values', Scalar('probed_value', str))
41 ])
42 },
43 optional_items={
44 'labels': List('list_of_labels', Scalar('label', str))
45 }
46 )
47 )
48 )
49"""
50
51import copy
Joel Kitchinga8be7962016-06-08 17:35:01 +080052from .type_utils import MakeList
Dean Liao452c6ee2013-03-11 16:23:33 +080053
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080054# To simplify portability issues, validating JSON schema is optional.
55try:
Peter Shih533566a2018-09-05 17:48:03 +080056 # pylint: disable=wrong-import-order
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080057 import jsonschema
58 _HAVE_JSONSCHEMA = True
59except ImportError:
60 _HAVE_JSONSCHEMA = False
61
Ricky Liang1b72ccf2013-03-01 10:23:01 +080062
63class SchemaException(Exception):
64 pass
65
66
67class BaseType(object):
68 """Base type class for schema classes.
69 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080070
Ricky Liang1b72ccf2013-03-01 10:23:01 +080071 def __init__(self, label):
Yong Hong3532ae82017-12-29 16:05:46 +080072 self.label = label
Ricky Liang1b72ccf2013-03-01 10:23:01 +080073
Dean Liao604e62b2013-03-11 19:12:50 +080074 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +080075 return 'BaseType(%r)' % self.label
Dean Liao604e62b2013-03-11 19:12:50 +080076
Ricky Liang1b72ccf2013-03-01 10:23:01 +080077 def Validate(self, data):
78 raise NotImplementedError
79
80
81class Scalar(BaseType):
82 """Scalar schema class.
83
84 Attributes:
85 label: A human-readable string to describe this Scalar.
86 element_type: The Python type of this Scalar. Cannot be a iterable type.
Jon Salz05fffde2014-07-14 12:56:47 +080087 choices: A set of allowable choices for the scalar, or None to allow
88 any values of the given type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +080089
90 Raises:
91 SchemaException if argument format is incorrect.
92 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080093
Jon Salz05fffde2014-07-14 12:56:47 +080094 def __init__(self, label, element_type, choices=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +080095 super(Scalar, self).__init__(label)
96 if getattr(element_type, '__iter__', None):
Dean Liao604e62b2013-03-11 19:12:50 +080097 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +080098 'element_type %r of Scalar %r is not a scalar type' % (element_type,
99 label))
Yong Hong3532ae82017-12-29 16:05:46 +0800100 self.element_type = element_type
101 self.choices = set(choices) if choices else None
Jon Salz05fffde2014-07-14 12:56:47 +0800102
Dean Liao604e62b2013-03-11 19:12:50 +0800103 def __repr__(self):
Jon Salz05fffde2014-07-14 12:56:47 +0800104 return 'Scalar(%r, %r%s)' % (
Yong Hong3532ae82017-12-29 16:05:46 +0800105 self.label, self.element_type,
106 ', choices=%r' % sorted(self.choices) if self.choices else '')
Dean Liao604e62b2013-03-11 19:12:50 +0800107
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800108 def Validate(self, data):
109 """Validates the given data against the Scalar schema.
110
111 It checks if the data's type matches the Scalar's element type. Also, it
112 checks if the data's value matches the Scalar's value if the required value
113 is specified.
114
115 Args:
116 data: A Python data structure to be validated.
117
118 Raises:
119 SchemaException if validation fails.
120 """
Yong Hong3532ae82017-12-29 16:05:46 +0800121 if not isinstance(data, self.element_type):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800122 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800123 (data, self.element_type, type(data)))
124 if self.choices and data not in self.choices:
Jon Salz05fffde2014-07-14 12:56:47 +0800125 raise SchemaException('Value mismatch on %r: expected one of %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800126 (data, sorted(self.choices)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800127
128
Yong Hong5961ad32019-05-14 17:43:18 +0800129class RegexpStr(Scalar):
130 """Schema class for a string which matches the specific regular expression.
131
132 Attributes:
133 label: A human-readable string to describe this Scalar.
134 regexp: A regular expression object to match.
135
136 Raises:
137 SchemaException if argument format is incorrect.
138 """
139
140 def __init__(self, label, regexp):
141 super(RegexpStr, self).__init__(label, str)
142 self.regexp = regexp
143
144 def __repr__(self):
145 return 'RegexpStr(%r, %s)' % (self.label, self.regexp.pattern)
146
147 def __deepcopy__(self, memo):
148 return RegexpStr(self.label, self.regexp)
149
150 def Validate(self, data):
151 """Validates the given data against the RegexpStr schema.
152
153 It first checks if the data's type is `str`. Then, it checks if the
154 value matches the regular expression.
155
156 Args:
157 data: A Python data structure to be validated.
158
159 Raises:
160 SchemaException if validation fails.
161 """
162 super(RegexpStr, self).Validate(data)
163 if not self.regexp.match(data):
164 raise SchemaException("Value %r doesn't match regeular expression %s" %
165 (data, self.regexp.pattern))
166
167
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800168class Dict(BaseType):
169 """Dict schema class.
170
171 This schema class is used to verify simple dict. Only the key type and value
172 type are validated.
173
174 Attributes:
175 label: A human-readable string to describe this Scalar.
Ricky Liangf5386b32013-03-11 16:40:45 +0800176 key_type: A schema object indicating the schema of the keys of this Dict. It
177 can be a Scalar or an AnyOf with possible values being all Scalars.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800178 value_type: A schema object indicating the schema of the values of this
179 Dict.
Yong Hong3532ae82017-12-29 16:05:46 +0800180 min_size: The minimum size of the elements, default to 0.
181 max_size: None or the maximum size of the elements.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800182
183 Raises:
184 SchemaException if argument format is incorrect.
185 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800186
Yong Hong3532ae82017-12-29 16:05:46 +0800187 def __init__(self, label, key_type, value_type, min_size=0, max_size=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800188 super(Dict, self).__init__(label)
Ricky Liangf5386b32013-03-11 16:40:45 +0800189 if not (isinstance(key_type, Scalar) or
Hung-Te Lin56b18402015-01-16 14:52:30 +0800190 (isinstance(key_type, AnyOf) and
191 key_type.CheckTypeOfPossibleValues(Scalar))):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800192 raise SchemaException('key_type %r of Dict %r is not Scalar' %
Yong Hong3532ae82017-12-29 16:05:46 +0800193 (key_type, self.label))
194 self.key_type = key_type
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800195 if not isinstance(value_type, BaseType):
196 raise SchemaException('value_type %r of Dict %r is not Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800197 (value_type, self.label))
198 self.value_type = value_type
199 self.min_size = min_size
200 self.max_size = max_size
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800201
Dean Liao604e62b2013-03-11 19:12:50 +0800202 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800203 size_expr = '[%d, %s]' % (self.min_size, ('inf' if self.max_size is None
204 else '%d' % self.max_size))
205 return 'Dict(%r, key_type=%r, value_type=%r, size=%s)' % (
206 self.label, self.key_type, self.value_type, size_expr)
Dean Liao604e62b2013-03-11 19:12:50 +0800207
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800208 def Validate(self, data):
209 """Validates the given data against the Dict schema.
210
211 It checks that all the keys in data matches the schema defined by key_type,
212 and all the values in data matches the schema defined by value_type.
213
214 Args:
215 data: A Python data structure to be validated.
216
217 Raises:
218 SchemaException if validation fails.
219 """
220 if not isinstance(data, dict):
221 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800222 (self.label, type(data)))
223
224 if len(data) < self.min_size:
225 raise SchemaException('Size mismatch on %r: expected size >= %r' %
226 (self.label, self.min_size))
227
228 if self.max_size is not None and self.max_size < len(data):
229 raise SchemaException('Size mismatch on %r: expected size <= %r' %
230 (self.label, self.max_size))
231
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800232 for k, v in data.iteritems():
Yong Hong3532ae82017-12-29 16:05:46 +0800233 self.key_type.Validate(k)
234 self.value_type.Validate(v)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800235
236
237class FixedDict(BaseType):
238 """FixedDict schema class.
239
240 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
241 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
242 in XML.
243
244 An example FixedDict schema:
245 FixedDict('foo',
246 items={
247 'a': Scalar('bar', str),
248 'b': Scalar('buz', int)
249 }, optional_items={
250 'c': Scalar('boo', int)
251 })
252
253 Attributes:
254 label: A human-readable string to describe this dict.
255 items: A dict of required items that must be specified.
256 optional_items: A dict of optional items.
Yong Hong5961ad32019-05-14 17:43:18 +0800257 allow_undefined_keys: A boolean that indicates whether additional items
258 that is not recorded in both `items` and `optional_items` are allowed
259 or not.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800260
261 Raises:
262 SchemaException if argument format is incorrect.
263 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800264
Yong Hong5961ad32019-05-14 17:43:18 +0800265 def __init__(self, label, items=None, optional_items=None,
266 allow_undefined_keys=False):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800267 super(FixedDict, self).__init__(label)
268 if items and not isinstance(items, dict):
269 raise SchemaException('items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800270 self.label)
271 self.items = copy.deepcopy(items) if items is not None else {}
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800272 if optional_items and not isinstance(optional_items, dict):
273 raise SchemaException('optional_items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800274 self.label)
275 self.optional_items = (
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800276 copy.deepcopy(optional_items) if optional_items is not None else {})
Yong Hong5961ad32019-05-14 17:43:18 +0800277 self.allow_undefined_keys = allow_undefined_keys
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800278
Dean Liao604e62b2013-03-11 19:12:50 +0800279 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800280 return 'FixedDict(%r, items=%r, optional_items=%r)' % (self.label,
281 self.items,
282 self.optional_items)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800283
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800284 def Validate(self, data):
285 """Validates the given data and all its key-value pairs against the Dict
286 schema.
287
288 If a key of Dict's type is required, then it must exist in the data's keys.
Yong Hong5961ad32019-05-14 17:43:18 +0800289 If `self.allow_undefined_keys` is `False` and some items in the given data
290 are not in either `self.items` or `self.optional_items`, the method will
291 raise `SchemaException`.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800292
293 Args:
294 data: A Python data structure to be validated.
295
296 Raises:
297 SchemaException if validation fails.
298 """
299 if not isinstance(data, dict):
300 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800301 (self.label, type(data)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800302 data_key_list = data.keys()
303 # Check that every key-value pair in items exists in data
Yong Hong3532ae82017-12-29 16:05:46 +0800304 for key, value_schema in self.items.iteritems():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800305 if key not in data:
306 raise SchemaException(
307 'Required item %r does not exist in FixedDict %r' %
308 (key, data))
309 value_schema.Validate(data[key])
310 data_key_list.remove(key)
311 # Check that all the remaining unmatched key-value pairs matches any
312 # definition in items or optional_items.
Yong Hong3532ae82017-12-29 16:05:46 +0800313 for key, value_schema in self.optional_items.iteritems():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800314 if key not in data:
315 continue
316 value_schema.Validate(data[key])
317 data_key_list.remove(key)
Yong Hong5961ad32019-05-14 17:43:18 +0800318 if not self.allow_undefined_keys and data_key_list:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800319 raise SchemaException('Keys %r are undefined in FixedDict %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800320 (data_key_list, self.label))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800321
322
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800323class JSONSchemaDict(BaseType):
324 """JSON schema class.
325
326 This schema class allows mixing JSON schema with other schema types.
327
328 Attributes:
329 label: A human-readable string to describe this JSON schema.
330 schema: a JSON schema object.
331
332 Raises:
Fei Shao186d25b2018-11-09 16:55:48 +0800333 SchemaException if given schema is invalid (SchemaError) or fail
334 to validate data using the schema (ValidationError).
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800335 """
336 def __init__(self, label, schema):
337 super(JSONSchemaDict, self).__init__(label)
Yong Hong3532ae82017-12-29 16:05:46 +0800338 self.label = label
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800339 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800340 try:
341 jsonschema.Draft4Validator.check_schema(schema)
342 except Exception as e:
343 raise SchemaException('Schema %r is invalid: %r' % (schema, e))
Yong Hong3532ae82017-12-29 16:05:46 +0800344 self.schema = schema
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800345
346 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800347 return 'JSONSchemaDict(%r, %r)' % (self.label, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800348
349 def Validate(self, data):
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800350 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800351 try:
352 jsonschema.validate(data, self.schema)
353 except Exception as e:
354 raise SchemaException('Fail to validate %r with JSON schema %r: %r' %
355 (data, self.schema, e))
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800356
357
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800358class List(BaseType):
359 """List schema class.
360
361 Attributes:
362 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800363 element_type: Optional schema object to validate the elements of the list.
364 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800365 min_length: The expected minimum length of the list. Default to 0.
366 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800367
368 Raises:
369 SchemaException if argument format is incorrect.
370 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800371
Yong Hong3532ae82017-12-29 16:05:46 +0800372 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800373 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800374 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800375 raise SchemaException(
376 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800377 (element_type, self.label))
378 self.element_type = copy.deepcopy(element_type)
379 self.min_length = min_length
380 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800381
Dean Liao604e62b2013-03-11 19:12:50 +0800382 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800383 max_bound_repr = ('inf' if self.max_length is None
384 else '%d' % self.max_length)
385 return 'List(%r, %r, [%r, %s])' % (
386 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800387
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800388 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800389 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800390
391 Args:
392 data: A Python data structure to be validated.
393
394 Raises:
395 SchemaException if validation fails.
396 """
397 if not isinstance(data, list):
398 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800399 (self.label, type(data)))
400
401 if len(data) < self.min_length:
402 raise SchemaException('Length mismatch on %r: expected length >= %d' %
403 (self.label, self.min_length))
404
405 if self.max_length is not None and self.max_length < len(data):
406 raise SchemaException('Length mismatch on %r: expected length <= %d' %
407 (self.label, self.max_length))
408
409 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800410 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800411 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800412
413
Ricky Liang3e5342b2013-03-08 12:16:25 +0800414class Tuple(BaseType):
415 """Tuple schema class.
416
417 Comparing to List, the Tuple schema makes sure that every element exactly
418 matches the defined position and schema.
419
420 Attributes:
421 label: A string to describe this tuple.
422 element_types: Optional list or tuple schema object to describe the
423 types of the Tuple.
424
425 Raises:
426 SchemaException if argument format is incorrect.
427 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800428
Ricky Liang3e5342b2013-03-08 12:16:25 +0800429 def __init__(self, label, element_types=None):
430 super(Tuple, self).__init__(label)
431 if (element_types and
432 (not isinstance(element_types, (tuple, list))) or
433 (not all([isinstance(x, BaseType)] for x in element_types))):
434 raise SchemaException(
435 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800436 (element_types, self.label))
437 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800438
Dean Liao604e62b2013-03-11 19:12:50 +0800439 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800440 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800441
Ricky Liang3e5342b2013-03-08 12:16:25 +0800442 def Validate(self, data):
443 """Validates the given data and all its elements against the Tuple schema.
444
445 Args:
446 data: A Python data structure to be validated.
447
448 Raises:
449 SchemaException if validation fails.
450 """
451 if not isinstance(data, tuple):
452 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800453 (self.label, type(data)))
454 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800455 raise SchemaException(
456 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800457 'in Tuple schema %r' % (str(data), self.label))
458 for data, element_type in zip(data, self.element_types):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800459 element_type.Validate(data)
460
461
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800462class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800463 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800464
465 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800466 types: A list of Schema objects to be matched.
467 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800468 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800469
Dean Liao604e62b2013-03-11 19:12:50 +0800470 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800471 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800472 if (not isinstance(types, list) or
473 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800474 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800475 'types in AnyOf(types=%r%s) should be a list of Schemas' %
476 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800477 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800478
479 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800480 label = '' if self.label is None else ', label=%r' % self.label
481 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800482
Ricky Liangf5386b32013-03-11 16:40:45 +0800483 def CheckTypeOfPossibleValues(self, schema_type):
484 """Checks if the acceptable types are of the same type as schema_type.
485
486 Args:
487 schema_type: The schema type to check against with.
488 """
Yong Hong3532ae82017-12-29 16:05:46 +0800489 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800490
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800491 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800492 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800493
494 Args:
495 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800496
497 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800498 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800499 """
500 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800501 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800502 try:
503 schema_type.Validate(data)
504 except SchemaException:
505 continue
506 match = True
507 break
508 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800509 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800510 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800511
512
513class Optional(AnyOf):
514 """A Schema class which accepts either None or given Schemas.
515
516 It is a special case of AnyOf class: in addition of given schema(s), it also
517 accepts None.
518
519 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800520 types: A (or a list of) Schema object(s) to be matched.
521 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800522 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800523
Dean Liao604e62b2013-03-11 19:12:50 +0800524 def __init__(self, types, label=None):
525 try:
526 super(Optional, self).__init__(MakeList(types), label=label)
527 except SchemaException:
528 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800529 'types in Optional(types=%r%s) should be a Schema or a list of '
530 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800531
532 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800533 label = '' if self.label is None else ', label=%r' % self.label
534 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800535
536 def Validate(self, data):
537 """Validates if the given data is None or matches any schema in types.
538
539 Args:
540 data: A Python data structue to be validated.
541
542 Raises:
543 SchemaException if data is not None and no schemas in types validates the
544 input data.
545 """
546 if data is None:
547 return
548 try:
549 super(Optional, self).Validate(data)
550 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800551 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800552 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800553 self.types))