"""Strict finite-choice schema validation; no ignored JSON Schema constraints.""" import json from dataclasses import dataclass import jsonschema from .config import Limits @dataclass(frozen=True) class Field: name: str description: str values: tuple def validate_schema(schema, limits=None): limits = limits or Limits() if not isinstance(schema, dict): raise ValueError("schema must be an object") if len(json.dumps(schema, ensure_ascii=False)) > limits.max_schema_chars: raise ValueError("schema exceeds character budget") jsonschema.Draft202012Validator.check_schema(schema) if set(schema) - {"type", "properties", "required", "additionalProperties"}: raise ValueError("unsupported object constraints") if schema.get("type") != "object" or schema.get("additionalProperties") is not False: raise ValueError("only closed flat object schemas are supported") properties = schema.get("properties", {}) if not properties or set(schema.get("required", [])) != set(properties): raise ValueError("all properties must be required") if len(properties) > limits.max_fields: raise ValueError("too many fields") fields = [] for name, spec in properties.items(): if not isinstance(spec, dict) or set(spec) - {"type", "enum", "description"}: raise ValueError("unsupported field constraints") if spec.get("type") == "boolean" and "enum" not in spec: values = (True, False) elif spec.get("type") == "string" and isinstance(spec.get("enum"), list): values = tuple(spec["enum"]) if ( not values or any(type(v) is not str for v in values) or len(set(values)) != len(values) ): raise ValueError("string enums must be nonempty and unique") else: raise ValueError("fields must be booleans or string enums") fields.append(Field(name, spec.get("description", ""), values)) if sum(len(f.values) for f in fields) > limits.max_candidates: raise ValueError("too many candidates") return tuple(fields) def evaluate(text, schema, expected): result = { "syntax_valid": False, "schema_compliant": False, "field_correct": 0, "field_total": len(expected), "exact_match": False, } try: parsed = json.loads(text) result["syntax_valid"] = True result["schema_compliant"] = jsonschema.Draft202012Validator(schema).is_valid(parsed) if isinstance(parsed, dict): result["field_correct"] = sum( k in parsed and type(parsed[k]) is type(v) and parsed[k] == v for k, v in expected.items() ) result["exact_match"] = result["schema_compliant"] and result["field_correct"] == len( expected ) except (ValueError, TypeError): pass return result