|
@@ -1,6 +1,7 @@
|
1
|
1
|
# -*- coding: utf-8 -*-
|
2
|
2
|
import json
|
3
|
3
|
import typing
|
|
4
|
+from http import HTTPStatus
|
4
|
5
|
|
5
|
6
|
import functools
|
6
|
7
|
|
|
@@ -11,12 +12,28 @@ import bottle
|
11
|
12
|
|
12
|
13
|
|
13
|
14
|
# CHANGE
|
|
15
|
+from hapic.exception import InputValidationException, \
|
|
16
|
+ OutputValidationException, InputWorkflowException, ProcessException
|
|
17
|
+
|
14
|
18
|
flatten = lambda l: [item for sublist in l for item in sublist]
|
15
|
19
|
|
16
|
20
|
|
17
|
21
|
_waiting = {}
|
18
|
22
|
_endpoints = {}
|
19
|
23
|
_default_global_context = None
|
|
24
|
+_default_global_error_schema = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+def error_schema(schema):
|
|
28
|
+ global _default_global_error_schema
|
|
29
|
+ _default_global_error_schema = schema
|
|
30
|
+
|
|
31
|
+ def decorator(func):
|
|
32
|
+ @functools.wraps(func)
|
|
33
|
+ def wrapper(*args, **kwargs):
|
|
34
|
+ return func(*args, **kwargs)
|
|
35
|
+ return wrapper
|
|
36
|
+ return decorator
|
20
|
37
|
|
21
|
38
|
|
22
|
39
|
def set_fake_default_context(context):
|
|
@@ -91,6 +108,16 @@ class RequestParameters(object):
|
91
|
108
|
self.header_parameters = header_parameters
|
92
|
109
|
|
93
|
110
|
|
|
111
|
+class ProcessValidationError(object):
|
|
112
|
+ def __init__(
|
|
113
|
+ self,
|
|
114
|
+ error_message: str,
|
|
115
|
+ error_details: dict,
|
|
116
|
+ ) -> None:
|
|
117
|
+ self.error_message = error_message
|
|
118
|
+ self.error_details = error_details
|
|
119
|
+
|
|
120
|
+
|
94
|
121
|
class ContextInterface(object):
|
95
|
122
|
def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
|
96
|
123
|
raise NotImplementedError()
|
|
@@ -99,7 +126,14 @@ class ContextInterface(object):
|
99
|
126
|
self,
|
100
|
127
|
response: dict,
|
101
|
128
|
http_code: int,
|
102
|
|
- ):
|
|
129
|
+ ) -> typing.Any:
|
|
130
|
+ raise NotImplementedError()
|
|
131
|
+
|
|
132
|
+ def get_validation_error_response(
|
|
133
|
+ self,
|
|
134
|
+ error: ProcessValidationError,
|
|
135
|
+ http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
|
|
136
|
+ ) -> typing.Any:
|
103
|
137
|
raise NotImplementedError()
|
104
|
138
|
|
105
|
139
|
|
|
@@ -126,6 +160,27 @@ class BottleContext(ContextInterface):
|
126
|
160
|
status=http_code,
|
127
|
161
|
)
|
128
|
162
|
|
|
163
|
+ def get_validation_error_response(
|
|
164
|
+ self,
|
|
165
|
+ error: ProcessValidationError,
|
|
166
|
+ http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
|
|
167
|
+ ) -> typing.Any:
|
|
168
|
+ unmarshall = _default_global_error_schema.dump(error)
|
|
169
|
+ if unmarshall.errors:
|
|
170
|
+ raise OutputValidationException(
|
|
171
|
+ 'Validation error during dump of error response: {}'.format(
|
|
172
|
+ str(unmarshall.errors)
|
|
173
|
+ )
|
|
174
|
+ )
|
|
175
|
+
|
|
176
|
+ return bottle.HTTPResponse(
|
|
177
|
+ body=json.dumps(unmarshall.data),
|
|
178
|
+ headers=[
|
|
179
|
+ ('Content-Type', 'application/json'),
|
|
180
|
+ ],
|
|
181
|
+ status=int(http_code),
|
|
182
|
+ )
|
|
183
|
+
|
129
|
184
|
|
130
|
185
|
class OutputProcessorInterface(object):
|
131
|
186
|
def __init__(self):
|
|
@@ -134,48 +189,68 @@ class OutputProcessorInterface(object):
|
134
|
189
|
def process(self, value):
|
135
|
190
|
raise NotImplementedError
|
136
|
191
|
|
|
192
|
+ def get_validation_error(
|
|
193
|
+ self,
|
|
194
|
+ request_context: RequestParameters,
|
|
195
|
+ ) -> ProcessValidationError:
|
|
196
|
+ raise NotImplementedError
|
|
197
|
+
|
137
|
198
|
|
138
|
199
|
class InputProcessorInterface(object):
|
139
|
200
|
def __init__(self):
|
140
|
201
|
self.schema = None
|
141
|
202
|
|
142
|
|
- def process(self, request_context: RequestParameters):
|
|
203
|
+ def process(
|
|
204
|
+ self,
|
|
205
|
+ request_context: RequestParameters,
|
|
206
|
+ ) -> typing.Any:
|
|
207
|
+ raise NotImplementedError
|
|
208
|
+
|
|
209
|
+ def get_validation_error(
|
|
210
|
+ self,
|
|
211
|
+ request_context: RequestParameters,
|
|
212
|
+ ) -> ProcessValidationError:
|
143
|
213
|
raise NotImplementedError
|
144
|
214
|
|
145
|
215
|
|
146
|
216
|
class MarshmallowOutputProcessor(OutputProcessorInterface):
|
147
|
217
|
def process(self, data: typing.Any):
|
148
|
|
- return self.schema.dump(data).data
|
|
218
|
+ unmarshall = self.schema.dump(data)
|
|
219
|
+ if unmarshall.errors:
|
|
220
|
+ raise InputValidationException(
|
|
221
|
+ 'Error when validate input: {}'.format(
|
|
222
|
+ str(unmarshall.errors),
|
|
223
|
+ )
|
|
224
|
+ )
|
|
225
|
+
|
|
226
|
+ return unmarshall.data
|
|
227
|
+
|
|
228
|
+ def get_validation_error(self, data: dict) -> ProcessValidationError:
|
|
229
|
+ marshmallow_errors = self.schema.dump(data).errors
|
|
230
|
+ return ProcessValidationError(
|
|
231
|
+ error_message='Validation error of output data',
|
|
232
|
+ error_details=marshmallow_errors,
|
|
233
|
+ )
|
149
|
234
|
|
150
|
235
|
|
151
|
236
|
class MarshmallowInputProcessor(OutputProcessorInterface):
|
152
|
237
|
def process(self, data: dict):
|
153
|
|
- return self.schema.load(data).data
|
154
|
|
-
|
155
|
|
-
|
156
|
|
-# class MarshmallowPathInputProcessor(OutputProcessorInterface):
|
157
|
|
-# def process(self, request_context: RequestParameters):
|
158
|
|
-# return self.schema.load(request_context.path_parameters).data
|
159
|
|
-#
|
160
|
|
-#
|
161
|
|
-# class MarshmallowQueryInputProcessor(OutputProcessorInterface):
|
162
|
|
-# def process(self, request_context: RequestParameters):
|
163
|
|
-# return self.schema.load(request_context.query_parameters).data
|
164
|
|
-#
|
165
|
|
-#
|
166
|
|
-# class MarshmallowJsonInputProcessor(OutputProcessorInterface):
|
167
|
|
-# def process(self, request_context: RequestParameters):
|
168
|
|
-# return self.schema.load(request_context.json_parameters).data
|
169
|
|
-
|
170
|
|
-
|
171
|
|
-# class MarshmallowFormInputProcessor(OutputProcessorInterface):
|
172
|
|
-# def process(self, request_context: RequestParameters):
|
173
|
|
-# return self.schema.load(xxx).data
|
174
|
|
-#
|
175
|
|
-#
|
176
|
|
-# class MarshmallowHeaderInputProcessor(OutputProcessorInterface):
|
177
|
|
-# def process(self, request_context: RequestParameters):
|
178
|
|
-# return self.schema.load(xxx).data
|
|
238
|
+ unmarshall = self.schema.load(data)
|
|
239
|
+ if unmarshall.errors:
|
|
240
|
+ raise OutputValidationException(
|
|
241
|
+ 'Error when validate ouput: {}'.format(
|
|
242
|
+ str(unmarshall.errors),
|
|
243
|
+ )
|
|
244
|
+ )
|
|
245
|
+
|
|
246
|
+ return unmarshall.data
|
|
247
|
+
|
|
248
|
+ def get_validation_error(self, data: dict) -> ProcessValidationError:
|
|
249
|
+ marshmallow_errors = self.schema.load(data).errors
|
|
250
|
+ return ProcessValidationError(
|
|
251
|
+ error_message='Validation error of input data',
|
|
252
|
+ error_details=marshmallow_errors,
|
|
253
|
+ )
|
179
|
254
|
|
180
|
255
|
|
181
|
256
|
class HapicData(object):
|
|
@@ -214,13 +289,12 @@ def output(
|
214
|
289
|
return wrapper
|
215
|
290
|
return decorator
|
216
|
291
|
|
217
|
|
-
|
218
|
292
|
# TODO: raccourcis 'input' tout court ?
|
219
|
293
|
def input_body(
|
220
|
294
|
schema,
|
221
|
295
|
processor: InputProcessorInterface=None,
|
222
|
296
|
context: ContextInterface=None,
|
223
|
|
- error_http_code=400,
|
|
297
|
+ error_http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
|
224
|
298
|
):
|
225
|
299
|
processor = processor or MarshmallowInputProcessor()
|
226
|
300
|
processor.schema = schema
|
|
@@ -234,8 +308,24 @@ def input_body(
|
234
|
308
|
updated_kwargs.update(kwargs)
|
235
|
309
|
hapic_data = updated_kwargs['hapic_data']
|
236
|
310
|
|
237
|
|
- request_parameters = context.get_request_parameters(*args, **updated_kwargs)
|
238
|
|
- hapic_data.body = processor.process(request_parameters.body_parameters)
|
|
311
|
+ request_parameters = context.get_request_parameters(
|
|
312
|
+ *args,
|
|
313
|
+ **updated_kwargs
|
|
314
|
+ )
|
|
315
|
+
|
|
316
|
+ try:
|
|
317
|
+ hapic_data.body = processor.process(
|
|
318
|
+ request_parameters.body_parameters,
|
|
319
|
+ )
|
|
320
|
+ except ProcessException:
|
|
321
|
+ error = processor.get_validation_error(
|
|
322
|
+ request_parameters.body_parameters,
|
|
323
|
+ )
|
|
324
|
+ error_response = context.get_validation_error_response(
|
|
325
|
+ error,
|
|
326
|
+ http_code=error_http_code,
|
|
327
|
+ )
|
|
328
|
+ return error_response
|
239
|
329
|
|
240
|
330
|
return func(*args, **updated_kwargs)
|
241
|
331
|
|