153 lines
7.2 KiB
Python
153 lines
7.2 KiB
Python
class ParamSchemaValidator:
|
|
|
|
AVAILABLE_TYPES = ['string', 'number', 'integer', 'boolean', 'array', 'object', 'null']
|
|
REQUIRED_PARAMETER_ATTRIBUTES = ['type']
|
|
REQUIRED_PARAMETER_ATTRIBUTES_BY_TYPE = {
|
|
'array': ['items'],
|
|
'string': ['maxLength'],
|
|
'object': ['properties', 'required']
|
|
}
|
|
OPTIONAL_PARAMETER_ATTRIBUTES = ['pattern', 'description', 'enum']
|
|
|
|
def __init__(self, param_name, param_definition, is_object_property=False):
|
|
self.param_name = param_name
|
|
self.param_definition = param_definition
|
|
self.is_object_property = is_object_property
|
|
self.param_type = self.param_definition.get('type')
|
|
|
|
def _validate(self):
|
|
self._check_missing_required_attributes()
|
|
self._check_unsupported_attributes()
|
|
self._is_valid_param_type()
|
|
self._check_pattern_only_with_allowed_types()
|
|
if self.param_type == 'array':
|
|
self._perform_array_checks()
|
|
elif self.param_type == 'object':
|
|
self._perform_object_checks()
|
|
|
|
def _check_missing_required_attributes(self):
|
|
missing_required_attributes = []
|
|
for required_attribute in self.REQUIRED_PARAMETER_ATTRIBUTES:
|
|
if required_attribute not in self.param_definition:
|
|
missing_required_attributes.append(required_attribute)
|
|
|
|
if missing_required_attributes:
|
|
raise ValueError(
|
|
f"The attributes '{missing_required_attributes}' must be defined for the parameter '{self.param_name}'."
|
|
)
|
|
|
|
def _check_unsupported_attributes(self):
|
|
unsupported_attributes = [
|
|
attribute for attribute in list(self.param_definition.keys())
|
|
if attribute not in self._get_supported_param_attributes()
|
|
]
|
|
if unsupported_attributes:
|
|
raise ValueError(
|
|
f"The attributes '{unsupported_attributes}' defined for the param '{self.param_name}' are not supported."
|
|
)
|
|
|
|
def _get_supported_param_attributes(self):
|
|
return (
|
|
self.REQUIRED_PARAMETER_ATTRIBUTES
|
|
+ self.REQUIRED_PARAMETER_ATTRIBUTES_BY_TYPE.get(self.param_type, [])
|
|
+ self.OPTIONAL_PARAMETER_ATTRIBUTES
|
|
)
|
|
|
|
def _check_pattern_only_with_allowed_types(self):
|
|
pattern = self.param_definition.get('pattern')
|
|
if pattern and self.param_type not in ['string', 'array']:
|
|
raise ValueError(
|
|
f"The pattern attribute cannot be defined for the parameter '{self.param_name}'. "
|
|
"The pattern attribute can only be defined for string parameters or arrays whose items are only of the type string."
|
|
)
|
|
|
|
def _is_valid_param_type(self):
|
|
if self.param_type not in self.AVAILABLE_TYPES:
|
|
raise ValueError(
|
|
f"The type '{self.param_type}' of parameter '{self.param_name}' is wrong. The available types are '{self.AVAILABLE_TYPES}'."
|
|
)
|
|
|
|
def _perform_array_checks(self):
|
|
self._check_missing_required_attributes_by_type('array')
|
|
self._check_array_item_types()
|
|
self._check_pattern_defined_only_if_string_array_item()
|
|
|
|
def _check_missing_required_attributes_by_type(self, param_type):
|
|
missing_required_attributes = []
|
|
for required_attribute in self.REQUIRED_PARAMETER_ATTRIBUTES_BY_TYPE.get(param_type, []):
|
|
if required_attribute not in self.param_definition:
|
|
missing_required_attributes.append(required_attribute)
|
|
|
|
if missing_required_attributes:
|
|
raise ValueError(
|
|
f"The attributes '{missing_required_attributes}' must be defined for a parameter of type '{self.param_type}'. "
|
|
f"They are missing for the parameter '{self.param_name}'."
|
|
)
|
|
|
|
def _check_array_item_types(self):
|
|
# array_item_types can be a single type '{'type': 'string'}' or a list of types {'anyOf': [{'type': 'string'}, {'type': 'number'}]}
|
|
failure_message = (
|
|
f"The types of the items of the 'array' param '{self.param_name}' are not defined correctly. "
|
|
"The types of the items must be defined as ('items': {'type': 'string'}) if all items are of the same type "
|
|
"or ('items': { 'anyOf': [ {'type': 'string'}, {'type': 'number'} ] }) if items are of different types"
|
|
)
|
|
array_item_types = self.param_definition.get('items')
|
|
if (
|
|
not isinstance(array_item_types, dict)
|
|
or len(array_item_types) > 1
|
|
or array_item_types.get('anyOf') and not isinstance(array_item_types['anyOf'], list)
|
|
):
|
|
raise ValueError(
|
|
failure_message
|
|
)
|
|
available_types = list(
|
|
set(self.AVAILABLE_TYPES) - {'array', 'object'}
|
|
)
|
|
array_item_types = array_item_types.get('anyOf') if 'anyOf' in array_item_types else [
|
|
array_item_types]
|
|
for item_type in array_item_types:
|
|
if not isinstance(item_type, dict) or len(item_type) > 1 or not item_type.get('type'):
|
|
raise ValueError(
|
|
failure_message
|
|
)
|
|
if item_type['type'] not in available_types:
|
|
raise ValueError(
|
|
f"The type '{item_type}' of the items of the 'array' parameter '{self.param_name}' is wrong. The available types are '{available_types}'."
|
|
)
|
|
|
|
def _check_pattern_defined_only_if_string_array_item(self):
|
|
pattern = self.param_definition.get('pattern')
|
|
array_item_types = self.param_definition.get('items')
|
|
array_item_types = array_item_types.get('anyOf', [array_item_types])
|
|
if pattern and not (len(array_item_types) == 1 and array_item_types[0]['type'] == 'string'):
|
|
raise ValueError(
|
|
f"The pattern attribute cannot be defined for the parameter '{self.param_name}'. "
|
|
"The pattern attribute can only be defined for string parameters or arrays whose items are only of the type string."
|
|
)
|
|
|
|
def _perform_object_checks(self):
|
|
self._check_nested_object()
|
|
self._check_missing_required_attributes_by_type('object')
|
|
self._check_required_attribute_is_list()
|
|
self._validate_object_properties()
|
|
|
|
def _check_nested_object(self):
|
|
if self.is_object_property:
|
|
raise ValueError(
|
|
f"Nested objects are not supported. The parameter '{self.param_name}' is defined as an object inside another object."
|
|
)
|
|
|
|
def _check_required_attribute_is_list(self):
|
|
if not isinstance(self.param_definition['required'], list):
|
|
raise TypeError(
|
|
f"The attribute 'required' for the object param '{self.param_name}' must be a 'list' containing "
|
|
"the required properties of the object or empty if all of them are not required."
|
|
)
|
|
|
|
def _validate_object_properties(self):
|
|
object_properties = self.param_definition['properties']
|
|
for object_property_name, object_property_definition in object_properties.items():
|
|
param_validator = ParamSchemaValidator(
|
|
object_property_name, object_property_definition, is_object_property=True)
|
|
param_validator._validate()
|