|
29 | 29 | IndexType, |
30 | 30 | ) |
31 | 31 |
|
| 32 | +import json |
| 33 | + |
32 | 34 | from redisvl.exceptions import SchemaValidationError |
33 | 35 | from redisvl.redis.utils import convert_bytes |
34 | 36 | from redisvl.schema import IndexSchema |
@@ -200,6 +202,80 @@ def _get_keys( |
200 | 202 | generated_keys.append(key) |
201 | 203 | return generated_keys |
202 | 204 |
|
| 205 | + def _create_readable_validation_error_message( |
| 206 | + self, validation_error: ValidationError, obj_index: int, obj: Dict[str, Any] |
| 207 | + ) -> str: |
| 208 | + """ |
| 209 | + Create a human-readable error message from a Pydantic ValidationError. |
| 210 | +
|
| 211 | + Args: |
| 212 | + validation_error: The Pydantic ValidationError |
| 213 | + obj_index: The index of the object that failed validation |
| 214 | + obj: The object that failed validation |
| 215 | +
|
| 216 | + Returns: |
| 217 | + A detailed, actionable error message |
| 218 | + """ |
| 219 | + error_details = [] |
| 220 | + |
| 221 | + for error in validation_error.errors(): |
| 222 | + field_name = ".".join(str(loc) for loc in error["loc"]) |
| 223 | + error_type = error["type"] |
| 224 | + error_msg = error["msg"] |
| 225 | + input_value = error.get("input", "N/A") |
| 226 | + |
| 227 | + # Create a more descriptive error message based on error type |
| 228 | + if error_type == "bytes_type": |
| 229 | + if isinstance(input_value, bool): |
| 230 | + suggestion = ( |
| 231 | + f"Field '{field_name}' expects bytes (vector data), but got boolean value '{input_value}'. " |
| 232 | + f"If this should be a vector field, provide a list of numbers or bytes. " |
| 233 | + f"If this should be a different field type, check your schema definition." |
| 234 | + ) |
| 235 | + else: |
| 236 | + suggestion = ( |
| 237 | + f"Field '{field_name}' expects bytes (vector data), but got {type(input_value).__name__} value '{input_value}'. " |
| 238 | + f"For vector fields, provide a list of numbers or bytes." |
| 239 | + ) |
| 240 | + elif error_type == "bool_type": |
| 241 | + suggestion = ( |
| 242 | + f"Field '{field_name}' cannot be boolean. Got '{input_value}' of type {type(input_value).__name__}. " |
| 243 | + f"Provide a valid numeric value instead." |
| 244 | + ) |
| 245 | + elif error_type == "string_type": |
| 246 | + suggestion = ( |
| 247 | + f"Field '{field_name}' expects a string, but got {type(input_value).__name__} value '{input_value}'. " |
| 248 | + f"Convert the value to a string or check your data types." |
| 249 | + ) |
| 250 | + elif error_type == "list_type": |
| 251 | + suggestion = ( |
| 252 | + f"Field '{field_name}' expects a list (for vector data), but got {type(input_value).__name__} value '{input_value}'. " |
| 253 | + f"Provide the vector as a list of numbers." |
| 254 | + ) |
| 255 | + elif "dimensions" in error_msg.lower(): |
| 256 | + suggestion = ( |
| 257 | + f"Vector field '{field_name}' has incorrect dimensions. {error_msg}" |
| 258 | + ) |
| 259 | + elif "range" in error_msg.lower(): |
| 260 | + suggestion = f"Vector field '{field_name}' has values outside the allowed range. {error_msg}" |
| 261 | + else: |
| 262 | + suggestion = f"Field '{field_name}': {error_msg}" |
| 263 | + |
| 264 | + error_details.append(f" • {suggestion}") |
| 265 | + |
| 266 | + # Create the final error message |
| 267 | + if len(error_details) == 1: |
| 268 | + detail_msg = error_details[0].strip(" • ") |
| 269 | + else: |
| 270 | + detail_msg = "Multiple validation errors:\n" + "\n".join(error_details) |
| 271 | + |
| 272 | + return ( |
| 273 | + f"Schema validation failed for object at index {obj_index}. {detail_msg}\n" |
| 274 | + f"Object data: {json.dumps(obj, default=str, indent=2)[:200]}{'...' if len(str(obj)) > 200 else ''}\n" |
| 275 | + f"Hint: Check that your data types match the schema field definitions. " |
| 276 | + f"Use index.schema.fields to view expected field types." |
| 277 | + ) |
| 278 | + |
203 | 279 | def _preprocess_and_validate_objects( |
204 | 280 | self, |
205 | 281 | objects: Iterable[Any], |
@@ -248,8 +324,11 @@ def _preprocess_and_validate_objects( |
248 | 324 | prepared_objects.append((key, processed_obj)) |
249 | 325 |
|
250 | 326 | except ValidationError as e: |
251 | | - # Convert Pydantic ValidationError to SchemaValidationError with index context |
252 | | - raise SchemaValidationError(str(e), index=i) from e |
| 327 | + # Create detailed, readable error message |
| 328 | + detailed_message = self._create_readable_validation_error_message( |
| 329 | + e, i, obj |
| 330 | + ) |
| 331 | + raise SchemaValidationError(detailed_message) from e |
253 | 332 | except Exception as e: |
254 | 333 | # Capture other exceptions with context |
255 | 334 | object_id = f"at index {i}" |
|
0 commit comments