forked from OperationCode/resources_api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidations.py
More file actions
149 lines (117 loc) · 4.94 KB
/
Copy pathvalidations.py
File metadata and controls
149 lines (117 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
from flask import request
from app.models import Resource
from app.utils import standardize_response
MISSING_BODY = "missing-body"
MISSING_PARAMS = "missing-params"
INVALID_PARAMS = "invalid-params"
INVALID_TYPE = "invalid-type"
def requires_body(func):
def wrapper(*args, **kwargs):
try:
# JSON body is {} or []
if not request.get_json():
return missing_json_error()
except Exception as e:
# JSON body is completely missing
if "Expecting value: line 1 column 1" in str(e):
return missing_json_error()
return func(*args, **kwargs)
return wrapper
def missing_json_error():
message = "You must provide a valid JSON body to use this endpoint"
error = {'errors': [{MISSING_BODY: {"message": message}}]}
return standardize_response(error, status_code=422)
def validate_resource_list(method, rlist):
errors = {'errors': []}
max_resources = 200
if len(rlist) > max_resources:
msg = f"This endpoint will accept a max of {max_resources} resources"
return {"errors": [{"too-long": {"message": msg}}]}
for i, r in enumerate(rlist):
validation = validate_resource(method, r)
if validation:
validation['index'] = i
errors['errors'].append(validation)
if bool(errors['errors']):
return errors
def validate_resource(method, json, id=-1):
errors = None
validation_errors = {}
missing_params = {"params": []}
invalid_params = {"params": []}
required = []
for column in Resource.__table__.columns:
# strip _id from category_id
col_name = column.name.replace('_id', '')
# There are only required parameters for POSTing new resources.
if method == 'POST':
if column.nullable is False and col_name != 'id':
required.append(col_name)
# Type Validation.
col_type = column.type.python_type
field = json.get(col_name)
if field is not None:
# Category is a foreign key (int) relation but we only accept string input.
# The provided category will get mapped to this resource later on.
if col_name == 'category':
col_type = str
# Allow String columns to accept integers.
if col_type is str:
if type(field) in [int, float]:
# Do not allow integers as urls
if col_name != 'url':
continue
# Allow Bool columns to accept strings that are variations "true" or "false"
if col_type is bool:
if type(field) is str and field.lower() in ["false", "true"]:
continue
# Validate that fields are of the correct type.
if col_type is not type(field):
invalid_params["params"].append(col_name)
# Special case for Languages - Must be list and all elements must be str.
langs = json.get('languages')
if langs:
if type(langs) is not list or not all(map(lambda x: type(x) is str, langs)):
invalid_params["params"].append("languages")
for prop in required:
if json.get(prop) is None:
missing_params["params"].append(prop)
url = json.get("url")
if url is not None:
# If there is an existing resource with the requested url
# and it isn't the Resource we are trying to update, return an error.
resource = Resource.query.filter_by(url=url).first()
if resource and resource.id != id:
invalid_params["params"].append('url')
message = f"Resource id {resource.id} already has this URL."
invalid_params["message"] = message
invalid_params["resource"] = \
f"https://resources.operationcode.org/api/v1/{resource.id}"
if missing_params["params"]:
validation_errors[MISSING_PARAMS] = missing_params
msg = " The following params were missing: "
msg += ", ".join(missing_params.get("params")) + "."
validation_errors[MISSING_PARAMS]["message"] = msg
errors = True
if invalid_params["params"]:
validation_errors[INVALID_PARAMS] = invalid_params
msg = " The following params were invalid: "
msg += ", ".join(invalid_params.get("params")) + ". "
msg += invalid_params.get("message", "")
validation_errors[INVALID_PARAMS]["message"] = msg.strip()
errors = True
if errors:
return validation_errors
def wrong_type(type_accepted, type_provided):
types = {
dict: "object",
int: "int",
list: "array",
float: "number",
bool: "boolean",
str: "string"
}
json_type = types[type_provided]
msg = f"Expected {type_accepted}, but found {json_type}"
validation_errors = {"errors": {INVALID_TYPE: {"message": msg}}}
return standardize_response(payload=validation_errors, status_code=422)