"""Shared validation helpers for dashboard handlers.""" import json import re from http.server import BaseHTTPRequestHandler _SLUG_RE = re.compile(r'^[a-z0-9][a-z0-9\-_]{1,38}[a-z0-9]$') def validate_slug(slug: str) -> str | None: """Returns error message or None if valid.""" if not slug: return "slug required" if not _SLUG_RE.match(slug): return "slug must be 3-40 chars, lowercase alphanumeric + hyphens/underscores" return None def validate_description(desc: str) -> str | None: """Returns error message or None if valid. Min 10 chars, max 500.""" if not desc or len(desc.strip()) < 10: return "description must be at least 10 characters" if len(desc) > 500: return "description must be at most 500 characters" return None def parse_json_body(handler: BaseHTTPRequestHandler) -> dict | None: """Parse JSON body from request. Returns None on failure (sends 400).""" try: length = int(handler.headers.get('Content-Length', '0') or '0') except (TypeError, ValueError): length = 0 def _send_error(msg: str) -> None: sender = getattr(handler, 'send_json', None) if callable(sender): sender({'error': msg}, 400) return body = json.dumps({'error': msg}).encode() handler.send_response(400) handler.send_header('Content-Type', 'application/json') handler.send_header('Content-Length', str(len(body))) handler.end_headers() handler.wfile.write(body) if length <= 0: _send_error('empty body') return None try: raw = handler.rfile.read(length) return json.loads(raw.decode('utf-8')) except (ValueError, json.JSONDecodeError, UnicodeDecodeError): _send_error('invalid JSON body') return None