blob: 61a91c63c66eb971a6470781bf3a69d578d5f6c5 [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
Fei Shaobd07c9a2020-06-15 19:04:50 +080068class BaseType:
Ricky Liang1b72ccf2013-03-01 10:23:01 +080069 """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
Yilin Yanged5d7e22020-07-23 11:14:21 +0800103 self.choices = set(choices) if choices else set()
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
Cheng Yuehf5bf58f2020-12-30 15:49:47 +0800359 def CreateOptional(self):
360 """Creates a new schema that accepts null and itself."""
361 return JSONSchemaDict(f'{self.label} or null',
362 {'anyOf': [
363 {
364 'type': 'null'
365 },
366 self.schema,
367 ]})
368
Chih-wei Ning5fbaf442017-08-15 21:34:53 +0800369
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800370class List(BaseType):
371 """List schema class.
372
373 Attributes:
374 label: A string to describe this list.
Dean Liao604e62b2013-03-11 19:12:50 +0800375 element_type: Optional schema object to validate the elements of the list.
376 Default None means no validation of elements' type.
Yong Hong3532ae82017-12-29 16:05:46 +0800377 min_length: The expected minimum length of the list. Default to 0.
378 max_length: None or the limit of the length.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800379
380 Raises:
381 SchemaException if argument format is incorrect.
382 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800383
Yong Hong3532ae82017-12-29 16:05:46 +0800384 def __init__(self, label, element_type=None, min_length=0, max_length=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800385 super(List, self).__init__(label)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800386 if element_type and not isinstance(element_type, BaseType):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800387 raise SchemaException(
388 'element_type %r of List %r is not a Schema object' %
Yong Hong3532ae82017-12-29 16:05:46 +0800389 (element_type, self.label))
390 self.element_type = copy.deepcopy(element_type)
391 self.min_length = min_length
392 self.max_length = max_length
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800393
Dean Liao604e62b2013-03-11 19:12:50 +0800394 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800395 max_bound_repr = ('inf' if self.max_length is None
396 else '%d' % self.max_length)
397 return 'List(%r, %r, [%r, %s])' % (
398 self.label, self.element_type, self.min_length, max_bound_repr)
Dean Liao604e62b2013-03-11 19:12:50 +0800399
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800400 def Validate(self, data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800401 """Validates the given data and all its elements against the List schema.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800402
403 Args:
404 data: A Python data structure to be validated.
405
406 Raises:
407 SchemaException if validation fails.
408 """
409 if not isinstance(data, list):
410 raise SchemaException('Type mismatch on %r: expected list, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800411 (self.label, type(data)))
412
413 if len(data) < self.min_length:
414 raise SchemaException('Length mismatch on %r: expected length >= %d' %
415 (self.label, self.min_length))
416
417 if self.max_length is not None and self.max_length < len(data):
418 raise SchemaException('Length mismatch on %r: expected length <= %d' %
419 (self.label, self.max_length))
420
421 if self.element_type:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800422 for data_value in data:
Yong Hong3532ae82017-12-29 16:05:46 +0800423 self.element_type.Validate(data_value)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800424
425
Ricky Liang3e5342b2013-03-08 12:16:25 +0800426class Tuple(BaseType):
427 """Tuple schema class.
428
429 Comparing to List, the Tuple schema makes sure that every element exactly
430 matches the defined position and schema.
431
432 Attributes:
433 label: A string to describe this tuple.
434 element_types: Optional list or tuple schema object to describe the
435 types of the Tuple.
436
437 Raises:
438 SchemaException if argument format is incorrect.
439 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800440
Ricky Liang3e5342b2013-03-08 12:16:25 +0800441 def __init__(self, label, element_types=None):
442 super(Tuple, self).__init__(label)
443 if (element_types and
444 (not isinstance(element_types, (tuple, list))) or
445 (not all([isinstance(x, BaseType)] for x in element_types))):
446 raise SchemaException(
447 'element_types %r of Tuple %r is not a tuple or list' %
Yong Hong3532ae82017-12-29 16:05:46 +0800448 (element_types, self.label))
449 self.element_types = copy.deepcopy(element_types)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800450
Dean Liao604e62b2013-03-11 19:12:50 +0800451 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800452 return 'Tuple(%r, %r)' % (self.label, self.element_types)
Dean Liao604e62b2013-03-11 19:12:50 +0800453
Ricky Liang3e5342b2013-03-08 12:16:25 +0800454 def Validate(self, data):
455 """Validates the given data and all its elements against the Tuple schema.
456
457 Args:
458 data: A Python data structure to be validated.
459
460 Raises:
461 SchemaException if validation fails.
462 """
463 if not isinstance(data, tuple):
464 raise SchemaException('Type mismatch on %r: expected tuple, got %r' %
Yong Hong3532ae82017-12-29 16:05:46 +0800465 (self.label, type(data)))
466 if self.element_types and len(self.element_types) != len(data):
Ricky Liang3e5342b2013-03-08 12:16:25 +0800467 raise SchemaException(
468 'Number of elements in tuple %r does not match that defined '
Yong Hong3532ae82017-12-29 16:05:46 +0800469 'in Tuple schema %r' % (str(data), self.label))
Yilin Yangd3d97b02020-01-14 16:46:33 +0800470 for content, element_type in zip(data, self.element_types):
471 element_type.Validate(content)
Ricky Liang3e5342b2013-03-08 12:16:25 +0800472
473
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800474class AnyOf(BaseType):
Dean Liao452c6ee2013-03-11 16:23:33 +0800475 """A Schema class which accepts any one of the given Schemas.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800476
477 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800478 types: A list of Schema objects to be matched.
479 label: An optional string to describe this AnyOf type.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800480 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800481
Dean Liao604e62b2013-03-11 19:12:50 +0800482 def __init__(self, types, label=None):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800483 super(AnyOf, self).__init__(label)
Dean Liao604e62b2013-03-11 19:12:50 +0800484 if (not isinstance(types, list) or
485 not all([isinstance(x, BaseType) for x in types])):
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800486 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800487 'types in AnyOf(types=%r%s) should be a list of Schemas' %
488 (types, '' if label is None else ', label=%r' % label))
Yong Hong3532ae82017-12-29 16:05:46 +0800489 self.types = list(types)
Dean Liao604e62b2013-03-11 19:12:50 +0800490
491 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800492 label = '' if self.label is None else ', label=%r' % self.label
493 return 'AnyOf(%r%s)' % (self.types, label)
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800494
Ricky Liangf5386b32013-03-11 16:40:45 +0800495 def CheckTypeOfPossibleValues(self, schema_type):
496 """Checks if the acceptable types are of the same type as schema_type.
497
498 Args:
499 schema_type: The schema type to check against with.
500 """
Yong Hong3532ae82017-12-29 16:05:46 +0800501 return all([isinstance(k, schema_type) for k in self.types])
Ricky Liangf5386b32013-03-11 16:40:45 +0800502
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800503 def Validate(self, data):
Dean Liao604e62b2013-03-11 19:12:50 +0800504 """Validates if the given data matches any schema in types
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800505
506 Args:
507 data: A Python data structue to be validated.
Dean Liao452c6ee2013-03-11 16:23:33 +0800508
509 Raises:
Dean Liao604e62b2013-03-11 19:12:50 +0800510 SchemaException if no schemas in types validates the input data.
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800511 """
512 match = False
Yong Hong3532ae82017-12-29 16:05:46 +0800513 for schema_type in self.types:
Ricky Liang1b72ccf2013-03-01 10:23:01 +0800514 try:
515 schema_type.Validate(data)
516 except SchemaException:
517 continue
518 match = True
519 break
520 if not match:
Dean Liao604e62b2013-03-11 19:12:50 +0800521 raise SchemaException('%r does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800522 self.types))
Dean Liao452c6ee2013-03-11 16:23:33 +0800523
524
525class Optional(AnyOf):
526 """A Schema class which accepts either None or given Schemas.
527
528 It is a special case of AnyOf class: in addition of given schema(s), it also
529 accepts None.
530
531 Attributes:
Dean Liao604e62b2013-03-11 19:12:50 +0800532 types: A (or a list of) Schema object(s) to be matched.
533 label: An optional string to describe this Optional type.
Dean Liao452c6ee2013-03-11 16:23:33 +0800534 """
Hung-Te Lin56b18402015-01-16 14:52:30 +0800535
Dean Liao604e62b2013-03-11 19:12:50 +0800536 def __init__(self, types, label=None):
537 try:
538 super(Optional, self).__init__(MakeList(types), label=label)
539 except SchemaException:
540 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800541 'types in Optional(types=%r%s) should be a Schema or a list of '
542 'Schemas' % (types, '' if label is None else ', label=%r' % label))
Dean Liao604e62b2013-03-11 19:12:50 +0800543
544 def __repr__(self):
Yong Hong3532ae82017-12-29 16:05:46 +0800545 label = '' if self.label is None else ', label=%r' % self.label
546 return 'Optional(%r%s)' % (self.types, label)
Dean Liao452c6ee2013-03-11 16:23:33 +0800547
548 def Validate(self, data):
549 """Validates if the given data is None or matches any schema in types.
550
551 Args:
552 data: A Python data structue to be validated.
553
554 Raises:
555 SchemaException if data is not None and no schemas in types validates the
556 input data.
557 """
558 if data is None:
559 return
560 try:
561 super(Optional, self).Validate(data)
562 except SchemaException:
Dean Liao604e62b2013-03-11 19:12:50 +0800563 raise SchemaException(
Hung-Te Lin56b18402015-01-16 14:52:30 +0800564 '%r is not None and does not match any type in %r' % (data,
Yong Hong3532ae82017-12-29 16:05:46 +0800565 self.types))