feat: project compact and verbose workflow schemas
This commit is contained in:
@@ -99,3 +99,111 @@ def schema_catalog() -> SchemaCatalog:
|
||||
raise RuntimeError(f"conflicting workflow schema definition: {name}")
|
||||
definitions.setdefault(name, deepcopy(value))
|
||||
return SchemaCatalog(roots=roots, definitions=definitions, aliases=dict(ALIASES))
|
||||
|
||||
|
||||
PRESENTATION_KEYS = (
|
||||
"type",
|
||||
"description",
|
||||
"default",
|
||||
"enum",
|
||||
"const",
|
||||
"minimum",
|
||||
"maximum",
|
||||
"exclusiveMinimum",
|
||||
"exclusiveMaximum",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"minItems",
|
||||
"maxItems",
|
||||
)
|
||||
|
||||
|
||||
def _ref_name(ref: str) -> str:
|
||||
prefix = "#/$defs/"
|
||||
if not ref.startswith(prefix):
|
||||
return ref
|
||||
return ref.removeprefix(prefix)
|
||||
|
||||
|
||||
def _compact_node(schema: object, related: set[str]) -> object:
|
||||
if not isinstance(schema, dict):
|
||||
return deepcopy(schema)
|
||||
ref = schema.get("$ref")
|
||||
if isinstance(ref, str):
|
||||
name = _ref_name(ref)
|
||||
if name != ref:
|
||||
related.add(name)
|
||||
return name
|
||||
|
||||
result: JsonObject = {}
|
||||
for key in PRESENTATION_KEYS:
|
||||
if key in schema:
|
||||
result[key] = deepcopy(schema[key])
|
||||
required = schema.get("required")
|
||||
if isinstance(required, list):
|
||||
result["required"] = deepcopy(required)
|
||||
properties = schema.get("properties")
|
||||
if isinstance(properties, dict):
|
||||
result["properties"] = {
|
||||
name: _compact_node(value, related)
|
||||
for name, value in properties.items()
|
||||
}
|
||||
if "items" in schema:
|
||||
result["items"] = _compact_node(schema["items"], related)
|
||||
for source_key in ("oneOf", "anyOf"):
|
||||
branches = schema.get(source_key)
|
||||
if not isinstance(branches, list):
|
||||
continue
|
||||
result["one_of"] = [_compact_node(branch, related) for branch in branches]
|
||||
break
|
||||
discriminator = schema.get("discriminator")
|
||||
if isinstance(discriminator, dict) and isinstance(discriminator.get("propertyName"), str):
|
||||
result["discriminator"] = discriminator["propertyName"]
|
||||
return result
|
||||
|
||||
|
||||
def compact_schema_outline(name: str) -> JsonObject:
|
||||
catalog = schema_catalog()
|
||||
canonical = catalog.resolve(name)
|
||||
schema = catalog.schema(canonical)
|
||||
related: set[str] = set()
|
||||
body = _compact_node(schema, related)
|
||||
if not isinstance(body, dict):
|
||||
body = {"schema": body}
|
||||
return {
|
||||
"name": canonical,
|
||||
"kind": "schema_outline",
|
||||
**body,
|
||||
"related": sorted(related - {canonical}),
|
||||
"full_schema_command": f"wf schema {name} --verbose",
|
||||
}
|
||||
|
||||
|
||||
def verbose_schema_document(name: str) -> JsonObject:
|
||||
catalog = schema_catalog()
|
||||
canonical = catalog.resolve(name)
|
||||
if canonical in catalog.roots:
|
||||
document = catalog.schema(canonical)
|
||||
document.setdefault("$schema", SCHEMA_DIALECT)
|
||||
else:
|
||||
document = {
|
||||
"$schema": SCHEMA_DIALECT,
|
||||
"$ref": f"#/$defs/{canonical}",
|
||||
"$defs": deepcopy(catalog.definitions),
|
||||
}
|
||||
Draft202012Validator.check_schema(document)
|
||||
return document
|
||||
|
||||
|
||||
def schema_catalog_payload() -> JsonObject:
|
||||
return {
|
||||
"schemas": [
|
||||
{
|
||||
"name": entry.name,
|
||||
"aliases": list(entry.aliases),
|
||||
"kind": entry.kind,
|
||||
"description": entry.description,
|
||||
}
|
||||
for entry in schema_catalog().entries()
|
||||
]
|
||||
}
|
||||
|
||||
@@ -123,3 +123,24 @@ def test_schema_catalog_resolves_aliases_and_components() -> None:
|
||||
assert catalog.resolve("NodeUse") == "NodeUse"
|
||||
assert catalog.entry("draft").kind == "root"
|
||||
assert catalog.entry("NodeUse").kind == "definition"
|
||||
|
||||
|
||||
from wf_cli.schema_catalog import compact_schema_outline, verbose_schema_document
|
||||
|
||||
|
||||
def test_compact_outline_replaces_local_refs_with_names() -> None:
|
||||
payload = compact_schema_outline("NodeUse")
|
||||
|
||||
assert payload["properties"]["input"]["items"]["one_of"] == [
|
||||
"InputPathBinding",
|
||||
"InputValueBinding",
|
||||
]
|
||||
assert "$ref" not in json.dumps(payload)
|
||||
|
||||
|
||||
def test_verbose_component_uses_generated_definitions() -> None:
|
||||
payload = verbose_schema_document("NodeUse")
|
||||
|
||||
assert payload["$schema"] == Draft202012Validator.META_SCHEMA["$id"]
|
||||
assert payload["$ref"] == "#/$defs/NodeUse"
|
||||
assert "InputPathBinding" in payload["$defs"]
|
||||
|
||||
Reference in New Issue
Block a user