blob: 2cdd23980204b8d3ca21dc1cbc5a7daec4537e9f [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
Yilin Yangea784662019-09-26 13:51:03 +080052
53from six import iteritems
54
Joel Kitchinga8be7962016-06-08 17:35:01 +080055from .type_utils import MakeList
Dean Liao452c6ee2013-03-11 16:23:33 +080056
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080057# To simplify portability issues, validating JSON schema is optional.
58try:
Peter Shih533566a2018-09-05 17:48:03 +080059 # pylint: disable=wrong-import-order
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080060 import jsonschema
61 _HAVE_JSONSCHEMA = True
62except ImportError:
63 _HAVE_JSONSCHEMA = False
64
Ricky Liang1b72ccf2013-03-01 10:23:01 +080065
66class SchemaException(Exception):
67 pass
68
69
70class BaseType(object):
71 """Base type class for schema classes.
72 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080073
Ricky Liang1b72ccf2013-03-01 10:23:01 +080074 def __init__(self, label):
Yong Hong3532ae82017-12-29 16:05:46 +080075 self.label = label
Ricky Liang1b72ccf2013-03-01 10:23:01 +080076
Dean Liao604e62b2013-03-11 19:12:50 +080077 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +080078 return 'BaseType(%r)' % self.label
Dean Liao604e62b2013-03-11 19:12:50 +080079
Ricky Liang1b72ccf2013-03-01 10:23:01 +080080 def Validate(self, data):
81 raise NotImplementedError
82
83
84class Scalar(BaseType):
85 """Scalar schema class.
86
87 Attributes:
88 label: A human-readable string to describe this Scalar.
89 element_type: The Python type of this Scalar. Cannot be a iterable type.
Jon Salz05fffde2014-07-14 12:56:47 +080090 choices: A set of allowable choices for the scalar, or None to allow
91 any values of the given type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +080092
93 Raises:
94 SchemaException if argument format is incorrect.
95 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080096
Jon Salz05fffde2014-07-14 12:56:47 +080097 def __init__(self, label, element_type, choices=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +080098 super(Scalar, self).__init__(label)
99 if getattr(element_type, '__iter__', None):
Dean Liao604e62b2013-03-11 19:12:50 +0800100 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800101 'element_type %r of Scalar %r is not a scalar type' % (element_type,
102 label))
Yong Hong3532ae82017-12-29 16:05:46 +0800103 self.element_type = element_type
104 self.choices = set(choices) if choices else None
Jon Salz05fffde2014-07-14 12:56:47 +0800105
Dean Liao604e62b2013-03-11 19:12:50 +0800106 def __repr__(self):
Jon Salz05fffde2014-07-14 12:56:47 +0800107 return 'Scalar(%r, %r%s)' % (
Yong Hong3532ae82017-12-29 16:05:46 +0800108 self.label, self.element_type,
109 ', choices=%r' % sorted(self.choices) if self.choices else '')
Dean Liao604e62b2013-03-11 19:12:50 +0800110
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800111 def Validate(self, data):
112 """Validates the given data against the Scalar schema.
113
114 It checks if the data's type matches the Scalar's element type. Also, it
115 checks if the data's value matches the Scalar's value if the required value
116 is specified.
117
118 Args:
119 data: A Python data structure to be validated.
120
121 Raises:
122 SchemaException if validation fails.
123 """
Yong Hong3532ae82017-12-29 16:05:46 +0800124 if not isinstance(data, self.element_type):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800125 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800126 (data, self.element_type, type(data)))
127 if self.choices and data not in self.choices:
Jon Salz05fffde2014-07-14 12:56:47 +0800128 raise SchemaException('Value mismatch on %r: expected one of %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800129 (data, sorted(self.choices)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800130
131
Yong Hong5961ad32019-05-14 17:43:18 +0800132class RegexpStr(Scalar):
133 """Schema class for a string which matches the specific regular expression.
134
135 Attributes:
136 label: A human-readable string to describe this Scalar.
137 regexp: A regular expression object to match.
138
139 Raises:
140 SchemaException if argument format is incorrect.
141 """
142
143 def __init__(self, label, regexp):
144 super(RegexpStr, self).__init__(label, str)
145 self.regexp = regexp
146
147 def __repr__(self):
148 return 'RegexpStr(%r, %s)' % (self.label, self.regexp.pattern)
149
150 def __deepcopy__(self, memo):
151 return RegexpStr(self.label, self.regexp)
152
153 def Validate(self, data):
154 """Validates the given data against the RegexpStr schema.
155
156 It first checks if the data's type is `str`. Then, it checks if the
157 value matches the regular expression.
158
159 Args:
160 data: A Python data structure to be validated.
161
162 Raises:
163 SchemaException if validation fails.
164 """
165 super(RegexpStr, self).Validate(data)
166 if not self.regexp.match(data):
167 raise SchemaException("Value %r doesn't match regeular expression %s" %
168 (data, self.regexp.pattern))
169
170
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800171class Dict(BaseType):
172 """Dict schema class.
173
174 This schema class is used to verify simple dict. Only the key type and value
175 type are validated.
176
177 Attributes:
178 label: A human-readable string to describe this Scalar.
Ricky Liangf5386b32013-03-11 16:40:45 +0800179 key_type: A schema object indicating the schema of the keys of this Dict. It
180 can be a Scalar or an AnyOf with possible values being all Scalars.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800181 value_type: A schema object indicating the schema of the values of this
182 Dict.
Yong Hong3532ae82017-12-29 16:05:46 +0800183 min_size: The minimum size of the elements, default to 0.
184 max_size: None or the maximum size of the elements.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800185
186 Raises:
187 SchemaException if argument format is incorrect.
188 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800189
Yong Hong3532ae82017-12-29 16:05:46 +0800190 def __init__(self, label, key_type, value_type, min_size=0, max_size=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800191 super(Dict, self).__init__(label)
Ricky Liangf5386b32013-03-11 16:40:45 +0800192 if not (isinstance(key_type, Scalar) or
Hung-Te Lin56b18402015-01-16 14:52:30 +0800193 (isinstance(key_type, AnyOf) and
194 key_type.CheckTypeOfPossibleValues(Scalar))):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800195 raise SchemaException('key_type %r of Dict %r is not Scalar' %
Yong Hong3532ae82017-12-29 16:05:46 +0800196 (key_type, self.label))
197 self.key_type = key_type
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800198 if not isinstance(value_type, BaseType):
199 raise SchemaException('value_type %r of Dict %r is not Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800200 (value_type, self.label))
201 self.value_type = value_type
202 self.min_size = min_size
203 self.max_size = max_size
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800204
Dean Liao604e62b2013-03-11 19:12:50 +0800205 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800206 size_expr = '[%d, %s]' % (self.min_size, ('inf' if self.max_size is None
207 else '%d' % self.max_size))
208 return 'Dict(%r, key_type=%r, value_type=%r, size=%s)' % (
209 self.label, self.key_type, self.value_type, size_expr)
Dean Liao604e62b2013-03-11 19:12:50 +0800210
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800211 def Validate(self, data):
212 """Validates the given data against the Dict schema.
213
214 It checks that all the keys in data matches the schema defined by key_type,
215 and all the values in data matches the schema defined by value_type.
216
217 Args:
218 data: A Python data structure to be validated.
219
220 Raises:
221 SchemaException if validation fails.
222 """
223 if not isinstance(data, dict):
224 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800225 (self.label, type(data)))
226
227 if len(data) < self.min_size:
228 raise SchemaException('Size mismatch on %r: expected size >= %r' %
229 (self.label, self.min_size))
230
231 if self.max_size is not None and self.max_size < len(data):
232 raise SchemaException('Size mismatch on %r: expected size <= %r' %
233 (self.label, self.max_size))
234
Yilin Yangea784662019-09-26 13:51:03 +0800235 for k, v in iteritems(data):
Yong Hong3532ae82017-12-29 16:05:46 +0800236 self.key_type.Validate(k)
237 self.value_type.Validate(v)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800238
239
240class FixedDict(BaseType):
241 """FixedDict schema class.
242
243 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
244 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
245 in XML.
246
247 An example FixedDict schema:
248 FixedDict('foo',
249 items={
250 'a': Scalar('bar', str),
251 'b': Scalar('buz', int)
252 }, optional_items={
253 'c': Scalar('boo', int)
254 })
255
256 Attributes:
257 label: A human-readable string to describe this dict.
258 items: A dict of required items that must be specified.
259 optional_items: A dict of optional items.
Yong Hong5961ad32019-05-14 17:43:18 +0800260 allow_undefined_keys: A boolean that indicates whether additional items
261 that is not recorded in both `items` and `optional_items` are allowed
262 or not.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800263
264 Raises:
265 SchemaException if argument format is incorrect.
266 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800267
Yong Hong5961ad32019-05-14 17:43:18 +0800268 def __init__(self, label, items=None, optional_items=None,
269 allow_undefined_keys=False):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800270 super(FixedDict, self).__init__(label)
271 if items and not isinstance(items, dict):
272 raise SchemaException('items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800273 self.label)
274 self.items = copy.deepcopy(items) if items is not None else {}
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800275 if optional_items and not isinstance(optional_items, dict):
276 raise SchemaException('optional_items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800277 self.label)
278 self.optional_items = (
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800279 copy.deepcopy(optional_items) if optional_items is not None else {})
Yong Hong5961ad32019-05-14 17:43:18 +0800280 self.allow_undefined_keys = allow_undefined_keys
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800281
Dean Liao604e62b2013-03-11 19:12:50 +0800282 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800283 return 'FixedDict(%r, items=%r, optional_items=%r)' % (self.label,
284 self.items,
285 self.optional_items)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800286
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800287 def Validate(self, data):
288 """Validates the given data and all its key-value pairs against the Dict
289 schema.
290
291 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 +0800292 If `self.allow_undefined_keys` is `False` and some items in the given data
293 are not in either `self.items` or `self.optional_items`, the method will
294 raise `SchemaException`.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800295
296 Args:
297 data: A Python data structure to be validated.
298
299 Raises:
300 SchemaException if validation fails.
301 """
302 if not isinstance(data, dict):
303 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800304 (self.label, type(data)))
Yilin Yang78fa12e2019-09-25 14:21:10 +0800305 data_key_list = list(data)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800306 # Check that every key-value pair in items exists in data
Yilin Yangea784662019-09-26 13:51:03 +0800307 for key, value_schema in iteritems(self.items):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800308 if key not in data:
309 raise SchemaException(
310 'Required item %r does not exist in FixedDict %r' %
311 (key, data))
312 value_schema.Validate(data[key])
313 data_key_list.remove(key)
314 # Check that all the remaining unmatched key-value pairs matches any
315 # definition in items or optional_items.
Yilin Yangea784662019-09-26 13:51:03 +0800316 for key, value_schema in iteritems(self.optional_items):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800317 if key not in data:
318 continue
319 value_schema.Validate(data[key])
320 data_key_list.remove(key)
Yong Hong5961ad32019-05-14 17:43:18 +0800321 if not self.allow_undefined_keys and data_key_list:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800322 raise SchemaException('Keys %r are undefined in FixedDict %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800323 (data_key_list, self.label))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800324
325
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800326class JSONSchemaDict(BaseType):
327 """JSON schema class.
328
329 This schema class allows mixing JSON schema with other schema types.
330
331 Attributes:
332 label: A human-readable string to describe this JSON schema.
333 schema: a JSON schema object.
334
335 Raises:
Fei Shao186d25b2018-11-09 16:55:48 +0800336 SchemaException if given schema is invalid (SchemaError) or fail
337 to validate data using the schema (ValidationError).
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800338 """
339 def __init__(self, label, schema):
340 super(JSONSchemaDict, self).__init__(label)
Yong Hong3532ae82017-12-29 16:05:46 +0800341 self.label = label
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800342 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800343 try:
344 jsonschema.Draft4Validator.check_schema(schema)
345 except Exception as e:
346 raise SchemaException('Schema %r is invalid: %r' % (schema, e))
Yong Hong3532ae82017-12-29 16:05:46 +0800347 self.schema = schema
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800348
349 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800350 return 'JSONSchemaDict(%r, %r)' % (self.label, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800351
352 def Validate(self, data):
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800353 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800354 try:
355 jsonschema.validate(data, self.schema)
356 except Exception as e:
357 raise SchemaException('Fail to validate %r with JSON schema %r: %r' %
358 (data, self.schema, e))
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800359
360
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800361class List(BaseType):
362 """List schema class.
363
364 Attributes:
365 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800366 element_type: Optional schema object to validate the elements of the list.
367 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800368 min_length: The expected minimum length of the list. Default to 0.
369 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800370
371 Raises:
372 SchemaException if argument format is incorrect.
373 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800374
Yong Hong3532ae82017-12-29 16:05:46 +0800375 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800376 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800377 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800378 raise SchemaException(
379 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800380 (element_type, self.label))
381 self.element_type = copy.deepcopy(element_type)
382 self.min_length = min_length
383 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800384
Dean Liao604e62b2013-03-11 19:12:50 +0800385 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800386 max_bound_repr = ('inf' if self.max_length is None
387 else '%d' % self.max_length)
388 return 'List(%r, %r, [%r, %s])' % (
389 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800390
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800391 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800392 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800393
394 Args:
395 data: A Python data structure to be validated.
396
397 Raises:
398 SchemaException if validation fails.
399 """
400 if not isinstance(data, list):
401 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800402 (self.label, type(data)))
403
404 if len(data) < self.min_length:
405 raise SchemaException('Length mismatch on %r: expected length >= %d' %
406 (self.label, self.min_length))
407
408 if self.max_length is not None and self.max_length < len(data):
409 raise SchemaException('Length mismatch on %r: expected length <= %d' %
410 (self.label, self.max_length))
411
412 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800413 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800414 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800415
416
Ricky Liang3e5342b2013-03-08 12:16:25 +0800417class Tuple(BaseType):
418 """Tuple schema class.
419
420 Comparing to List, the Tuple schema makes sure that every element exactly
421 matches the defined position and schema.
422
423 Attributes:
424 label: A string to describe this tuple.
425 element_types: Optional list or tuple schema object to describe the
426 types of the Tuple.
427
428 Raises:
429 SchemaException if argument format is incorrect.
430 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800431
Ricky Liang3e5342b2013-03-08 12:16:25 +0800432 def __init__(self, label, element_types=None):
433 super(Tuple, self).__init__(label)
434 if (element_types and
435 (not isinstance(element_types, (tuple, list))) or
436 (not all([isinstance(x, BaseType)] for x in element_types))):
437 raise SchemaException(
438 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800439 (element_types, self.label))
440 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800441
Dean Liao604e62b2013-03-11 19:12:50 +0800442 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800443 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800444
Ricky Liang3e5342b2013-03-08 12:16:25 +0800445 def Validate(self, data):
446 """Validates the given data and all its elements against the Tuple schema.
447
448 Args:
449 data: A Python data structure to be validated.
450
451 Raises:
452 SchemaException if validation fails.
453 """
454 if not isinstance(data, tuple):
455 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800456 (self.label, type(data)))
457 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800458 raise SchemaException(
459 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800460 'in Tuple schema %r' % (str(data), self.label))
Yilin Yangd3d97b02020-01-14 16:46:33 +0800461 for content, element_type in zip(data, self.element_types):
462 element_type.Validate(content)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800463
464
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800465class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800466 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800467
468 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800469 types: A list of Schema objects to be matched.
470 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800471 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800472
Dean Liao604e62b2013-03-11 19:12:50 +0800473 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800474 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800475 if (not isinstance(types, list) or
476 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800477 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800478 'types in AnyOf(types=%r%s) should be a list of Schemas' %
479 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800480 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800481
482 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800483 label = '' if self.label is None else ', label=%r' % self.label
484 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800485
Ricky Liangf5386b32013-03-11 16:40:45 +0800486 def CheckTypeOfPossibleValues(self, schema_type):
487 """Checks if the acceptable types are of the same type as schema_type.
488
489 Args:
490 schema_type: The schema type to check against with.
491 """
Yong Hong3532ae82017-12-29 16:05:46 +0800492 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800493
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800494 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800495 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800496
497 Args:
498 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800499
500 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800501 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800502 """
503 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800504 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800505 try:
506 schema_type.Validate(data)
507 except SchemaException:
508 continue
509 match = True
510 break
511 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800512 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800513 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800514
515
516class Optional(AnyOf):
517 """A Schema class which accepts either None or given Schemas.
518
519 It is a special case of AnyOf class: in addition of given schema(s), it also
520 accepts None.
521
522 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800523 types: A (or a list of) Schema object(s) to be matched.
524 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800525 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800526
Dean Liao604e62b2013-03-11 19:12:50 +0800527 def __init__(self, types, label=None):
528 try:
529 super(Optional, self).__init__(MakeList(types), label=label)
530 except SchemaException:
531 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800532 'types in Optional(types=%r%s) should be a Schema or a list of '
533 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800534
535 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800536 label = '' if self.label is None else ', label=%r' % self.label
537 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800538
539 def Validate(self, data):
540 """Validates if the given data is None or matches any schema in types.
541
542 Args:
543 data: A Python data structue to be validated.
544
545 Raises:
546 SchemaException if data is not None and no schemas in types validates the
547 input data.
548 """
549 if data is None:
550 return
551 try:
552 super(Optional, self).Validate(data)
553 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800554 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800555 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800556 self.types))