Spaces:
Sleeping
Sleeping
File size: 4,821 Bytes
72de9a9 | 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 150 151 | """
Field validation helpers used by the graders.
Each validator returns True if the value matches the expected type,
False otherwise. These are intentionally simple and deterministic.
"""
import re
from typing import Any, Dict, List, Tuple
EMAIL_RE = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
PHONE_RE = re.compile(r"^\+?[1-9]\d{6,14}$")
URL_RE = re.compile(r"^https?://[^\s]+$")
ISO_DATETIME_RE = re.compile(
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$"
)
def validate_email(value: Any) -> bool:
return isinstance(value, str) and bool(EMAIL_RE.match(value))
def validate_phone(value: Any) -> bool:
if not isinstance(value, str):
return False
cleaned = value.replace(" ", "").replace("-", "")
return bool(PHONE_RE.match(cleaned))
def validate_url(value: Any) -> bool:
return isinstance(value, str) and bool(URL_RE.match(value))
def validate_datetime(value: Any) -> bool:
return isinstance(value, str) and bool(ISO_DATETIME_RE.match(value))
def validate_enum(value: Any, allowed_values: List[str]) -> bool:
return isinstance(value, str) and value in allowed_values
def validate_field_type(value: Any, expected_type: str) -> bool:
"""Check if a value matches the expected type string from the spec.
Supported types: string, integer, float, boolean, email, datetime,
url, phone, enum:val1,val2, object, array.
"""
if value is None:
return False
if expected_type == "string":
return isinstance(value, str)
elif expected_type == "integer":
return isinstance(value, int) and not isinstance(value, bool)
elif expected_type == "float":
return isinstance(value, (int, float)) and not isinstance(value, bool)
elif expected_type == "boolean":
return isinstance(value, bool)
elif expected_type == "email":
return validate_email(value)
elif expected_type == "datetime":
return validate_datetime(value)
elif expected_type == "url":
return validate_url(value)
elif expected_type == "phone":
return validate_phone(value)
elif expected_type.startswith("enum:"):
allowed = expected_type.split(":", 1)[1].split(",")
return validate_enum(value, allowed)
elif expected_type == "object":
return isinstance(value, dict)
elif expected_type == "array":
return isinstance(value, list)
else:
# Unknown type, accept anything non-None
return True
def validate_request_against_spec(
request: Dict[str, Any],
spec: Dict[str, Any],
) -> Tuple[float, str]:
"""Validate a request body against its spec.
Returns (score, feedback_string) where score is 0.0 to 1.0
based on how many checks pass.
"""
checks = []
total = 0
passed = 0
# Check required fields are present and non-null
for field in spec["required_fields"]:
total += 1
if field in request and request[field] is not None:
passed += 1
checks.append(f" {field}: PRESENT")
else:
checks.append(f" {field}: MISSING")
# Check field types for fields that are present
for field, expected_type in spec["field_types"].items():
if field not in request or request[field] is None:
continue
total += 1
if validate_field_type(request[field], expected_type):
passed += 1
checks.append(f" {field} type: VALID ({expected_type})")
else:
checks.append(f" {field} type: INVALID (expected {expected_type})")
# Check no extra unknown fields
all_known = set(spec["required_fields"]) | set(spec.get("optional_fields", []))
for field in request:
if field not in all_known:
total += 1
checks.append(f" {field}: UNKNOWN FIELD (not in spec)")
score = passed / total if total > 0 else 0.0
feedback = f"Validation: {passed}/{total} checks passed.\n" + "\n".join(checks)
return round(score, 4), feedback
def validate_headers_against_spec(
headers: Dict[str, str],
spec: Dict[str, Any],
) -> Tuple[float, str]:
"""Validate request headers against the spec's required_headers.
Returns (score, feedback_string).
"""
required = spec.get("required_headers", {})
if not required:
return 1.0, "No required headers."
total = len(required)
passed = 0
checks = []
for header_name in required:
if header_name in headers:
passed += 1
checks.append(f" {header_name}: PRESENT")
else:
checks.append(f" {header_name}: MISSING")
score = passed / total if total > 0 else 1.0
feedback = f"Headers: {passed}/{total} present.\n" + "\n".join(checks)
return round(score, 4), feedback
|