Files

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()