safeshield 1.2.1__py3-none-any.whl → 1.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {safeshield-1.2.1.dist-info → safeshield-1.3.2.dist-info}/METADATA +7 -1
- safeshield-1.3.2.dist-info/RECORD +31 -0
- validator/core/validator.py +3 -3
- validator/database/manager.py +6 -1
- validator/rules/array.py +6 -6
- validator/rules/basic.py +200 -4
- validator/rules/comparison.py +12 -12
- validator/rules/conditional.py +71 -238
- validator/rules/date.py +7 -7
- validator/rules/files.py +5 -5
- validator/rules/format.py +8 -8
- validator/rules/string.py +8 -15
- validator/rules/type.py +10 -3
- validator/rules/utilities.py +78 -153
- validator/services/rule_error_handler.py +221 -24
- safeshield-1.2.1.dist-info/RECORD +0 -31
- {safeshield-1.2.1.dist-info → safeshield-1.3.2.dist-info}/LICENSE +0 -0
- {safeshield-1.2.1.dist-info → safeshield-1.3.2.dist-info}/WHEEL +0 -0
- {safeshield-1.2.1.dist-info → safeshield-1.3.2.dist-info}/top_level.txt +0 -0
validator/rules/utilities.py
CHANGED
|
@@ -1,208 +1,133 @@
|
|
|
1
1
|
from .base import ValidationRule
|
|
2
2
|
from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
|
|
3
|
+
from .basic import AcceptedRule, DeclinedRule, ExcludeRule, RequiredRule, PresentRule, MissingRule, ProhibitedRule
|
|
3
4
|
|
|
4
|
-
class
|
|
5
|
+
class RequiredWithRule(RequiredRule):
|
|
5
6
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
6
7
|
if not params:
|
|
7
8
|
return False
|
|
8
|
-
return any(self.get_field_value(param, param) == value for param in params)
|
|
9
|
-
|
|
10
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
11
|
-
return f"The :name must be one of: {', '.join(params)}"
|
|
12
|
-
|
|
13
|
-
class ExcludeRule(ValidationRule):
|
|
14
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
15
|
-
return False # This rule is typically used to exclude fields from validation
|
|
16
|
-
|
|
17
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
18
|
-
return f"The :name field is excluded."
|
|
19
|
-
|
|
20
|
-
class ExcludeIfRule(ValidationRule):
|
|
21
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
22
|
-
conditions = [(f.strip(), v.strip()) for f, v in zip(params[::2], params[1::2])]
|
|
23
|
-
|
|
24
|
-
all_conditions_met = all(
|
|
25
|
-
self.get_field_value(f) == v
|
|
26
|
-
for f, v in conditions
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
if all_conditions_met:
|
|
30
|
-
self.validator._is_exclude = True
|
|
31
|
-
|
|
32
|
-
return True
|
|
33
|
-
|
|
34
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
35
|
-
return f"The :name field is excluded when {params[0]} is {params[1]}."
|
|
36
|
-
|
|
37
|
-
class ExcludeUnlessRule(ValidationRule):
|
|
38
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
39
|
-
conditions = [(f.strip(), v.strip()) for f, v in zip(params[::2], params[1::2])]
|
|
40
|
-
|
|
41
|
-
all_conditions_met = all(
|
|
42
|
-
self.get_field_value(f) == v
|
|
43
|
-
for f, v in conditions
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if not all_conditions_met:
|
|
47
|
-
self.validator._is_exclude = True
|
|
48
9
|
|
|
10
|
+
if any(f in self.validator.data for f in params):
|
|
11
|
+
return super().validate(field, value, params)
|
|
49
12
|
return True
|
|
50
|
-
|
|
51
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
52
|
-
return f"The :name field is excluded unless {params[0]} is {params[1]}."
|
|
53
13
|
|
|
54
|
-
class
|
|
14
|
+
class RequiredWithAllRule(RequiredRule):
|
|
55
15
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
56
|
-
if
|
|
57
|
-
|
|
16
|
+
if not params:
|
|
17
|
+
return False
|
|
58
18
|
|
|
19
|
+
if all(f in self.validator.data for f in params):
|
|
20
|
+
return not self.is_empty(value)
|
|
59
21
|
return True
|
|
60
22
|
|
|
61
|
-
|
|
62
|
-
return f"The :name field is excluded when any of {', '.join(params)} is present."
|
|
63
|
-
|
|
64
|
-
class ExcludeWithoutRule(ValidationRule):
|
|
23
|
+
class RequiredWithoutRule(RequiredRule):
|
|
65
24
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
66
|
-
if
|
|
67
|
-
|
|
25
|
+
if not params:
|
|
26
|
+
return False
|
|
68
27
|
|
|
28
|
+
if any(f not in self.validator.data for f in params):
|
|
29
|
+
return super().validate(field, value, params)
|
|
69
30
|
return True
|
|
70
31
|
|
|
71
|
-
|
|
72
|
-
return f"The :name field is excluded when any of {', '.join(params)} is missing."
|
|
73
|
-
|
|
74
|
-
class MissingRule(ValidationRule):
|
|
75
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
76
|
-
return value is None
|
|
77
|
-
|
|
78
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
79
|
-
return f"The :name field must be missing."
|
|
80
|
-
|
|
81
|
-
class MissingIfRule(ValidationRule):
|
|
82
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
83
|
-
if len(params) < 2:
|
|
84
|
-
return False
|
|
85
|
-
other_field, other_value = params[0], params[1]
|
|
86
|
-
return value is None if self.get_field_value(other_field, None) == other_value else True
|
|
87
|
-
|
|
88
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
89
|
-
return f"The :name field must be missing when {params[0]} is {params[1]}."
|
|
90
|
-
|
|
91
|
-
class MissingUnlessRule(ValidationRule):
|
|
32
|
+
class RequiredWithoutAllRule(RequiredRule):
|
|
92
33
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
93
|
-
if
|
|
34
|
+
if not params:
|
|
94
35
|
return False
|
|
95
|
-
|
|
96
|
-
|
|
36
|
+
|
|
37
|
+
if all(f not in self.validator.data for f in params):
|
|
38
|
+
return super().validate(field, value, params)
|
|
39
|
+
return True
|
|
97
40
|
|
|
98
|
-
|
|
99
|
-
return f"The :name field must be missing unless {params[0]} is {params[1]}."
|
|
100
|
-
|
|
101
|
-
class MissingWithRule(ValidationRule):
|
|
41
|
+
class PresentWithRule(PresentRule):
|
|
102
42
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
103
43
|
if not params:
|
|
104
44
|
return False
|
|
105
|
-
|
|
45
|
+
if any(self.get_field_value(param, None) is not None for param in params):
|
|
46
|
+
return super().validate(field, value, params)
|
|
47
|
+
|
|
48
|
+
return True
|
|
106
49
|
|
|
107
|
-
|
|
108
|
-
return f"The :name field must be missing when any of {', '.join(params)} is present."
|
|
109
|
-
|
|
110
|
-
class MissingWithAllRule(ValidationRule):
|
|
50
|
+
class PresentWithAllRule(PresentRule):
|
|
111
51
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
112
52
|
if not params:
|
|
113
53
|
return False
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class PresentIfRule(ValidationRule):
|
|
54
|
+
|
|
55
|
+
if all(self.get_field_value(param, None) is not None for param in params):
|
|
56
|
+
return super().validate(field, value, params)
|
|
57
|
+
|
|
58
|
+
class MissingWithRule(MissingRule):
|
|
120
59
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
121
|
-
if
|
|
60
|
+
if not params:
|
|
122
61
|
return False
|
|
123
|
-
|
|
124
|
-
|
|
62
|
+
|
|
63
|
+
if any(self.get_field_value(param, None) is not None for param in params):
|
|
64
|
+
return super().validate(field, value, params)
|
|
65
|
+
|
|
66
|
+
return True
|
|
125
67
|
|
|
126
|
-
|
|
127
|
-
return f"The :name field must be present when {params[0]} is {params[1]}."
|
|
128
|
-
|
|
129
|
-
class PresentUnlessRule(ValidationRule):
|
|
68
|
+
class MissingWithAllRule(MissingRule):
|
|
130
69
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
131
|
-
if
|
|
70
|
+
if not params:
|
|
132
71
|
return False
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return
|
|
72
|
+
|
|
73
|
+
if all(self.get_field_value(param, None) is not None for param in params):
|
|
74
|
+
return super().validate(field, value, params)
|
|
75
|
+
|
|
76
|
+
return True
|
|
138
77
|
|
|
139
|
-
class
|
|
78
|
+
class ExcludeWithRule(ExcludeRule):
|
|
140
79
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
141
|
-
if not params:
|
|
142
|
-
return
|
|
143
|
-
|
|
80
|
+
if any(not self.is_empty(self.get_field_value(param, None)) for param in params):
|
|
81
|
+
return super().validate(field, value, params)
|
|
82
|
+
|
|
83
|
+
return True
|
|
144
84
|
|
|
145
|
-
|
|
146
|
-
return f"The :name field must be present when any of {', '.join(params)} is present."
|
|
147
|
-
|
|
148
|
-
class PresentWithAllRule(ValidationRule):
|
|
85
|
+
class ExcludeWithoutRule(ExcludeRule):
|
|
149
86
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
150
|
-
if
|
|
151
|
-
return
|
|
152
|
-
|
|
87
|
+
if any(self.is_empty(self.get_field_value(param, None)) for param in params):
|
|
88
|
+
return super().validate(field, value, params)
|
|
89
|
+
|
|
90
|
+
return True
|
|
153
91
|
|
|
154
|
-
|
|
155
|
-
return f"The :name field must be present when all of {', '.join(params)} are present."
|
|
156
|
-
|
|
157
|
-
class ProhibitedIfAcceptedRule(ValidationRule):
|
|
92
|
+
class ProhibitedIfAcceptedRule(ProhibitedRule, AcceptedRule):
|
|
158
93
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
159
94
|
if not params:
|
|
160
95
|
return False
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return f"The :name field is prohibited when {params[0]} is accepted."
|
|
96
|
+
|
|
97
|
+
if AcceptedRule.validate(self, field, params[0], params):
|
|
98
|
+
return super().validate(field, value, params)
|
|
99
|
+
return True
|
|
166
100
|
|
|
167
|
-
class ProhibitedIfDeclinedRule(
|
|
101
|
+
class ProhibitedIfDeclinedRule(ProhibitedRule, DeclinedRule):
|
|
168
102
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
169
103
|
if not params:
|
|
170
104
|
return False
|
|
171
|
-
|
|
172
|
-
|
|
105
|
+
|
|
106
|
+
if DeclinedRule.validate(self, field, params[0], params):
|
|
107
|
+
return super().validate(field, value, params)
|
|
108
|
+
|
|
109
|
+
return True
|
|
173
110
|
|
|
174
|
-
|
|
175
|
-
return f"The :name field is prohibited when {params[0]} is declined."
|
|
176
|
-
|
|
177
|
-
class ProhibitsRule(ValidationRule):
|
|
178
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
179
|
-
if not params or value is None:
|
|
180
|
-
return True
|
|
181
|
-
return all(self.get_field_value(param, param) in (None, 'None') for param in params)
|
|
182
|
-
|
|
183
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
184
|
-
return f"When :name is present, {', '.join(params)} must be absent."
|
|
185
|
-
|
|
186
|
-
class RequiredIfAcceptedRule(ValidationRule):
|
|
111
|
+
class RequiredIfAcceptedRule(RequiredRule, AcceptedRule):
|
|
187
112
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
188
113
|
if not params:
|
|
189
114
|
return False
|
|
190
|
-
|
|
191
|
-
|
|
115
|
+
|
|
116
|
+
if AcceptedRule.validate(self, field, params[0], params):
|
|
117
|
+
return super().validate(field, value, params)
|
|
118
|
+
|
|
119
|
+
return True
|
|
192
120
|
|
|
193
|
-
|
|
194
|
-
return f"The :name field is required when {params[0]} is accepted."
|
|
195
|
-
|
|
196
|
-
class RequiredIfDeclinedRule(ValidationRule):
|
|
121
|
+
class RequiredIfDeclinedRule(RequiredRule, DeclinedRule):
|
|
197
122
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
198
123
|
if not params:
|
|
199
124
|
return False
|
|
200
|
-
|
|
201
|
-
|
|
125
|
+
|
|
126
|
+
if DeclinedRule.validate(self, field, params[0], params):
|
|
127
|
+
return super().validate(field, value, params)
|
|
128
|
+
|
|
129
|
+
return True
|
|
202
130
|
|
|
203
|
-
def message(self, field: str, params: List[str]) -> str:
|
|
204
|
-
return f"The :name field is required when {params[0]} is declined."
|
|
205
|
-
|
|
206
131
|
class RequiredArrayKeysRule(ValidationRule):
|
|
207
132
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
208
133
|
if not isinstance(value, dict) or not params:
|
|
@@ -210,4 +135,4 @@ class RequiredArrayKeysRule(ValidationRule):
|
|
|
210
135
|
return all(key in value for key in params)
|
|
211
136
|
|
|
212
137
|
def message(self, field: str, params: List[str]) -> str:
|
|
213
|
-
return f"The :
|
|
138
|
+
return f"The :attribute must contain all required keys: {', '.join(params)}"
|
|
@@ -1,41 +1,238 @@
|
|
|
1
|
-
from typing import Dict, List, Any
|
|
1
|
+
from typing import Dict, List, Any, Optional, Union
|
|
2
|
+
import re
|
|
2
3
|
|
|
3
4
|
class RuleErrorHandler:
|
|
4
|
-
"""
|
|
5
|
+
"""Enhanced validation error handler with complete Laravel-style placeholder support,
|
|
6
|
+
including field-value pair parameters (field1,value1,field2,value2)"""
|
|
7
|
+
|
|
5
8
|
def __init__(self, messages: Dict[str, str], custom_attributes: Dict[str, str]):
|
|
6
9
|
self.messages = messages or {}
|
|
7
10
|
self.custom_attributes = custom_attributes or {}
|
|
8
11
|
self.errors: Dict[str, List[str]] = {}
|
|
12
|
+
self._current_rule: Optional[str] = None
|
|
13
|
+
self._current_params: Optional[List[str]] = None
|
|
14
|
+
self._current_value: Optional[Any] = None
|
|
9
15
|
|
|
10
|
-
def add_error(self, field: str, rule_name: str, default_message: str, value: Any):
|
|
11
|
-
"""Add formatted error message"""
|
|
12
|
-
|
|
16
|
+
def add_error(self, field: str, rule_name: str, rule_params: List[str], default_message: str, value: Any) -> None:
|
|
17
|
+
"""Add a formatted error message with complete placeholder support"""
|
|
18
|
+
self._current_rule = rule_name
|
|
19
|
+
self._current_params = rule_params
|
|
20
|
+
self._current_value = value
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
message = self._format_message(field, rule_name, default_message, value)
|
|
15
23
|
self.errors.setdefault(field, []).append(message)
|
|
16
24
|
|
|
17
25
|
def _format_message(self, field: str, rule_name: str, default_message: str, value: Any) -> str:
|
|
18
|
-
"""Format error message with placeholders"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
"""Format error message with all supported placeholders"""
|
|
27
|
+
attribute = self._get_attribute_name(field)
|
|
28
|
+
value_str = self._stringify_value(value)
|
|
29
|
+
|
|
30
|
+
# Get the most specific message available
|
|
31
|
+
message = self._get_message(field, rule_name, attribute, default_message)
|
|
32
|
+
|
|
33
|
+
# Prepare all possible replacements
|
|
34
|
+
replacements = self._prepare_replacements(attribute, value_str)
|
|
35
|
+
|
|
36
|
+
# Apply replacements safely
|
|
37
|
+
return self._apply_replacements(message, replacements)
|
|
38
|
+
|
|
39
|
+
def _get_attribute_name(self, field: str) -> str:
|
|
40
|
+
"""Get the display name for a field with nested field support"""
|
|
41
|
+
# Check for exact match first
|
|
42
|
+
if field in self.custom_attributes:
|
|
43
|
+
return self.custom_attributes[field]
|
|
44
|
+
|
|
45
|
+
# Handle nested fields (e.g., 'user.profile.name')
|
|
46
|
+
parts = field.split('.')
|
|
47
|
+
for i in range(len(parts), 0, -1):
|
|
48
|
+
wildcard_key = '.'.join(parts[:i]) + '.*'
|
|
49
|
+
if wildcard_key in self.custom_attributes:
|
|
50
|
+
return self.custom_attributes[wildcard_key]
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
# Fallback to last part or field itself
|
|
53
|
+
return self.custom_attributes.get(parts[-1], field.replace('_', ' ').title())
|
|
54
|
+
|
|
55
|
+
def _stringify_value(self, value: Any) -> str:
|
|
56
|
+
"""Convert any value to a string representation"""
|
|
57
|
+
if value is None:
|
|
58
|
+
return ''
|
|
59
|
+
if isinstance(value, (list, dict, set)):
|
|
60
|
+
return ', '.join(str(v) for v in value) if value else ''
|
|
61
|
+
return str(value)
|
|
62
|
+
|
|
63
|
+
def _get_message(self, field: str, rule_name: str, attribute: str, default: str) -> str:
|
|
64
|
+
"""Get the most specific error message available"""
|
|
65
|
+
return (
|
|
66
|
+
self.messages.get(f"{field}.{rule_name}") or # Field-specific rule message
|
|
67
|
+
self.messages.get(field) or # Field-specific default
|
|
68
|
+
self.messages.get(rule_name) or # Rule-specific default
|
|
69
|
+
default # Fallback
|
|
37
70
|
)
|
|
38
71
|
|
|
72
|
+
def _prepare_replacements(self, attribute: str, value_str: str) -> Dict[str, str]:
|
|
73
|
+
"""Prepare all placeholder replacements including field-value pairs"""
|
|
74
|
+
replacements = {
|
|
75
|
+
':attribute': attribute,
|
|
76
|
+
':input': value_str,
|
|
77
|
+
':value': value_str,
|
|
78
|
+
':values': self._get_values_param(),
|
|
79
|
+
':min': self._get_min_param(),
|
|
80
|
+
':max': self._get_max_param(),
|
|
81
|
+
':size': self._get_size_param(),
|
|
82
|
+
':other': self._get_other_param_display(),
|
|
83
|
+
':date': self._get_date_param(),
|
|
84
|
+
':format': self._get_format_param(),
|
|
85
|
+
':param': self._get_first_param(),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Add numbered placeholders for field-value pairs (e.g., :other1, :value1, :other2, :value2)
|
|
89
|
+
if self._is_field_value_rule() and self._current_params:
|
|
90
|
+
field_value_pairs = self._get_field_value_pairs()
|
|
91
|
+
if field_value_pairs:
|
|
92
|
+
first_field, first_value = field_value_pairs[0]
|
|
93
|
+
replacements[':other'] = self._get_attribute_name(first_field)
|
|
94
|
+
replacements[':value'] = first_value
|
|
95
|
+
|
|
96
|
+
for i, (field, val) in enumerate(field_value_pairs[1:], start=2):
|
|
97
|
+
replacements[f':other{i}'] = self._get_attribute_name(field)
|
|
98
|
+
replacements[f':value{i}'] = val
|
|
99
|
+
|
|
100
|
+
return replacements
|
|
101
|
+
|
|
102
|
+
def _is_field_value_rule(self) -> bool:
|
|
103
|
+
"""Check if the current rule uses field-value pairs"""
|
|
104
|
+
return self._current_rule and self._current_rule.lower() in {
|
|
105
|
+
'required_if', 'required_unless',
|
|
106
|
+
'exclude_if', 'exclude_unless',
|
|
107
|
+
'missing_if', 'missing_unless',
|
|
108
|
+
'present_if', 'present_unless'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def _get_field_value_pairs(self) -> List[tuple]:
|
|
112
|
+
"""Extract field-value pairs from parameters"""
|
|
113
|
+
if not self._current_params:
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
pairs = []
|
|
117
|
+
params = self._current_params.copy()
|
|
118
|
+
|
|
119
|
+
# Process parameters in pairs (field, value)
|
|
120
|
+
while len(params) >= 2:
|
|
121
|
+
field = params.pop(0)
|
|
122
|
+
value = params.pop(0)
|
|
123
|
+
pairs.append((field, value))
|
|
124
|
+
|
|
125
|
+
return pairs
|
|
126
|
+
|
|
127
|
+
def _apply_replacements(self, message: str, replacements: Dict[str, str]) -> str:
|
|
128
|
+
"""Safely apply all replacements to the message"""
|
|
129
|
+
for placeholder, replacement in replacements.items():
|
|
130
|
+
if replacement is not None:
|
|
131
|
+
# Use regex to avoid partial replacements
|
|
132
|
+
message = re.sub(
|
|
133
|
+
re.escape(placeholder) + r'(?![a-zA-Z0-9_])',
|
|
134
|
+
str(replacement),
|
|
135
|
+
message
|
|
136
|
+
)
|
|
137
|
+
return message
|
|
138
|
+
|
|
139
|
+
def _get_other_param_display(self) -> Optional[str]:
|
|
140
|
+
"""Get display names for other fields with proper formatting"""
|
|
141
|
+
other_fields = self._get_raw_other_fields()
|
|
142
|
+
if not other_fields:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
display_names = [self._get_attribute_name(f) for f in other_fields]
|
|
146
|
+
|
|
147
|
+
if len(display_names) == 1:
|
|
148
|
+
return display_names[0]
|
|
149
|
+
if len(display_names) == 2:
|
|
150
|
+
return f"{display_names[0]} and {display_names[1]}"
|
|
151
|
+
return f"{', '.join(display_names[:-1])}, and {display_names[-1]}"
|
|
152
|
+
|
|
153
|
+
def _get_raw_other_fields(self) -> List[str]:
|
|
154
|
+
"""Extract field references from rule parameters"""
|
|
155
|
+
if not self._current_rule or not self._current_params:
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
rule = self._current_rule.lower()
|
|
159
|
+
|
|
160
|
+
# Rules with field-value pairs (field1,value1,field2,value2,...)
|
|
161
|
+
if rule in {
|
|
162
|
+
'required_if', 'required_unless', 'exclude_if', 'exclude_unless',
|
|
163
|
+
'missing_if', 'missing_unless', 'present_if', 'present_unless'
|
|
164
|
+
}:
|
|
165
|
+
return self._current_params[::2] # Take every even index
|
|
166
|
+
|
|
167
|
+
# Rules with just field references
|
|
168
|
+
if rule in {
|
|
169
|
+
'prohibits', 'exclude_with', 'exclude_without',
|
|
170
|
+
'missing_with', 'missing_with_all',
|
|
171
|
+
'present_with', 'present_with_all'
|
|
172
|
+
}:
|
|
173
|
+
return self._current_params
|
|
174
|
+
|
|
175
|
+
# Single field rules
|
|
176
|
+
if rule in {
|
|
177
|
+
'required_if_accepted', 'required_if_declined',
|
|
178
|
+
'prohibited_if_accepted', 'prohibited_if_declined'
|
|
179
|
+
}:
|
|
180
|
+
return [self._current_params[0]] if self._current_params else []
|
|
181
|
+
|
|
182
|
+
return []
|
|
183
|
+
|
|
184
|
+
def _get_min_param(self) -> Optional[str]:
|
|
185
|
+
"""Get min parameter from rule"""
|
|
186
|
+
if not self._current_params:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
if self._current_rule and self._current_rule.startswith(('min', 'between', 'digits_between')):
|
|
190
|
+
return self._current_params[0]
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def _get_max_param(self) -> Optional[str]:
|
|
194
|
+
"""Get max parameter from rule"""
|
|
195
|
+
if not self._current_params or len(self._current_params) < 2:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
if self._current_rule and self._current_rule.startswith(('max', 'between', 'digits_between')):
|
|
199
|
+
return self._current_params[1] if self._current_rule.startswith('between') else self._current_params[0]
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def _get_size_param(self) -> Optional[str]:
|
|
203
|
+
"""Get size parameter from rule"""
|
|
204
|
+
if self._current_rule and self._current_rule.startswith('size') and self._current_params:
|
|
205
|
+
return self._current_params[0]
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def _get_values_param(self) -> Optional[str]:
|
|
209
|
+
"""Get values list for in/not_in rules"""
|
|
210
|
+
if (self._current_rule and
|
|
211
|
+
self._current_rule.startswith(('in', 'not_in')) and
|
|
212
|
+
self._current_params):
|
|
213
|
+
return ', '.join(self._current_params)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def _get_date_param(self) -> Optional[str]:
|
|
217
|
+
"""Get date parameter for date rules"""
|
|
218
|
+
if (self._current_rule and
|
|
219
|
+
self._current_rule.startswith(('after', 'before', 'after_or_equal', 'before_or_equal')) and
|
|
220
|
+
self._current_params):
|
|
221
|
+
return self._current_params[0]
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
def _get_format_param(self) -> Optional[str]:
|
|
225
|
+
"""Get format parameter"""
|
|
226
|
+
if (self._current_rule and
|
|
227
|
+
self._current_rule.startswith('date_format') and
|
|
228
|
+
self._current_params):
|
|
229
|
+
return self._current_params[0]
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
def _get_first_param(self) -> Optional[str]:
|
|
233
|
+
"""Get first parameter from rule"""
|
|
234
|
+
return self._current_params[0] if self._current_params else None
|
|
235
|
+
|
|
39
236
|
@property
|
|
40
237
|
def has_errors(self) -> bool:
|
|
41
238
|
"""Check if any errors exist"""
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
validator/__init__.py,sha256=udxDzUicPfxBOAQvzsnl3pHur9VUppKbWMgg35hpiww,244
|
|
2
|
-
validator/exceptions.py,sha256=y2v7CaXmeGFHWcnigtLl4U-sFta_jMiXkGKXWIIVglY,366
|
|
3
|
-
validator/factory.py,sha256=bImQNLhEJg5VTxtHiMYVb2EbHWvimTDqHq-UcA8uolw,812
|
|
4
|
-
validator/core/__init__.py,sha256=ZcqlXJSk03i_CVzmIN-nVe1UOyvwwO5jhbEj7f62Y_o,59
|
|
5
|
-
validator/core/validator.py,sha256=00qVnbH-EJC5KALlaoUBLAfsszAFLcoSxfRbmy0amyk,12751
|
|
6
|
-
validator/database/__init__.py,sha256=O-cB6-MhNapJ3iwe5jvifbMfr1dPjXLtEdfNTKIu0hc,171
|
|
7
|
-
validator/database/detector.py,sha256=Vac7oVL26GjU6expGo01-6mgUtXqldr-jirzpYokZBM,9597
|
|
8
|
-
validator/database/manager.py,sha256=Ezz4NUh22Hz2puh-NJggSGCaw3lAGyp3V88plMeBBVU,6232
|
|
9
|
-
validator/rules/__init__.py,sha256=nDE3qoI82qJTCbILLUWkXuwsMOmsDtB1m-3IGIvRfpY,919
|
|
10
|
-
validator/rules/array.py,sha256=tx8FCDqn-27Vs7tgtjeoCE9ceDMVrdBEb2-pq-lNLuo,2809
|
|
11
|
-
validator/rules/base.py,sha256=hrGESfkdvqZrQ1yIK_ftVVOMUuyvNFoz-_qaqUucSy8,3474
|
|
12
|
-
validator/rules/basic.py,sha256=fMk0s5_IEQu27X1wndP_ocNyOZ1upXJCn7fR9YYbI74,1527
|
|
13
|
-
validator/rules/comparison.py,sha256=9xb2mO5GiThR1iO8867ex2o7olQC2Bnew6MBdD2UtEo,9402
|
|
14
|
-
validator/rules/conditional.py,sha256=8_O1etzCyCGeD8lmfhegJ1uzhkBSjTEVdufbZmkqgP4,12993
|
|
15
|
-
validator/rules/date.py,sha256=18JIKTO5nzFtCnJEMXm4OUbneqTMB7HGPN6jUkxGU4Y,5278
|
|
16
|
-
validator/rules/files.py,sha256=vu_TZFffDPzDojyTsFXSQ6MmQm3WU6ppFfz7-wuDy98,6550
|
|
17
|
-
validator/rules/format.py,sha256=UeeIAkFn0CdzC5bS9tI78_lbPYRgcpaUujxdmyCSdzE,3744
|
|
18
|
-
validator/rules/string.py,sha256=vYzu4ICKY9FCuGahmsQCoJLmnlBF7uNvVazFj9DQ438,3178
|
|
19
|
-
validator/rules/type.py,sha256=Tu-EOBkTtxkcCe0ANXavurZC449n63iE_VXVzc3BIiM,1596
|
|
20
|
-
validator/rules/utilities.py,sha256=AIm9JRGYf6cTCSkivg3gTj3U5DnXlCAJ5ej1yUSa1dU,9724
|
|
21
|
-
validator/services/__init__.py,sha256=zzKTmqL7v4niFGWHJBfWLqgJ0iTaW_69OzYZN8uInzQ,210
|
|
22
|
-
validator/services/rule_conflict.py,sha256=s1RJNUY5d0WtSMHkrKulBCgJ2BZL2GE0Eu5pdAoiIbM,4943
|
|
23
|
-
validator/services/rule_error_handler.py,sha256=MGvvkP6hbZLpVXxC3xpzg15OmVdPlk7l0M2Srmy5VfM,1729
|
|
24
|
-
validator/services/rule_preparer.py,sha256=jRcMNjqq2xyZjO64Pim8jWmja5DmTzf0V_uuHG0lJTg,5621
|
|
25
|
-
validator/utils/__init__.py,sha256=Yzo-xv285Be-a233M4duDdYtscuHiuBbPSX_C8yViJI,20
|
|
26
|
-
validator/utils/string.py,sha256=0YACzeEaWNEOR9_7O9A8D1ItIbtWfOJ8IfrzcB8VMYA,515
|
|
27
|
-
safeshield-1.2.1.dist-info/LICENSE,sha256=qugtRyKckyaks6hd2xyxOFSOYM6au1N80pMXuMTPvC4,1090
|
|
28
|
-
safeshield-1.2.1.dist-info/METADATA,sha256=CvKZoic-BfXRql20T5GApMX4WnfD4IO51mFeDLg_asQ,1843
|
|
29
|
-
safeshield-1.2.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
30
|
-
safeshield-1.2.1.dist-info/top_level.txt,sha256=iUtV3dlHOIiMfLuY4pruY00lFni8JzOkQ3Nh1II19OE,10
|
|
31
|
-
safeshield-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|