Files
cadSet/SimpleCADAPI/tools/scene_contract_fixtures/schema_field_matrix.py

331 lines
12 KiB
Python

"""Schema-grounded structural field matrices for all contract artifacts."""
from __future__ import annotations
import json
import re
from copy import deepcopy
from pathlib import Path
from typing import Any, Mapping
from jsonschema import Draft202012Validator
Json = Any
SCHEMAS = {
"scene": "scene-1.0.schema.json",
"entities": "entities-1.0.schema.json",
"presentation": "presentation-1.0.schema.json",
"connector_binding": "connector-binding-1.0.schema.json",
"normalized_product": "normalized-product-1.schema.json",
}
def _resolve(root: Mapping[str, Any], schema: Mapping[str, Any]) -> Mapping[str, Any]:
reference = schema.get("$ref")
if not isinstance(reference, str) or not reference.startswith("#/"):
return schema
current: Any = root
for part in reference[2:].split("/"):
current = current[part.replace("~1", "/").replace("~0", "~")]
return current
def _wrapper(root: Mapping[str, Any], pointer: str) -> dict[str, Any]:
if pointer == "#":
return deepcopy(dict(root))
return {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": pointer,
"$defs": deepcopy(root.get("$defs", {})),
}
def _string_example(schema: Mapping[str, Any]) -> str:
pattern = str(schema.get("pattern", ""))
if pattern == "^sha256:[0-9a-f]{64}$":
return "sha256:" + "0" * 64
if pattern.startswith("^appearance/evaluated/"):
return "appearance/evaluated/" + "0" * 64
if pattern.startswith("^entity/"):
return "entity/face/0"
for candidate in ("a", "x", "root", "entity/face/0"):
if not pattern or re.fullmatch(pattern, candidate):
return candidate
raise AssertionError(f"no structural string example for pattern {pattern!r}")
def _accepts_null(root: Mapping[str, Any], schema: Mapping[str, Any]) -> bool:
wrapper = deepcopy(dict(schema))
wrapper["$defs"] = deepcopy(root.get("$defs", {}))
return Draft202012Validator(wrapper).is_valid(None)
def _nonnull_example(root: Mapping[str, Any], schema: Mapping[str, Any]) -> Json:
resolved = _resolve(root, schema)
if "oneOf" in resolved:
for branch in resolved["oneOf"]:
if not _accepts_null(root, branch):
return _example(root, branch, prefer_nonnull=True)
types = resolved.get("type")
if isinstance(types, list):
draft = dict(resolved)
draft["type"] = next(item for item in types if item != "null")
return _example(root, draft, prefer_nonnull=True)
return _example(root, resolved, prefer_nonnull=True)
def _condition_matches(
root: Mapping[str, Any], condition: Mapping[str, Any], value: Json
) -> bool:
schema = deepcopy(dict(condition))
schema["$defs"] = deepcopy(root.get("$defs", {}))
return Draft202012Validator(schema).is_valid(value)
def _example(
root: Mapping[str, Any],
schema: Mapping[str, Any],
*,
overrides: Mapping[str, Json] | None = None,
prefer_nonnull: bool = False,
) -> Json:
resolved = _resolve(root, schema)
if "const" in resolved:
return deepcopy(resolved["const"])
if "enum" in resolved:
return deepcopy(resolved["enum"][0])
for union_key in ("oneOf", "anyOf"):
if union_key in resolved and not (
resolved.get("type") == "object" or "properties" in resolved
):
branches = resolved[union_key]
if prefer_nonnull:
branches = sorted(
branches,
key=lambda branch: _accepts_null(root, branch),
)
return _example(root, branches[0], prefer_nonnull=prefer_nonnull)
types = resolved.get("type")
if isinstance(types, list):
if "null" in types and not prefer_nonnull:
return None
types = next(item for item in types if item != "null")
if types == "object" or "properties" in resolved:
properties = resolved.get("properties", {})
result = {
field: _example(root, properties.get(field, {}))
for field in resolved.get("required", [])
}
if overrides:
result.update(deepcopy(dict(overrides)))
for branch in resolved.get("anyOf", []):
branch_required = branch.get("required", [])
if branch_required:
for field in branch_required:
result.setdefault(field, _example(root, properties.get(field, {})))
break
for condition in resolved.get("allOf", []):
then = condition.get("then")
if isinstance(then, dict) and _condition_matches(
root, condition.get("if", {}), result
):
for field in then.get("required", []):
result.setdefault(field, _example(root, properties.get(field, {})))
return result
if types == "array" or "prefixItems" in resolved:
result = [_example(root, item) for item in resolved.get("prefixItems", [])]
minimum = resolved.get("minItems", 0)
while len(result) < minimum:
result.append(_example(root, resolved.get("items", {})))
return result
if types == "string":
return _string_example(resolved)
if types == "integer":
return int(resolved.get("minimum", 0))
if types == "number":
if "exclusiveMinimum" in resolved:
return resolved["exclusiveMinimum"] + 1
return resolved.get("minimum", 0)
if types == "boolean":
return False
if types == "null":
return None
return {}
def _closed_records(schema: Mapping[str, Any]) -> list[tuple[str, Mapping[str, Any]]]:
result: dict[str, Mapping[str, Any]] = {}
if schema.get("type") == "object" and schema.get("additionalProperties") is False:
result["#"] = schema
def visit(value: Any, pointer: str) -> None:
if not isinstance(value, dict):
return
if value.get("type") == "object" and value.get("additionalProperties") is False:
result[pointer] = value
for key in ("properties", "items", "oneOf", "anyOf", "allOf"):
child = value.get(key)
if isinstance(child, dict):
for name, nested in child.items():
visit(nested, f"{pointer}/{key}/{name}")
elif isinstance(child, list):
for index, nested in enumerate(child):
visit(nested, f"{pointer}/{key}/{index}")
for name, definition in schema.get("$defs", {}).items():
visit(definition, f"#/$defs/{name}")
return list(result.items())
def _mutate(base: Json, operation: str, field: str, value: Json = None) -> Json:
result = deepcopy(base)
if operation == "delete":
del result[field]
else:
result[field] = deepcopy(value)
return result
def _record_matrix(
artifact: str,
root: Mapping[str, Any],
pointer: str,
schema: Mapping[str, Any],
*,
overrides: Mapping[str, Json] | None = None,
variant: str = "base",
) -> dict[str, Any]:
validator = Draft202012Validator(_wrapper(root, pointer))
base = _example(root, schema, overrides=overrides)
if not validator.is_valid(base):
errors = [error.message for error in validator.iter_errors(base)]
raise AssertionError(
f"invalid generated {artifact} {pointer} {variant}: {errors}"
)
properties = schema.get("properties", {})
cases: list[dict[str, Any]] = [
{"mutations": [], "name": "base_valid", "valid": True}
]
for field in base:
cases.append(
{
"mutations": [{"operation": "delete", "path": [field]}],
"name": f"missing_{field}",
"valid": validator.is_valid(_mutate(base, "delete", field)),
}
)
cases.append(
{
"mutations": [
{"operation": "set", "path": ["unknown"], "value": True}
],
"name": "unknown_field",
"valid": validator.is_valid(_mutate(base, "set", "unknown", True)),
}
)
optional = [field for field in properties if field not in base]
for field in optional:
present = _example(root, properties[field])
cases.append(
{
"mutations": [
{"operation": "set", "path": [field], "value": present}
],
"name": f"optional_{field}_present",
"valid": validator.is_valid(_mutate(base, "set", field, present)),
}
)
if optional:
all_present = deepcopy(base)
mutations = []
for field in optional:
present = _example(root, properties[field])
all_present[field] = present
mutations.append(
{"operation": "set", "path": [field], "value": present}
)
cases.append(
{
"mutations": mutations,
"name": "optional_fields_all_present",
"valid": validator.is_valid(all_present),
}
)
for field, field_schema in properties.items():
if field in base and base[field] is None and _accepts_null(root, field_schema):
nonnull = _nonnull_example(root, field_schema)
cases.append(
{
"mutations": [
{"operation": "set", "path": [field], "value": nonnull}
],
"name": f"nullable_{field}_nonnull",
"valid": validator.is_valid(
_mutate(base, "set", field, nonnull)
),
}
)
return {
"artifact": artifact,
"base": base,
"cases": cases,
"schema_pointer": pointer,
"variant": variant,
}
def build_schema_field_matrices(schema_dir: Path) -> list[dict[str, Any]]:
"""Build declarative matrices for every closed object and discriminator."""
matrices: list[dict[str, Any]] = []
for artifact, filename in SCHEMAS.items():
root = json.loads(schema_dir.joinpath(filename).read_text(encoding="utf-8"))
for pointer, schema in _closed_records(root):
discriminator = next(
(
(field, field_schema["enum"])
for field, field_schema in schema.get("properties", {}).items()
if isinstance(field_schema, dict)
and len(field_schema.get("enum", [])) > 1
),
None,
)
variants: list[tuple[str, Mapping[str, Json] | None]] = [("base", None)]
if discriminator is not None:
field, values = discriminator
variants = [
(f"{field}={value}", {field: value}) for value in values
]
if artifact == "normalized_product" and pointer == "#/$defs/connector":
variants = []
for anchor_name in (
"geometryAnchor",
"placementAnchor",
"forwardedAnchor",
):
anchor = _example(root, root["$defs"][anchor_name])
variants.append(
(f"anchor={anchor['anchor_kind']}", {"anchor": anchor})
)
for variant, overrides in variants:
matrices.append(
_record_matrix(
artifact,
root,
pointer,
schema,
overrides=overrides,
variant=variant,
)
)
identities = [
(matrix["artifact"], matrix["schema_pointer"], matrix["variant"])
for matrix in matrices
]
if len(identities) != len(set(identities)):
raise AssertionError("duplicate schema field matrix identity")
return matrices