blob: a8d0dad021ed84db78a0121973118d18ee9e3882 [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
Joel Kitchinga8be7962016-06-08 17:35:01 +080053from .type_utils import MakeList
Dean Liao452c6ee2013-03-11 16:23:33 +080054
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080055# To simplify portability issues, validating JSON schema is optional.
56try:
Peter Shih533566a2018-09-05 17:48:03 +080057 # pylint: disable=wrong-import-order
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +080058 import jsonschema
59 _HAVE_JSONSCHEMA = True
60except ImportError:
61 _HAVE_JSONSCHEMA = False
62
Ricky Liang1b72ccf2013-03-01 10:23:01 +080063
64class SchemaException(Exception):
65 pass
66
67
68class BaseType(object):
69 """Base type class for schema classes.
70 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080071
Ricky Liang1b72ccf2013-03-01 10:23:01 +080072 def __init__(self, label):
Yong Hong3532ae82017-12-29 16:05:46 +080073 self.label = label
Ricky Liang1b72ccf2013-03-01 10:23:01 +080074
Dean Liao604e62b2013-03-11 19:12:50 +080075 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +080076 return 'BaseType(%r)' % self.label
Dean Liao604e62b2013-03-11 19:12:50 +080077
Ricky Liang1b72ccf2013-03-01 10:23:01 +080078 def Validate(self, data):
79 raise NotImplementedError
80
81
82class Scalar(BaseType):
83 """Scalar schema class.
84
85 Attributes:
86 label: A human-readable string to describe this Scalar.
87 element_type: The Python type of this Scalar. Cannot be a iterable type.
Jon Salz05fffde2014-07-14 12:56:47 +080088 choices: A set of allowable choices for the scalar, or None to allow
89 any values of the given type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +080090
91 Raises:
92 SchemaException if argument format is incorrect.
93 """
Hung-Te Lin56b18402015-01-16 14:52:30 +080094
Jon Salz05fffde2014-07-14 12:56:47 +080095 def __init__(self, label, element_type, choices=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +080096 super(Scalar, self).__init__(label)
Yilin Yang4d4bcab2019-11-15 13:37:28 +080097 if getattr(element_type, '__iter__', None) and element_type not in (
98 str, bytes):
Dean Liao604e62b2013-03-11 19:12:50 +080099 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800100 'element_type %r of Scalar %r is not a scalar type' % (element_type,
101 label))
Yong Hong3532ae82017-12-29 16:05:46 +0800102 self.element_type = element_type
103 self.choices = set(choices) if choices else None
Jon Salz05fffde2014-07-14 12:56:47 +0800104
Dean Liao604e62b2013-03-11 19:12:50 +0800105 def __repr__(self):
Jon Salz05fffde2014-07-14 12:56:47 +0800106 return 'Scalar(%r, %r%s)' % (
Yong Hong3532ae82017-12-29 16:05:46 +0800107 self.label, self.element_type,
108 ', choices=%r' % sorted(self.choices) if self.choices else '')
Dean Liao604e62b2013-03-11 19:12:50 +0800109
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800110 def Validate(self, data):
111 """Validates the given data against the Scalar schema.
112
113 It checks if the data's type matches the Scalar's element type. Also, it
114 checks if the data's value matches the Scalar's value if the required value
115 is specified.
116
117 Args:
118 data: A Python data structure to be validated.
119
120 Raises:
121 SchemaException if validation fails.
122 """
Yong Hong3532ae82017-12-29 16:05:46 +0800123 if not isinstance(data, self.element_type):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800124 raise SchemaException('Type mismatch on %r: expected %r, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800125 (data, self.element_type, type(data)))
126 if self.choices and data not in self.choices:
Jon Salz05fffde2014-07-14 12:56:47 +0800127 raise SchemaException('Value mismatch on %r: expected one of %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800128 (data, sorted(self.choices)))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800129
130
Yong Hong5961ad32019-05-14 17:43:18 +0800131class RegexpStr(Scalar):
132 """Schema class for a string which matches the specific regular expression.
133
134 Attributes:
135 label: A human-readable string to describe this Scalar.
136 regexp: A regular expression object to match.
137
138 Raises:
139 SchemaException if argument format is incorrect.
140 """
141
142 def __init__(self, label, regexp):
143 super(RegexpStr, self).__init__(label, str)
144 self.regexp = regexp
145
146 def __repr__(self):
147 return 'RegexpStr(%r, %s)' % (self.label, self.regexp.pattern)
148
149 def __deepcopy__(self, memo):
150 return RegexpStr(self.label, self.regexp)
151
152 def Validate(self, data):
153 """Validates the given data against the RegexpStr schema.
154
155 It first checks if the data's type is `str`. Then, it checks if the
156 value matches the regular expression.
157
158 Args:
159 data: A Python data structure to be validated.
160
161 Raises:
162 SchemaException if validation fails.
163 """
164 super(RegexpStr, self).Validate(data)
165 if not self.regexp.match(data):
166 raise SchemaException("Value %r doesn't match regeular expression %s" %
167 (data, self.regexp.pattern))
168
169
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800170class Dict(BaseType):
171 """Dict schema class.
172
173 This schema class is used to verify simple dict. Only the key type and value
174 type are validated.
175
176 Attributes:
177 label: A human-readable string to describe this Scalar.
Ricky Liangf5386b32013-03-11 16:40:45 +0800178 key_type: A schema object indicating the schema of the keys of this Dict. It
179 can be a Scalar or an AnyOf with possible values being all Scalars.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800180 value_type: A schema object indicating the schema of the values of this
181 Dict.
Yong Hong3532ae82017-12-29 16:05:46 +0800182 min_size: The minimum size of the elements, default to 0.
183 max_size: None or the maximum size of the elements.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800184
185 Raises:
186 SchemaException if argument format is incorrect.
187 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800188
Yong Hong3532ae82017-12-29 16:05:46 +0800189 def __init__(self, label, key_type, value_type, min_size=0, max_size=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800190 super(Dict, self).__init__(label)
Ricky Liangf5386b32013-03-11 16:40:45 +0800191 if not (isinstance(key_type, Scalar) or
Hung-Te Lin56b18402015-01-16 14:52:30 +0800192 (isinstance(key_type, AnyOf) and
193 key_type.CheckTypeOfPossibleValues(Scalar))):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800194 raise SchemaException('key_type %r of Dict %r is not Scalar' %
Yong Hong3532ae82017-12-29 16:05:46 +0800195 (key_type, self.label))
196 self.key_type = key_type
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800197 if not isinstance(value_type, BaseType):
198 raise SchemaException('value_type %r of Dict %r is not Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800199 (value_type, self.label))
200 self.value_type = value_type
201 self.min_size = min_size
202 self.max_size = max_size
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800203
Dean Liao604e62b2013-03-11 19:12:50 +0800204 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800205 size_expr = '[%d, %s]' % (self.min_size, ('inf' if self.max_size is None
206 else '%d' % self.max_size))
207 return 'Dict(%r, key_type=%r, value_type=%r, size=%s)' % (
208 self.label, self.key_type, self.value_type, size_expr)
Dean Liao604e62b2013-03-11 19:12:50 +0800209
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800210 def Validate(self, data):
211 """Validates the given data against the Dict schema.
212
213 It checks that all the keys in data matches the schema defined by key_type,
214 and all the values in data matches the schema defined by value_type.
215
216 Args:
217 data: A Python data structure to be validated.
218
219 Raises:
220 SchemaException if validation fails.
221 """
222 if not isinstance(data, dict):
223 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800224 (self.label, type(data)))
225
226 if len(data) < self.min_size:
227 raise SchemaException('Size mismatch on %r: expected size >= %r' %
228 (self.label, self.min_size))
229
230 if self.max_size is not None and self.max_size < len(data):
231 raise SchemaException('Size mismatch on %r: expected size <= %r' %
232 (self.label, self.max_size))
233
Yilin Yang879fbda2020-05-14 13:52:30 +0800234 for k, v in data.items():
Yong Hong3532ae82017-12-29 16:05:46 +0800235 self.key_type.Validate(k)
236 self.value_type.Validate(v)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800237
238
239class FixedDict(BaseType):
240 """FixedDict schema class.
241
242 FixedDict is a Dict with predefined allowed keys. And each key corresponds to
243 a value type. The analogy of Dict vs. FixedDict can be Elements vs. Attribues
244 in XML.
245
246 An example FixedDict schema:
247 FixedDict('foo',
248 items={
249 'a': Scalar('bar', str),
250 'b': Scalar('buz', int)
251 }, optional_items={
252 'c': Scalar('boo', int)
253 })
254
255 Attributes:
256 label: A human-readable string to describe this dict.
257 items: A dict of required items that must be specified.
258 optional_items: A dict of optional items.
Yong Hong5961ad32019-05-14 17:43:18 +0800259 allow_undefined_keys: A boolean that indicates whether additional items
260 that is not recorded in both `items` and `optional_items` are allowed
261 or not.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800262
263 Raises:
264 SchemaException if argument format is incorrect.
265 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800266
Yong Hong5961ad32019-05-14 17:43:18 +0800267 def __init__(self, label, items=None, optional_items=None,
268 allow_undefined_keys=False):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800269 super(FixedDict, self).__init__(label)
270 if items and not isinstance(items, dict):
271 raise SchemaException('items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800272 self.label)
273 self.items = copy.deepcopy(items) if items is not None else {}
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800274 if optional_items and not isinstance(optional_items, dict):
275 raise SchemaException('optional_items of FixedDict %r should be a dict' %
Yong Hong3532ae82017-12-29 16:05:46 +0800276 self.label)
277 self.optional_items = (
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800278 copy.deepcopy(optional_items) if optional_items is not None else {})
Yong Hong5961ad32019-05-14 17:43:18 +0800279 self.allow_undefined_keys = allow_undefined_keys
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800280
Dean Liao604e62b2013-03-11 19:12:50 +0800281 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800282 return 'FixedDict(%r, items=%r, optional_items=%r)' % (self.label,
283 self.items,
284 self.optional_items)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800285
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800286 def Validate(self, data):
287 """Validates the given data and all its key-value pairs against the Dict
288 schema.
289
290 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 +0800291 If `self.allow_undefined_keys` is `False` and some items in the given data
292 are not in either `self.items` or `self.optional_items`, the method will
293 raise `SchemaException`.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800294
295 Args:
296 data: A Python data structure to be validated.
297
298 Raises:
299 SchemaException if validation fails.
300 """
301 if not isinstance(data, dict):
302 raise SchemaException('Type mismatch on %r: expected dict, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800303 (self.label, type(data)))
Yilin Yang78fa12e2019-09-25 14:21:10 +0800304 data_key_list = list(data)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800305 # Check that every key-value pair in items exists in data
Yilin Yang879fbda2020-05-14 13:52:30 +0800306 for key, value_schema in self.items.items():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800307 if key not in data:
308 raise SchemaException(
309 'Required item %r does not exist in FixedDict %r' %
310 (key, data))
311 value_schema.Validate(data[key])
312 data_key_list.remove(key)
313 # Check that all the remaining unmatched key-value pairs matches any
314 # definition in items or optional_items.
Yilin Yang879fbda2020-05-14 13:52:30 +0800315 for key, value_schema in self.optional_items.items():
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800316 if key not in data:
317 continue
318 value_schema.Validate(data[key])
319 data_key_list.remove(key)
Yong Hong5961ad32019-05-14 17:43:18 +0800320 if not self.allow_undefined_keys and data_key_list:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800321 raise SchemaException('Keys %r are undefined in FixedDict %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800322 (data_key_list, self.label))
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800323
324
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800325class JSONSchemaDict(BaseType):
326 """JSON schema class.
327
328 This schema class allows mixing JSON schema with other schema types.
329
330 Attributes:
331 label: A human-readable string to describe this JSON schema.
332 schema: a JSON schema object.
333
334 Raises:
Fei Shao186d25b2018-11-09 16:55:48 +0800335 SchemaException if given schema is invalid (SchemaError) or fail
336 to validate data using the schema (ValidationError).
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800337 """
338 def __init__(self, label, schema):
339 super(JSONSchemaDict, self).__init__(label)
Yong Hong3532ae82017-12-29 16:05:46 +0800340 self.label = label
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800341 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800342 try:
343 jsonschema.Draft4Validator.check_schema(schema)
344 except Exception as e:
345 raise SchemaException('Schema %r is invalid: %r' % (schema, e))
Yong Hong3532ae82017-12-29 16:05:46 +0800346 self.schema = schema
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800347
348 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800349 return 'JSONSchemaDict(%r, %r)' % (self.label, self.schema)
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800350
351 def Validate(self, data):
Chih-Wei Ning8da12ec2017-08-29 12:10:02 +0800352 if _HAVE_JSONSCHEMA:
Fei Shao186d25b2018-11-09 16:55:48 +0800353 try:
354 jsonschema.validate(data, self.schema)
355 except Exception as e:
356 raise SchemaException('Fail to validate %r with JSON schema %r: %r' %
357 (data, self.schema, e))
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800358
359
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800360class List(BaseType):
361 """List schema class.
362
363 Attributes:
364 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800365 element_type: Optional schema object to validate the elements of the list.
366 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800367 min_length: The expected minimum length of the list. Default to 0.
368 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800369
370 Raises:
371 SchemaException if argument format is incorrect.
372 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800373
Yong Hong3532ae82017-12-29 16:05:46 +0800374 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800375 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800376 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800377 raise SchemaException(
378 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800379 (element_type, self.label))
380 self.element_type = copy.deepcopy(element_type)
381 self.min_length = min_length
382 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800383
Dean Liao604e62b2013-03-11 19:12:50 +0800384 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800385 max_bound_repr = ('inf' if self.max_length is None
386 else '%d' % self.max_length)
387 return 'List(%r, %r, [%r, %s])' % (
388 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800389
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800390 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800391 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800392
393 Args:
394 data: A Python data structure to be validated.
395
396 Raises:
397 SchemaException if validation fails.
398 """
399 if not isinstance(data, list):
400 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800401 (self.label, type(data)))
402
403 if len(data) < self.min_length:
404 raise SchemaException('Length mismatch on %r: expected length >= %d' %
405 (self.label, self.min_length))
406
407 if self.max_length is not None and self.max_length < len(data):
408 raise SchemaException('Length mismatch on %r: expected length <= %d' %
409 (self.label, self.max_length))
410
411 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800412 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800413 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800414
415
Ricky Liang3e5342b2013-03-08 12:16:25 +0800416class Tuple(BaseType):
417 """Tuple schema class.
418
419 Comparing to List, the Tuple schema makes sure that every element exactly
420 matches the defined position and schema.
421
422 Attributes:
423 label: A string to describe this tuple.
424 element_types: Optional list or tuple schema object to describe the
425 types of the Tuple.
426
427 Raises:
428 SchemaException if argument format is incorrect.
429 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800430
Ricky Liang3e5342b2013-03-08 12:16:25 +0800431 def __init__(self, label, element_types=None):
432 super(Tuple, self).__init__(label)
433 if (element_types and
434 (not isinstance(element_types, (tuple, list))) or
435 (not all([isinstance(x, BaseType)] for x in element_types))):
436 raise SchemaException(
437 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800438 (element_types, self.label))
439 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800440
Dean Liao604e62b2013-03-11 19:12:50 +0800441 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800442 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800443
Ricky Liang3e5342b2013-03-08 12:16:25 +0800444 def Validate(self, data):
445 """Validates the given data and all its elements against the Tuple schema.
446
447 Args:
448 data: A Python data structure to be validated.
449
450 Raises:
451 SchemaException if validation fails.
452 """
453 if not isinstance(data, tuple):
454 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800455 (self.label, type(data)))
456 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800457 raise SchemaException(
458 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800459 'in Tuple schema %r' % (str(data), self.label))
Yilin Yangd3d97b02020-01-14 16:46:33 +0800460 for content, element_type in zip(data, self.element_types):
461 element_type.Validate(content)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800462
463
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800464class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800465 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800466
467 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800468 types: A list of Schema objects to be matched.
469 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800470 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800471
Dean Liao604e62b2013-03-11 19:12:50 +0800472 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800473 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800474 if (not isinstance(types, list) or
475 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800476 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800477 'types in AnyOf(types=%r%s) should be a list of Schemas' %
478 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800479 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800480
481 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800482 label = '' if self.label is None else ', label=%r' % self.label
483 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800484
Ricky Liangf5386b32013-03-11 16:40:45 +0800485 def CheckTypeOfPossibleValues(self, schema_type):
486 """Checks if the acceptable types are of the same type as schema_type.
487
488 Args:
489 schema_type: The schema type to check against with.
490 """
Yong Hong3532ae82017-12-29 16:05:46 +0800491 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800492
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800493 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800494 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800495
496 Args:
497 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800498
499 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800500 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800501 """
502 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800503 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800504 try:
505 schema_type.Validate(data)
506 except SchemaException:
507 continue
508 match = True
509 break
510 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800511 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800512 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800513
514
515class Optional(AnyOf):
516 """A Schema class which accepts either None or given Schemas.
517
518 It is a special case of AnyOf class: in addition of given schema(s), it also
519 accepts None.
520
521 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800522 types: A (or a list of) Schema object(s) to be matched.
523 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800524 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800525
Dean Liao604e62b2013-03-11 19:12:50 +0800526 def __init__(self, types, label=None):
527 try:
528 super(Optional, self).__init__(MakeList(types), label=label)
529 except SchemaException:
530 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800531 'types in Optional(types=%r%s) should be a Schema or a list of '
532 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800533
534 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800535 label = '' if self.label is None else ', label=%r' % self.label
536 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800537
538 def Validate(self, data):
539 """Validates if the given data is None or matches any schema in types.
540
541 Args:
542 data: A Python data structue to be validated.
543
544 Raises:
545 SchemaException if data is not None and no schemas in types validates the
546 input data.
547 """
548 if data is None:
549 return
550 try:
551 super(Optional, self).Validate(data)
552 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800553 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800554 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800555 self.types))