Skip to content

Commit 8db5c08

Browse files
committed
Add support for additional validation properties
Add support for the following validation properties: - multipleOf - maximum - exclusiveMaximum - minimum - exclusiveMinimum - maxLength - minLength - pattern - maxItems - minItems - uniqueItems - maxProperties - minProperties Fixes #49
1 parent 24109d9 commit 8db5c08

File tree

3 files changed

+478
-1
lines changed

3 files changed

+478
-1
lines changed

openapi_core/schema/schemas/factories.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ def create(self, schema_spec):
2929
all_of_spec = schema_deref.get('allOf', None)
3030
one_of_spec = schema_deref.get('oneOf', None)
3131
additional_properties_spec = schema_deref.get('additionalProperties')
32+
min_items = schema_deref.get('minItems', None)
33+
max_items = schema_deref.get('maxItems', None)
34+
min_length = schema_deref.get('minLength', None)
35+
max_length = schema_deref.get('maxLength', None)
36+
pattern = schema_deref.get('pattern', None)
37+
unique_items = schema_deref.get('uniqueItems', None)
38+
minimum = schema_deref.get('minimum', None)
39+
maximum = schema_deref.get('maximum', None)
40+
multiple_of = schema_deref.get('multipleOf', None)
41+
exclusive_minimum = schema_deref.get('exclusiveMinimum', False)
42+
exclusive_maximum = schema_deref.get('exclusiveMaximum', False)
43+
min_properties = schema_deref.get('minProperties', None)
44+
max_properties = schema_deref.get('maxProperties', None)
3245

3346
properties = None
3447
if properties_spec:
@@ -56,6 +69,12 @@ def create(self, schema_spec):
5669
default=default, nullable=nullable, enum=enum,
5770
deprecated=deprecated, all_of=all_of, one_of=one_of,
5871
additional_properties=additional_properties,
72+
min_items=min_items, max_items=max_items, min_length=min_length,
73+
max_length=max_length, pattern=pattern, unique_items=unique_items,
74+
minimum=minimum, maximum=maximum, multiple_of=multiple_of,
75+
exclusive_maximum=exclusive_maximum,
76+
exclusive_minimum=exclusive_minimum,
77+
min_properties=min_properties, max_properties=max_properties,
5978
)
6079

6180
@property

openapi_core/schema/schemas/models.py

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
from collections import defaultdict
44
from datetime import date, datetime
5+
import re
56
import warnings
67

78
from six import iteritems, integer_types, binary_type, text_type
@@ -61,7 +62,11 @@ def __init__(
6162
self, schema_type=None, model=None, properties=None, items=None,
6263
schema_format=None, required=None, default=None, nullable=False,
6364
enum=None, deprecated=False, all_of=None, one_of=None,
64-
additional_properties=None):
65+
additional_properties=None, min_items=None, max_items=None,
66+
min_length=None, max_length=None, pattern=None, unique_items=False,
67+
minimum=None, maximum=None, multiple_of=None,
68+
exclusive_minimum=False, exclusive_maximum=False,
69+
min_properties=None, max_properties=None):
6570
self.type = SchemaType(schema_type)
6671
self.model = model
6772
self.properties = properties and dict(properties) or {}
@@ -75,6 +80,22 @@ def __init__(
7580
self.all_of = all_of and list(all_of) or []
7681
self.one_of = one_of and list(one_of) or []
7782
self.additional_properties = additional_properties
83+
self.min_items = int(min_items) if min_items is not None else None
84+
self.max_items = int(max_items) if max_items is not None else None
85+
self.min_length = int(min_length) if min_length is not None else None
86+
self.max_length = int(max_length) if max_length is not None else None
87+
self.pattern = pattern and re.compile(pattern) or None
88+
self.unique_items = unique_items
89+
self.minimum = int(minimum) if minimum is not None else None
90+
self.maximum = int(maximum) if maximum is not None else None
91+
self.multiple_of = int(multiple_of)\
92+
if multiple_of is not None else None
93+
self.exclusive_minimum = exclusive_minimum
94+
self.exclusive_maximum = exclusive_maximum
95+
self.min_properties = int(min_properties)\
96+
if min_properties is not None else None
97+
self.max_properties = int(max_properties)\
98+
if max_properties is not None else None
7899

79100
self._all_required_properties_cache = None
80101
self._all_optional_properties_cache = None
@@ -288,6 +309,8 @@ def get_validator_mapping(self):
288309
SchemaType.ARRAY: self._validate_collection,
289310
SchemaType.STRING: self._validate_string,
290311
SchemaType.OBJECT: self._validate_object,
312+
SchemaType.INTEGER: self._validate_number,
313+
SchemaType.NUMBER: self._validate_number,
291314
}
292315

293316
return defaultdict(lambda: lambda x: x, mapping)
@@ -318,8 +341,66 @@ def _validate_collection(self, value):
318341
if self.items is None:
319342
raise OpenAPISchemaError("Schema for collection not defined")
320343

344+
if self.min_items is not None:
345+
if self.min_items < 0:
346+
raise OpenAPISchemaError(
347+
"Schema for collection invalid:"
348+
" minItems must be non-negative"
349+
)
350+
if len(value) < self.min_items:
351+
raise InvalidSchemaValue(
352+
"Value must contain at least {0} item(s),"
353+
" {1} found".format(
354+
self.min_items, len(value))
355+
)
356+
if self.max_items is not None:
357+
if self.max_items < 0:
358+
raise OpenAPISchemaError(
359+
"Schema for collection invalid:"
360+
" maxItems must be non-negative"
361+
)
362+
if len(value) > self.max_items:
363+
raise InvalidSchemaValue(
364+
"Value must contain at most {0} item(s),"
365+
" {1} found".format(
366+
self.max_items, len(value))
367+
)
368+
if self.unique_items and len(set(value)) != len(value):
369+
raise InvalidSchemaValue("Value may not contain duplicate items")
370+
321371
return list(map(self.items.validate, value))
322372

373+
def _validate_number(self, value):
374+
if self.minimum is not None:
375+
if self.exclusive_minimum and value <= self.minimum:
376+
raise InvalidSchemaValue(
377+
"Value {0} is not less than or equal to {1}".format(
378+
value, self.minimum)
379+
)
380+
elif value < self.minimum:
381+
raise InvalidSchemaValue(
382+
"Value {0} is not less than {1}".format(
383+
value, self.minimum)
384+
)
385+
386+
if self.maximum is not None:
387+
if self.exclusive_maximum and value >= self.maximum:
388+
raise InvalidSchemaValue(
389+
"Value {0} is not greater than or equal to {1}".format(
390+
value, self.maximum)
391+
)
392+
elif value > self.maximum:
393+
raise InvalidSchemaValue(
394+
"Value {0} is not greater than {1}".format(
395+
value, self.maximum)
396+
)
397+
398+
if self.multiple_of is not None and value % self.multiple_of:
399+
raise InvalidSchemaValue(
400+
"Value {0} is not a multiple of {1}".format(
401+
value, self.multiple_of)
402+
)
403+
323404
def _validate_string(self, value):
324405
try:
325406
schema_format = SchemaFormat(self.format)
@@ -338,6 +419,34 @@ def _validate_string(self, value):
338419
value, self.format)
339420
)
340421

422+
if self.min_length is not None:
423+
if self.min_length < 0:
424+
raise OpenAPISchemaError(
425+
"Schema for string invalid:"
426+
" minLength must be non-negative"
427+
)
428+
if len(value) < self.min_length:
429+
raise InvalidSchemaValue(
430+
"Value is shorter than the minimum length of {0}".format(
431+
self.min_length)
432+
)
433+
if self.max_length is not None:
434+
if self.max_length < 0:
435+
raise OpenAPISchemaError(
436+
"Schema for string invalid:"
437+
" maxLength must be non-negative"
438+
)
439+
if len(value) > self.max_length:
440+
raise InvalidSchemaValue(
441+
"Value is longer than the maximum length of {0}".format(
442+
self.max_length)
443+
)
444+
if self.pattern is not None and not self.pattern.search(value):
445+
raise InvalidSchemaValue(
446+
"Value {0} does not match the pattern {1}".format(
447+
value, self.pattern.pattern)
448+
)
449+
341450
return True
342451

343452
def _validate_object(self, value):
@@ -364,6 +473,33 @@ def _validate_object(self, value):
364473
else:
365474
self._validate_properties(properties)
366475

476+
if self.min_properties is not None:
477+
if self.min_properties < 0:
478+
raise OpenAPISchemaError(
479+
"Schema for object invalid:"
480+
" minProperties must be non-negative"
481+
)
482+
483+
if len(properties) < self.min_properties:
484+
raise InvalidSchemaValue(
485+
"Value must contain at least {0} properties,"
486+
" {1} found".format(
487+
self.min_properties, len(properties))
488+
)
489+
490+
if self.max_properties is not None:
491+
if self.max_properties < 0:
492+
raise OpenAPISchemaError(
493+
"Schema for object invalid:"
494+
" maxProperties must be non-negative"
495+
)
496+
if len(properties) > self.max_properties:
497+
raise InvalidSchemaValue(
498+
"Value must contain at most {0} properties,"
499+
" {1} found".format(
500+
self.max_properties, len(properties))
501+
)
502+
367503
return True
368504

369505
def _validate_properties(self, value, one_of_schema=None):

0 commit comments

Comments
 (0)