string-schema 0.1.1__py3-none-any.whl → 0.1.3__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.
- string_schema/core/__init__.py +23 -0
- string_schema/core/builders.py +244 -0
- string_schema/core/fields.py +138 -0
- string_schema/core/validators.py +242 -0
- string_schema/examples/__init__.py +36 -0
- string_schema/examples/presets.py +345 -0
- string_schema/examples/recipes.py +380 -0
- string_schema/integrations/__init__.py +15 -0
- string_schema/integrations/json_schema.py +385 -0
- string_schema/integrations/openapi.py +484 -0
- string_schema/integrations/pydantic.py +662 -0
- string_schema/integrations/reverse.py +275 -0
- string_schema/parsing/__init__.py +16 -0
- string_schema/parsing/optimizer.py +246 -0
- string_schema/parsing/string_parser.py +703 -0
- string_schema/parsing/syntax.py +250 -0
- {string_schema-0.1.1.dist-info → string_schema-0.1.3.dist-info}/METADATA +2 -3
- string_schema-0.1.3.dist-info/RECORD +24 -0
- string_schema-0.1.1.dist-info/RECORD +0 -8
- {string_schema-0.1.1.dist-info → string_schema-0.1.3.dist-info}/WHEEL +0 -0
- {string_schema-0.1.1.dist-info → string_schema-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {string_schema-0.1.1.dist-info → string_schema-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module for String Schema
|
|
3
|
+
|
|
4
|
+
Contains the fundamental classes and functions for schema definition and building.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .fields import SimpleField
|
|
8
|
+
from .builders import (
|
|
9
|
+
simple_schema,
|
|
10
|
+
list_of_objects_schema,
|
|
11
|
+
simple_array_schema,
|
|
12
|
+
quick_pydantic_model
|
|
13
|
+
)
|
|
14
|
+
from .validators import validate_schema
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"SimpleField",
|
|
18
|
+
"simple_schema",
|
|
19
|
+
"list_of_objects_schema",
|
|
20
|
+
"simple_array_schema",
|
|
21
|
+
"quick_pydantic_model",
|
|
22
|
+
"validate_schema"
|
|
23
|
+
]
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schema builders for Simple Schema
|
|
3
|
+
|
|
4
|
+
Contains functions for building schemas from SimpleField definitions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional, Union, Type
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from .fields import SimpleField
|
|
11
|
+
|
|
12
|
+
# Optional pydantic import
|
|
13
|
+
try:
|
|
14
|
+
from pydantic import BaseModel, Field, create_model
|
|
15
|
+
HAS_PYDANTIC = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
HAS_PYDANTIC = False
|
|
18
|
+
BaseModel = None
|
|
19
|
+
Field = None
|
|
20
|
+
create_model = None
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def simple_schema(fields: Dict[str, Union[str, SimpleField]]) -> Dict[str, Any]:
|
|
26
|
+
"""Generate JSON Schema from simple field definitions with enhanced support"""
|
|
27
|
+
properties = {}
|
|
28
|
+
required = []
|
|
29
|
+
|
|
30
|
+
for field_name, field_def in fields.items():
|
|
31
|
+
if isinstance(field_def, str):
|
|
32
|
+
field_def = SimpleField(field_def)
|
|
33
|
+
|
|
34
|
+
prop_schema = _simple_field_to_json_schema(field_def)
|
|
35
|
+
properties[field_name] = prop_schema
|
|
36
|
+
|
|
37
|
+
if field_def.required:
|
|
38
|
+
required.append(field_name)
|
|
39
|
+
|
|
40
|
+
schema = {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": properties
|
|
43
|
+
}
|
|
44
|
+
if required:
|
|
45
|
+
schema["required"] = required
|
|
46
|
+
|
|
47
|
+
return schema
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _simple_field_to_json_schema(field: SimpleField) -> Dict[str, Any]:
|
|
51
|
+
"""Convert SimpleField to JSON Schema property with enhanced features"""
|
|
52
|
+
# Handle union types first
|
|
53
|
+
if field.union_types and len(field.union_types) > 1:
|
|
54
|
+
union_schemas = []
|
|
55
|
+
for union_type in field.union_types:
|
|
56
|
+
if union_type == "null":
|
|
57
|
+
union_schemas.append({"type": "null"})
|
|
58
|
+
else:
|
|
59
|
+
union_schemas.append({"type": union_type})
|
|
60
|
+
prop = {"anyOf": union_schemas}
|
|
61
|
+
|
|
62
|
+
# Add description to the union
|
|
63
|
+
if field.description:
|
|
64
|
+
prop["description"] = field.description
|
|
65
|
+
|
|
66
|
+
return prop
|
|
67
|
+
|
|
68
|
+
# Regular single type
|
|
69
|
+
prop = {"type": field.field_type}
|
|
70
|
+
|
|
71
|
+
if field.description:
|
|
72
|
+
prop["description"] = field.description
|
|
73
|
+
if field.default is not None:
|
|
74
|
+
prop["default"] = field.default
|
|
75
|
+
|
|
76
|
+
# Handle enum/choices
|
|
77
|
+
if field.choices:
|
|
78
|
+
prop["enum"] = field.choices
|
|
79
|
+
|
|
80
|
+
# Add format hints for special types
|
|
81
|
+
if field.format_hint:
|
|
82
|
+
if field.format_hint == "email":
|
|
83
|
+
prop["format"] = "email"
|
|
84
|
+
elif field.format_hint in ["url", "uri"]:
|
|
85
|
+
prop["format"] = "uri"
|
|
86
|
+
elif field.format_hint == "datetime":
|
|
87
|
+
prop["format"] = "date-time"
|
|
88
|
+
elif field.format_hint == "date":
|
|
89
|
+
prop["format"] = "date"
|
|
90
|
+
elif field.format_hint == "uuid":
|
|
91
|
+
prop["format"] = "uuid"
|
|
92
|
+
# Note: phone doesn't have a standard JSON Schema format
|
|
93
|
+
|
|
94
|
+
# Numeric constraints
|
|
95
|
+
if field.field_type in ["integer", "number"]:
|
|
96
|
+
if field.min_val is not None:
|
|
97
|
+
prop["minimum"] = field.min_val
|
|
98
|
+
if field.max_val is not None:
|
|
99
|
+
prop["maximum"] = field.max_val
|
|
100
|
+
|
|
101
|
+
# String constraints
|
|
102
|
+
if field.field_type == "string":
|
|
103
|
+
if field.min_length is not None:
|
|
104
|
+
prop["minLength"] = field.min_length
|
|
105
|
+
if field.max_length is not None:
|
|
106
|
+
prop["maxLength"] = field.max_length
|
|
107
|
+
|
|
108
|
+
# Array constraints (for when this field itself is an array)
|
|
109
|
+
if field.min_items is not None:
|
|
110
|
+
prop["minItems"] = field.min_items
|
|
111
|
+
if field.max_items is not None:
|
|
112
|
+
prop["maxItems"] = field.max_items
|
|
113
|
+
|
|
114
|
+
return prop
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def list_of_objects_schema(item_fields: Dict[str, Union[str, SimpleField]],
|
|
118
|
+
description: str = "List of objects",
|
|
119
|
+
min_items: Optional[int] = None,
|
|
120
|
+
max_items: Optional[int] = None) -> Dict[str, Any]:
|
|
121
|
+
"""Generate schema for array of objects with enhanced constraints"""
|
|
122
|
+
item_schema = simple_schema(item_fields)
|
|
123
|
+
|
|
124
|
+
array_schema = {
|
|
125
|
+
"type": "array",
|
|
126
|
+
"description": description,
|
|
127
|
+
"items": item_schema
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Add array size constraints
|
|
131
|
+
if min_items is not None:
|
|
132
|
+
array_schema["minItems"] = min_items
|
|
133
|
+
if max_items is not None:
|
|
134
|
+
array_schema["maxItems"] = max_items
|
|
135
|
+
|
|
136
|
+
return array_schema
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def simple_array_schema(item_type: str = 'string', description: str = 'Array of items',
|
|
140
|
+
min_items: Optional[int] = None, max_items: Optional[int] = None,
|
|
141
|
+
format_hint: Optional[str] = None) -> Dict[str, Any]:
|
|
142
|
+
"""Generate schema for simple arrays like [string], [int], [email]"""
|
|
143
|
+
items_schema = {"type": item_type}
|
|
144
|
+
|
|
145
|
+
# Add format for special types
|
|
146
|
+
if format_hint:
|
|
147
|
+
if format_hint == "email":
|
|
148
|
+
items_schema["format"] = "email"
|
|
149
|
+
elif format_hint in ["url", "uri"]:
|
|
150
|
+
items_schema["format"] = "uri"
|
|
151
|
+
elif format_hint == "datetime":
|
|
152
|
+
items_schema["format"] = "date-time"
|
|
153
|
+
elif format_hint == "date":
|
|
154
|
+
items_schema["format"] = "date"
|
|
155
|
+
elif format_hint == "uuid":
|
|
156
|
+
items_schema["format"] = "uuid"
|
|
157
|
+
|
|
158
|
+
array_schema = {
|
|
159
|
+
"type": "array",
|
|
160
|
+
"description": description,
|
|
161
|
+
"items": items_schema
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Add array size constraints
|
|
165
|
+
if min_items is not None:
|
|
166
|
+
array_schema["minItems"] = min_items
|
|
167
|
+
if max_items is not None:
|
|
168
|
+
array_schema["maxItems"] = max_items
|
|
169
|
+
|
|
170
|
+
return array_schema
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def quick_pydantic_model(name: str, fields: Dict[str, Union[str, SimpleField]]) -> Type:
|
|
174
|
+
"""Create Pydantic model from simple field definitions"""
|
|
175
|
+
if not HAS_PYDANTIC:
|
|
176
|
+
raise ImportError("Pydantic is required for quick_pydantic_model. Install with: pip install pydantic")
|
|
177
|
+
|
|
178
|
+
pydantic_fields = {}
|
|
179
|
+
|
|
180
|
+
for field_name, field_def in fields.items():
|
|
181
|
+
if isinstance(field_def, str):
|
|
182
|
+
field_def = SimpleField(field_def)
|
|
183
|
+
|
|
184
|
+
from ..integrations.pydantic import _simple_field_to_pydantic
|
|
185
|
+
python_type, field_info = _simple_field_to_pydantic(field_def)
|
|
186
|
+
pydantic_fields[field_name] = (python_type, field_info)
|
|
187
|
+
|
|
188
|
+
return create_model(name, **pydantic_fields)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _simple_field_to_pydantic(field: SimpleField) -> tuple:
|
|
192
|
+
"""Convert SimpleField to Pydantic field specification"""
|
|
193
|
+
if not HAS_PYDANTIC:
|
|
194
|
+
raise ImportError("Pydantic is required for this function")
|
|
195
|
+
|
|
196
|
+
# Type mapping
|
|
197
|
+
type_mapping = {
|
|
198
|
+
'string': str,
|
|
199
|
+
'integer': int,
|
|
200
|
+
'number': float,
|
|
201
|
+
'boolean': bool
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
python_type = type_mapping.get(field.field_type, str)
|
|
205
|
+
|
|
206
|
+
# Handle union types
|
|
207
|
+
if field.union_types and len(field.union_types) > 1:
|
|
208
|
+
from typing import Union as TypingUnion
|
|
209
|
+
union_python_types = []
|
|
210
|
+
for union_type in field.union_types:
|
|
211
|
+
if union_type == "null":
|
|
212
|
+
union_python_types.append(type(None))
|
|
213
|
+
else:
|
|
214
|
+
union_python_types.append(type_mapping.get(union_type, str))
|
|
215
|
+
python_type = TypingUnion[tuple(union_python_types)]
|
|
216
|
+
|
|
217
|
+
# Handle optional fields
|
|
218
|
+
if not field.required:
|
|
219
|
+
python_type = Optional[python_type]
|
|
220
|
+
|
|
221
|
+
# Build Field arguments
|
|
222
|
+
field_kwargs = {}
|
|
223
|
+
|
|
224
|
+
if field.description:
|
|
225
|
+
field_kwargs['description'] = field.description
|
|
226
|
+
|
|
227
|
+
if field.default is not None:
|
|
228
|
+
field_kwargs['default'] = field.default
|
|
229
|
+
elif not field.required:
|
|
230
|
+
field_kwargs['default'] = None
|
|
231
|
+
|
|
232
|
+
# Numeric constraints
|
|
233
|
+
if field.min_val is not None:
|
|
234
|
+
field_kwargs['ge'] = field.min_val
|
|
235
|
+
if field.max_val is not None:
|
|
236
|
+
field_kwargs['le'] = field.max_val
|
|
237
|
+
|
|
238
|
+
# String constraints
|
|
239
|
+
if field.min_length is not None:
|
|
240
|
+
field_kwargs['min_length'] = field.min_length
|
|
241
|
+
if field.max_length is not None:
|
|
242
|
+
field_kwargs['max_length'] = field.max_length
|
|
243
|
+
|
|
244
|
+
return python_type, Field(**field_kwargs) if field_kwargs else Field()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core field definitions for Simple Schema
|
|
3
|
+
|
|
4
|
+
Contains the SimpleField class and related field utilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Optional, Union
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SimpleField:
|
|
14
|
+
"""Enhanced SimpleField with support for all new features"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, field_type: str, description: str = "", required: bool = True,
|
|
17
|
+
default: Any = None, min_val: Optional[Union[int, float]] = None,
|
|
18
|
+
max_val: Optional[Union[int, float]] = None, min_length: Optional[int] = None,
|
|
19
|
+
max_length: Optional[int] = None, choices: Optional[List[Any]] = None,
|
|
20
|
+
min_items: Optional[int] = None, max_items: Optional[int] = None,
|
|
21
|
+
format_hint: Optional[str] = None, union_types: Optional[List[str]] = None):
|
|
22
|
+
"""
|
|
23
|
+
Initialize a SimpleField with comprehensive validation options.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
field_type: The base type (string, integer, number, boolean)
|
|
27
|
+
description: Human-readable description of the field
|
|
28
|
+
required: Whether the field is required (default: True)
|
|
29
|
+
default: Default value if field is not provided
|
|
30
|
+
min_val: Minimum value for numeric types
|
|
31
|
+
max_val: Maximum value for numeric types
|
|
32
|
+
min_length: Minimum length for string types
|
|
33
|
+
max_length: Maximum length for string types
|
|
34
|
+
choices: List of allowed values (enum)
|
|
35
|
+
min_items: Minimum items for array types
|
|
36
|
+
max_items: Maximum items for array types
|
|
37
|
+
format_hint: Special format hint (email, url, datetime, etc.)
|
|
38
|
+
union_types: List of types for union fields
|
|
39
|
+
"""
|
|
40
|
+
self.field_type = field_type
|
|
41
|
+
self.description = description
|
|
42
|
+
self.required = required
|
|
43
|
+
self.default = default
|
|
44
|
+
self.min_val = min_val
|
|
45
|
+
self.max_val = max_val
|
|
46
|
+
self.min_length = min_length
|
|
47
|
+
self.max_length = max_length
|
|
48
|
+
self.choices = choices
|
|
49
|
+
self.min_items = min_items
|
|
50
|
+
self.max_items = max_items
|
|
51
|
+
self.format_hint = format_hint
|
|
52
|
+
self.union_types = union_types or []
|
|
53
|
+
|
|
54
|
+
def __repr__(self):
|
|
55
|
+
"""String representation of the field"""
|
|
56
|
+
parts = [f"type={self.field_type}"]
|
|
57
|
+
if self.description:
|
|
58
|
+
parts.append(f"desc='{self.description}'")
|
|
59
|
+
if not self.required:
|
|
60
|
+
parts.append("optional")
|
|
61
|
+
if self.choices:
|
|
62
|
+
parts.append(f"choices={self.choices}")
|
|
63
|
+
if self.union_types:
|
|
64
|
+
parts.append(f"union={self.union_types}")
|
|
65
|
+
return f"SimpleField({', '.join(parts)})"
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> dict:
|
|
68
|
+
"""Convert field to dictionary representation"""
|
|
69
|
+
result = {
|
|
70
|
+
'type': self.field_type,
|
|
71
|
+
'required': self.required
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if self.description:
|
|
75
|
+
result['description'] = self.description
|
|
76
|
+
if self.default is not None:
|
|
77
|
+
result['default'] = self.default
|
|
78
|
+
if self.min_val is not None:
|
|
79
|
+
result['min_val'] = self.min_val
|
|
80
|
+
if self.max_val is not None:
|
|
81
|
+
result['max_val'] = self.max_val
|
|
82
|
+
if self.min_length is not None:
|
|
83
|
+
result['min_length'] = self.min_length
|
|
84
|
+
if self.max_length is not None:
|
|
85
|
+
result['max_length'] = self.max_length
|
|
86
|
+
if self.choices:
|
|
87
|
+
result['choices'] = self.choices
|
|
88
|
+
if self.min_items is not None:
|
|
89
|
+
result['min_items'] = self.min_items
|
|
90
|
+
if self.max_items is not None:
|
|
91
|
+
result['max_items'] = self.max_items
|
|
92
|
+
if self.format_hint:
|
|
93
|
+
result['format_hint'] = self.format_hint
|
|
94
|
+
if self.union_types:
|
|
95
|
+
result['union_types'] = self.union_types
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_dict(cls, data: dict) -> 'SimpleField':
|
|
101
|
+
"""Create SimpleField from dictionary representation"""
|
|
102
|
+
return cls(
|
|
103
|
+
field_type=data['type'],
|
|
104
|
+
description=data.get('description', ''),
|
|
105
|
+
required=data.get('required', True),
|
|
106
|
+
default=data.get('default'),
|
|
107
|
+
min_val=data.get('min_val'),
|
|
108
|
+
max_val=data.get('max_val'),
|
|
109
|
+
min_length=data.get('min_length'),
|
|
110
|
+
max_length=data.get('max_length'),
|
|
111
|
+
choices=data.get('choices'),
|
|
112
|
+
min_items=data.get('min_items'),
|
|
113
|
+
max_items=data.get('max_items'),
|
|
114
|
+
format_hint=data.get('format_hint'),
|
|
115
|
+
union_types=data.get('union_types')
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Utility functions for creating common field types
|
|
120
|
+
def create_enhanced_field(field_type: str, **kwargs) -> SimpleField:
|
|
121
|
+
"""Create enhanced SimpleField with all features"""
|
|
122
|
+
return SimpleField(field_type, **kwargs)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def create_special_type_field(special_type: str, required: bool = True, **kwargs) -> SimpleField:
|
|
126
|
+
"""Create field with special type hints"""
|
|
127
|
+
return SimpleField('string', format_hint=special_type, required=required, **kwargs)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def create_enum_field(values: List[str], required: bool = True, **kwargs) -> SimpleField:
|
|
131
|
+
"""Create enum field with specific values"""
|
|
132
|
+
return SimpleField('string', choices=values, required=required, **kwargs)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def create_union_field(types: List[str], required: bool = True, **kwargs) -> SimpleField:
|
|
136
|
+
"""Create union field with multiple types"""
|
|
137
|
+
primary_type = types[0] if types else 'string'
|
|
138
|
+
return SimpleField(primary_type, union_types=types, required=required, **kwargs)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation utilities for Simple Schema
|
|
3
|
+
|
|
4
|
+
Contains functions for validating schemas and field definitions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Union
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from .fields import SimpleField
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Validate a JSON schema generated by Simple Schema.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
schema: The JSON schema to validate
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dictionary with validation results including:
|
|
24
|
+
- valid: Boolean indicating if schema is valid
|
|
25
|
+
- errors: List of error messages
|
|
26
|
+
- warnings: List of warning messages
|
|
27
|
+
- field_count: Number of fields in the schema
|
|
28
|
+
"""
|
|
29
|
+
result = {
|
|
30
|
+
'valid': True,
|
|
31
|
+
'errors': [],
|
|
32
|
+
'warnings': [],
|
|
33
|
+
'field_count': 0,
|
|
34
|
+
'features_used': []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Basic schema structure validation
|
|
39
|
+
if not isinstance(schema, dict):
|
|
40
|
+
result['errors'].append("Schema must be a dictionary")
|
|
41
|
+
result['valid'] = False
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
if 'type' not in schema:
|
|
45
|
+
result['errors'].append("Schema must have a 'type' field")
|
|
46
|
+
result['valid'] = False
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
schema_type = schema['type']
|
|
50
|
+
|
|
51
|
+
if schema_type == 'object':
|
|
52
|
+
result.update(_validate_object_schema(schema))
|
|
53
|
+
elif schema_type == 'array':
|
|
54
|
+
result.update(_validate_array_schema(schema))
|
|
55
|
+
else:
|
|
56
|
+
result['warnings'].append(f"Unusual schema type: {schema_type}")
|
|
57
|
+
|
|
58
|
+
# Check for potential issues
|
|
59
|
+
if result['field_count'] > 20:
|
|
60
|
+
result['warnings'].append("Schema has many fields (>20), consider simplifying")
|
|
61
|
+
|
|
62
|
+
if result['field_count'] == 0:
|
|
63
|
+
result['warnings'].append("Schema has no fields defined")
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
result['errors'].append(f"Validation error: {str(e)}")
|
|
67
|
+
result['valid'] = False
|
|
68
|
+
|
|
69
|
+
# Set overall validity
|
|
70
|
+
result['valid'] = len(result['errors']) == 0
|
|
71
|
+
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _validate_object_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
76
|
+
"""Validate object-type schema"""
|
|
77
|
+
result = {
|
|
78
|
+
'field_count': 0,
|
|
79
|
+
'features_used': [],
|
|
80
|
+
'errors': [],
|
|
81
|
+
'warnings': []
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
properties = schema.get('properties', {})
|
|
85
|
+
required = schema.get('required', [])
|
|
86
|
+
|
|
87
|
+
result['field_count'] = len(properties)
|
|
88
|
+
|
|
89
|
+
# Validate each property
|
|
90
|
+
for field_name, field_schema in properties.items():
|
|
91
|
+
field_result = _validate_field_schema(field_name, field_schema)
|
|
92
|
+
result['errors'].extend(field_result['errors'])
|
|
93
|
+
result['warnings'].extend(field_result['warnings'])
|
|
94
|
+
result['features_used'].extend(field_result['features_used'])
|
|
95
|
+
|
|
96
|
+
# Validate required fields
|
|
97
|
+
for req_field in required:
|
|
98
|
+
if req_field not in properties:
|
|
99
|
+
result['errors'].append(f"Required field '{req_field}' not found in properties")
|
|
100
|
+
|
|
101
|
+
# Remove duplicate features
|
|
102
|
+
result['features_used'] = list(set(result['features_used']))
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _validate_array_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
108
|
+
"""Validate array-type schema"""
|
|
109
|
+
result = {
|
|
110
|
+
'field_count': 0,
|
|
111
|
+
'features_used': ['arrays'],
|
|
112
|
+
'errors': [],
|
|
113
|
+
'warnings': []
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
items = schema.get('items', {})
|
|
117
|
+
|
|
118
|
+
if not items:
|
|
119
|
+
result['errors'].append("Array schema must have 'items' definition")
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
# Validate array constraints
|
|
123
|
+
if 'minItems' in schema:
|
|
124
|
+
result['features_used'].append('array_constraints')
|
|
125
|
+
if 'maxItems' in schema:
|
|
126
|
+
result['features_used'].append('array_constraints')
|
|
127
|
+
|
|
128
|
+
# Validate items schema
|
|
129
|
+
if items.get('type') == 'object':
|
|
130
|
+
items_result = _validate_object_schema(items)
|
|
131
|
+
result['field_count'] = items_result['field_count']
|
|
132
|
+
result['errors'].extend(items_result['errors'])
|
|
133
|
+
result['warnings'].extend(items_result['warnings'])
|
|
134
|
+
result['features_used'].extend(items_result['features_used'])
|
|
135
|
+
else:
|
|
136
|
+
# Simple array items
|
|
137
|
+
field_result = _validate_field_schema('array_item', items)
|
|
138
|
+
result['errors'].extend(field_result['errors'])
|
|
139
|
+
result['warnings'].extend(field_result['warnings'])
|
|
140
|
+
result['features_used'].extend(field_result['features_used'])
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _validate_field_schema(field_name: str, field_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
146
|
+
"""Validate individual field schema"""
|
|
147
|
+
result = {
|
|
148
|
+
'features_used': [],
|
|
149
|
+
'errors': [],
|
|
150
|
+
'warnings': []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Check for union types
|
|
154
|
+
if 'anyOf' in field_schema:
|
|
155
|
+
result['features_used'].append('union_types')
|
|
156
|
+
# Validate each union option
|
|
157
|
+
for i, union_option in enumerate(field_schema['anyOf']):
|
|
158
|
+
if 'type' not in union_option:
|
|
159
|
+
result['errors'].append(f"Union option {i} in field '{field_name}' missing type")
|
|
160
|
+
|
|
161
|
+
# Check for enum
|
|
162
|
+
if 'enum' in field_schema:
|
|
163
|
+
result['features_used'].append('enums')
|
|
164
|
+
enum_values = field_schema['enum']
|
|
165
|
+
if not isinstance(enum_values, list) or len(enum_values) == 0:
|
|
166
|
+
result['errors'].append(f"Field '{field_name}' enum must be non-empty list")
|
|
167
|
+
|
|
168
|
+
# Check for format hints
|
|
169
|
+
if 'format' in field_schema:
|
|
170
|
+
result['features_used'].append('special_types')
|
|
171
|
+
format_value = field_schema['format']
|
|
172
|
+
valid_formats = ['email', 'uri', 'date-time', 'date', 'uuid']
|
|
173
|
+
if format_value not in valid_formats:
|
|
174
|
+
result['warnings'].append(f"Field '{field_name}' uses non-standard format: {format_value}")
|
|
175
|
+
|
|
176
|
+
# Check for constraints
|
|
177
|
+
constraint_fields = ['minimum', 'maximum', 'minLength', 'maxLength', 'minItems', 'maxItems']
|
|
178
|
+
if any(field in field_schema for field in constraint_fields):
|
|
179
|
+
result['features_used'].append('constraints')
|
|
180
|
+
|
|
181
|
+
# Validate constraint values
|
|
182
|
+
if 'minimum' in field_schema and 'maximum' in field_schema:
|
|
183
|
+
if field_schema['minimum'] > field_schema['maximum']:
|
|
184
|
+
result['errors'].append(f"Field '{field_name}' minimum > maximum")
|
|
185
|
+
|
|
186
|
+
if 'minLength' in field_schema and 'maxLength' in field_schema:
|
|
187
|
+
if field_schema['minLength'] > field_schema['maxLength']:
|
|
188
|
+
result['errors'].append(f"Field '{field_name}' minLength > maxLength")
|
|
189
|
+
|
|
190
|
+
if 'minItems' in field_schema and 'maxItems' in field_schema:
|
|
191
|
+
if field_schema['minItems'] > field_schema['maxItems']:
|
|
192
|
+
result['errors'].append(f"Field '{field_name}' minItems > maxItems")
|
|
193
|
+
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def validate_simple_field(field: SimpleField) -> Dict[str, Any]:
|
|
198
|
+
"""
|
|
199
|
+
Validate a SimpleField instance.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
field: The SimpleField to validate
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dictionary with validation results
|
|
206
|
+
"""
|
|
207
|
+
result = {
|
|
208
|
+
'valid': True,
|
|
209
|
+
'errors': [],
|
|
210
|
+
'warnings': []
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Validate field type
|
|
214
|
+
valid_types = ['string', 'integer', 'number', 'boolean']
|
|
215
|
+
if field.field_type not in valid_types:
|
|
216
|
+
result['warnings'].append(f"Unusual field type: {field.field_type}")
|
|
217
|
+
|
|
218
|
+
# Validate constraints
|
|
219
|
+
if field.min_val is not None and field.max_val is not None:
|
|
220
|
+
if field.min_val > field.max_val:
|
|
221
|
+
result['errors'].append("min_val cannot be greater than max_val")
|
|
222
|
+
|
|
223
|
+
if field.min_length is not None and field.max_length is not None:
|
|
224
|
+
if field.min_length > field.max_length:
|
|
225
|
+
result['errors'].append("min_length cannot be greater than max_length")
|
|
226
|
+
|
|
227
|
+
if field.min_items is not None and field.max_items is not None:
|
|
228
|
+
if field.min_items > field.max_items:
|
|
229
|
+
result['errors'].append("min_items cannot be greater than max_items")
|
|
230
|
+
|
|
231
|
+
# Validate choices
|
|
232
|
+
if field.choices and len(field.choices) == 0:
|
|
233
|
+
result['errors'].append("choices list cannot be empty")
|
|
234
|
+
|
|
235
|
+
# Validate union types
|
|
236
|
+
if field.union_types and len(field.union_types) < 2:
|
|
237
|
+
result['warnings'].append("union_types should have at least 2 types")
|
|
238
|
+
|
|
239
|
+
# Set overall validity
|
|
240
|
+
result['valid'] = len(result['errors']) == 0
|
|
241
|
+
|
|
242
|
+
return result
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Examples module for String Schema
|
|
3
|
+
|
|
4
|
+
Contains built-in schema presets and common patterns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .presets import (
|
|
8
|
+
user_schema,
|
|
9
|
+
product_schema,
|
|
10
|
+
contact_schema,
|
|
11
|
+
article_schema,
|
|
12
|
+
event_schema,
|
|
13
|
+
get_examples
|
|
14
|
+
)
|
|
15
|
+
from .recipes import (
|
|
16
|
+
create_list_schema,
|
|
17
|
+
create_nested_schema,
|
|
18
|
+
create_enum_schema,
|
|
19
|
+
create_union_schema
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Presets
|
|
24
|
+
"user_schema",
|
|
25
|
+
"product_schema",
|
|
26
|
+
"contact_schema",
|
|
27
|
+
"article_schema",
|
|
28
|
+
"event_schema",
|
|
29
|
+
"get_examples",
|
|
30
|
+
|
|
31
|
+
# Recipes
|
|
32
|
+
"create_list_schema",
|
|
33
|
+
"create_nested_schema",
|
|
34
|
+
"create_enum_schema",
|
|
35
|
+
"create_union_schema"
|
|
36
|
+
]
|