Skip to content

Commit 3641796

Browse files
committed
carve out payloads and rework them to make use of the validation helper
1 parent e967330 commit 3641796

File tree

2 files changed

+96
-74
lines changed

2 files changed

+96
-74
lines changed

mig/lib/coresvc/payloads.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from collections import defaultdict, namedtuple
2+
3+
from mig.shared.safeinput import validate_helper
4+
5+
6+
def _is_not_none(value):
7+
"""value is not None"""
8+
assert value is not None, _is_not_none.__doc__
9+
10+
11+
def _is_string_and_non_empty(value):
12+
"""value is a non-empty string"""
13+
assert isinstance(value, str) and len(value) > 0, _is_string_and_non_empty.__doc__
14+
15+
16+
class _FieldOnError:
17+
def __init__(self, field):
18+
self._field = field
19+
20+
def replace(self, _, __):
21+
return self._field
22+
23+
24+
def _define_payload(payload_name, payload_fields, validators_by_field):
25+
class Payload(namedtuple('PostUserArgs', payload_fields)):
26+
VALIDATORS = validators_by_field
27+
28+
def keys(self):
29+
return Payload._fields
30+
31+
def items(self):
32+
return self._asdict().items()
33+
34+
@classmethod
35+
def to_checks(cls):
36+
value_checks = cls.VALIDATORS
37+
38+
type_checks = {}
39+
for key in cls._fields:
40+
type_checks[key] = lambda _: None
41+
42+
return type_checks, value_checks
43+
44+
Payload.__name__ = payload_name
45+
46+
return Payload
47+
48+
49+
class ValidationReport(RuntimeError):
50+
def __init__(self, errors_by_field):
51+
self.errors_by_field = errors_by_field
52+
53+
def serialize(self, output_format='text'):
54+
if output_format == 'json':
55+
return dict(errors=self.errors_by_field)
56+
else:
57+
lines = ["- %s: required %s" %
58+
(k, v) for k, v in self.errors_by_field.items()]
59+
lines.insert(0, '')
60+
return 'payload failed to validate:%s' % ('\n'.join(lines),)
61+
62+
63+
def validate_payload(definition, payload):
64+
args = definition(*[payload.get(field, _FieldOnError(field))
65+
for field in definition._fields])
66+
fields = definition._fields
67+
type_checks, value_checks = definition.to_checks()
68+
69+
_, errors_by_field = validate_helper(
70+
args._asdict(), fields, type_checks, value_checks, list_wrap=True)
71+
72+
if errors_by_field:
73+
raise ValidationReport(errors_by_field)
74+
75+
return args
76+
77+
78+
PAYLOAD_POST_USER = _define_payload('PostUserArgs', [
79+
'full_name',
80+
'organization',
81+
'state',
82+
'country',
83+
'email',
84+
'comment',
85+
'password',
86+
], defaultdict(lambda: _is_not_none, dict(
87+
full_name=_is_string_and_non_empty,
88+
organization=_is_string_and_non_empty,
89+
state=_is_string_and_non_empty,
90+
country=_is_string_and_non_empty,
91+
email=_is_string_and_non_empty,
92+
comment=_is_string_and_non_empty,
93+
password=_is_string_and_non_empty,
94+
)))

mig/lib/coresvc/server.py

+2-74
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import werkzeug.exceptions as httpexceptions
4545
from wsgiref.simple_server import WSGIRequestHandler
4646

47+
from mig.lib.coresvc.payloads import ValidationReport, validate_payload, \
48+
PAYLOAD_POST_USER as _REQUEST_ARGS_POST_USER
4749
from mig.shared.base import canonical_user, keyword_auto, force_native_str_rec
4850
from mig.shared.useradm import fill_user, \
4951
create_user as useradm_create_user, search_users as useradm_search_users
@@ -58,20 +60,6 @@ def http_error_from_status_code(http_status_code, http_url, description=None):
5860
return httpexceptions_by_code[http_status_code](description)
5961

6062

61-
class ValidationReport(RuntimeError):
62-
def __init__(self, errors_by_field):
63-
self.errors_by_field = errors_by_field
64-
65-
def serialize(self, output_format='text'):
66-
if output_format == 'json':
67-
return dict(errors=self.errors_by_field)
68-
else:
69-
lines = ["- %s: required %s" %
70-
(k, v) for k, v in self.errors_by_field.items()]
71-
lines.insert(0, '')
72-
return 'payload failed to validate:%s' % ('\n'.join(lines),)
73-
74-
7563
def _create_user(user_dict, conf_path, **kwargs):
7664
try:
7765
useradm_create_user(user_dict, conf_path, keyword_auto, **kwargs)
@@ -80,71 +68,11 @@ def _create_user(user_dict, conf_path, **kwargs):
8068
return 0
8169

8270

83-
def _define_payload(payload_name, payload_fields):
84-
class Payload(namedtuple('PostUserArgs', payload_fields)):
85-
def keys(self):
86-
return Payload._fields
87-
88-
def items(self):
89-
return self._asdict().items()
90-
91-
Payload.__name__ = payload_name
92-
93-
return Payload
94-
95-
96-
def _is_not_none(value):
97-
"""value is not None"""
98-
return value is not None
99-
100-
101-
def _is_string_and_non_empty(value):
102-
"""value is a non-empty string"""
103-
return isinstance(value, str) and len(value) > 0
104-
105-
106-
_REQUEST_ARGS_POST_USER = _define_payload('PostUserArgs', [
107-
'full_name',
108-
'organization',
109-
'state',
110-
'country',
111-
'email',
112-
'comment',
113-
'password',
114-
])
115-
116-
117-
_REQUEST_ARGS_POST_USER._validators = defaultdict(lambda: _is_not_none, dict(
118-
full_name=_is_string_and_non_empty,
119-
organization=_is_string_and_non_empty,
120-
state=_is_string_and_non_empty,
121-
country=_is_string_and_non_empty,
122-
email=_is_string_and_non_empty,
123-
comment=_is_string_and_non_empty,
124-
password=_is_string_and_non_empty,
125-
))
126-
127-
12871
def search_users(configuration, search_filter):
12972
_, hits = useradm_search_users(search_filter, configuration, keyword_auto)
13073
return list((obj for _, obj in hits))
13174

13275

133-
def validate_payload(definition, payload):
134-
args = definition(*[payload.get(field, None)
135-
for field in definition._fields])
136-
137-
errors_by_field = {}
138-
for field_name, field_value in args._asdict().items():
139-
validator_fn = definition._validators[field_name]
140-
if not validator_fn(field_value):
141-
errors_by_field[field_name] = validator_fn.__doc__
142-
if errors_by_field:
143-
raise ValidationReport(errors_by_field)
144-
else:
145-
return args
146-
147-
14876
def _create_and_expose_server(server, configuration):
14977
app = Flask('coreapi')
15078

0 commit comments

Comments
 (0)