processor.py 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. # -*- coding: utf-8 -*-
  2. import typing
  3. from hapic.exception import OutputValidationException
  4. from hapic.exception import ConfigurationException
  5. class RequestParameters(object):
  6. def __init__(
  7. self,
  8. path_parameters,
  9. query_parameters,
  10. body_parameters,
  11. form_parameters,
  12. header_parameters,
  13. files_parameters,
  14. ):
  15. self.path_parameters = path_parameters
  16. self.query_parameters = query_parameters
  17. self.body_parameters = body_parameters
  18. self.form_parameters = form_parameters
  19. self.header_parameters = header_parameters
  20. self.files_parameters = files_parameters
  21. class ProcessValidationError(object):
  22. def __init__(
  23. self,
  24. message: str,
  25. details: dict,
  26. ) -> None:
  27. self.message = message
  28. self.details = details
  29. class ProcessorInterface(object):
  30. def __init__(self):
  31. self._schema = None
  32. @property
  33. def schema(self):
  34. if not self._schema:
  35. raise ConfigurationException('Schema not set for processor {}'.format(str(self)))
  36. return self._schema
  37. @schema.setter
  38. def schema(self, schema):
  39. self._schema = schema
  40. def process(self, value):
  41. raise NotImplementedError
  42. def get_validation_error(
  43. self,
  44. request_context: RequestParameters,
  45. ) -> ProcessValidationError:
  46. raise NotImplementedError
  47. class Processor(ProcessorInterface):
  48. @classmethod
  49. def clean_data(cls, data: typing.Any) -> dict:
  50. # Fixes #22: Schemas make not validation if None is given
  51. if data is None:
  52. return {}
  53. return data
  54. class InputProcessor(Processor):
  55. pass
  56. class OutputProcessor(Processor):
  57. pass
  58. class MarshmallowOutputProcessor(OutputProcessor):
  59. def process(self, data: typing.Any):
  60. clean_data = self.clean_data(data)
  61. dump_data = self.schema.dump(clean_data).data
  62. self.validate(dump_data)
  63. return dump_data
  64. def validate(self, data: typing.Any) -> None:
  65. clean_data = self.clean_data(data)
  66. errors = self.schema.load(clean_data).errors
  67. if errors:
  68. raise OutputValidationException(
  69. 'Error when validate input: {}'.format(
  70. str(errors),
  71. )
  72. )
  73. def get_validation_error(self, data: dict) -> ProcessValidationError:
  74. clean_data = self.clean_data(data)
  75. dump_data = self.schema.dump(clean_data).data
  76. errors = self.schema.load(dump_data).errors
  77. return ProcessValidationError(
  78. message='Validation error of output data',
  79. details=errors,
  80. )
  81. class MarshmallowInputProcessor(InputProcessor):
  82. def process(self, data: dict):
  83. clean_data = self.clean_data(data)
  84. unmarshall = self.schema.load(clean_data)
  85. if unmarshall.errors:
  86. raise OutputValidationException(
  87. 'Error when validate ouput: {}'.format(
  88. str(unmarshall.errors),
  89. )
  90. )
  91. return unmarshall.data
  92. def get_validation_error(self, data: dict) -> ProcessValidationError:
  93. clean_data = self.clean_data(data)
  94. marshmallow_errors = self.schema.load(clean_data).errors
  95. return ProcessValidationError(
  96. message='Validation error of input data',
  97. details=marshmallow_errors,
  98. )
  99. class MarshmallowInputFilesProcessor(MarshmallowInputProcessor):
  100. def process(self, data: dict):
  101. clean_data = self.clean_data(data)
  102. unmarshall = self.schema.load(clean_data)
  103. additional_errors = self._get_files_errors(unmarshall.data)
  104. if unmarshall.errors:
  105. raise OutputValidationException(
  106. 'Error when validate ouput: {}'.format(
  107. str(unmarshall.errors),
  108. )
  109. )
  110. if additional_errors:
  111. raise OutputValidationException(
  112. 'Error when validate ouput: {}'.format(
  113. str(additional_errors),
  114. )
  115. )
  116. return unmarshall.data
  117. def get_validation_error(self, data: dict) -> ProcessValidationError:
  118. clean_data = self.clean_data(data)
  119. unmarshall = self.schema.load(clean_data)
  120. marshmallow_errors = unmarshall.errors
  121. additional_errors = self._get_files_errors(unmarshall.data)
  122. if marshmallow_errors:
  123. return ProcessValidationError(
  124. message='Validation error of input data',
  125. details=marshmallow_errors,
  126. )
  127. if additional_errors:
  128. return ProcessValidationError(
  129. message='Validation error of input data',
  130. details=additional_errors,
  131. )
  132. def _get_files_errors(self, validated_data: dict) -> typing.Dict[str, str]:
  133. """
  134. Additional check of data
  135. :param validated_data: previously validated data by marshmallow schema
  136. :return: list of error if any
  137. """
  138. errors = {}
  139. for field_name, field in self.schema.fields.items():
  140. # Actually just check if value not empty
  141. # TODO BS 20171102: Think about case where test file content is more complicated
  142. if field.required and (field_name not in validated_data or not validated_data[field_name]):
  143. errors.setdefault(field_name, []).append('Missing data for required field')
  144. return errors