fix(core): parse picoschema modifier descriptions (#796)

Signed-off-by: Drew Cain <groksrc@gmail.com>
Signed-off-by: phernandez <paul@basicmachines.co>
Co-authored-by: phernandez <paul@basicmachines.co>
This commit is contained in:
Drew Cain
2026-05-06 12:21:35 -05:00
committed by GitHub
parent b08659e228
commit 3415fd1014
4 changed files with 309 additions and 24 deletions
+67 -20
View File
@@ -8,7 +8,9 @@ Syntax reference:
field: type, description # required field
field?: type, description # optional field
field(array): type # array of values
field(array, description): type # array with description
field?(enum): [val1, val2] # enumeration
field?(enum, description): [val1, val2] # enum with description
field?(object): # nested object
sub_field: type
EntityName as type (capitalized) # entity reference
@@ -58,46 +60,88 @@ class SchemaDefinition:
# with an uppercase letter is treated as an entity reference.
SCALAR_TYPES = frozenset({"string", "integer", "number", "boolean", "any"})
MODIFIER_TYPES = frozenset({"array", "enum", "object"})
# --- Field Name Parsing ---
def _parse_field_key(key: str) -> tuple[str, bool, bool, bool, bool]:
def _parse_field_key_parts(key: str) -> tuple[str, bool, bool, bool, bool, str | None]:
"""Parse a Picoschema field key into its components.
Returns (name, required, is_array, is_enum, is_object).
The key format is: name[?][(array|enum|object)]
Returns (name, required, is_array, is_enum, is_object, description).
The key format is: name[?][(array|enum|object[, description])]
Examples:
"name" -> ("name", True, False, False)
"role?" -> ("role", False, False, False)
"tags?(array)" -> ("tags", False, True, False)
"status?(enum)" -> ("status", False, False, True)
"metadata?(object)" -> ("metadata", False, False, False) + children
"name" -> ("name", True, False, False, False, None)
"role?" -> ("role", False, False, False, False, None)
"tags?(array)" -> ("tags", False, True, False, False, None)
"tags?(array, labels)" -> ("tags", False, True, False, False, "labels")
"status?(enum)" -> ("status", False, False, True, False, None)
"metadata?(object)" -> ("metadata", False, False, False, True, None)
"""
required = True
is_array = False
is_enum = False
is_object = False
description = None
# Check for modifier suffix: (array), (enum), (object)
if key.endswith("(array)"):
key, modifier, description = _split_modifier_suffix(key)
if modifier == "array":
is_array = True
key = key[: -len("(array)")]
elif key.endswith("(enum)"):
elif modifier == "enum":
is_enum = True
key = key[: -len("(enum)")]
elif key.endswith("(object)"):
elif modifier == "object":
is_object = True
key = key[: -len("(object)")]
# Check for optional marker
if key.endswith("?"):
required = False
key = key[:-1]
return key, required, is_array, is_enum, is_object
return key.strip(), required, is_array, is_enum, is_object, description
def _parse_field_key(key: str) -> tuple[str, bool, bool, bool, bool]:
"""Parse a Picoschema field key, discarding any modifier description."""
name, required, is_array, is_enum, is_object, _description = _parse_field_key_parts(key)
return name, required, is_array, is_enum, is_object
def _split_modifier_suffix(key: str) -> tuple[str, str | None, str | None]:
"""Split a trailing picoschema modifier from a field key."""
stripped_key = key.rstrip()
if not stripped_key.endswith(")"):
return key, None, None
# Trigger: field names and modifier descriptions may both contain parentheses
# Why: only the parenthesis paired with the final suffix can introduce a modifier
# Outcome: preserves names like "risk(score)" and descriptions like "labels (freeform)"
open_paren_index = -1
depth = 0
for index in range(len(stripped_key) - 1, -1, -1):
char = stripped_key[index]
if char == ")":
depth += 1
elif char == "(":
depth -= 1
if depth == 0:
open_paren_index = index
break
if open_paren_index == -1:
return key, None, None
modifier_text = stripped_key[open_paren_index + 1 : -1].strip()
modifier, separator, description = modifier_text.partition(",")
modifier = modifier.strip()
if modifier not in MODIFIER_TYPES:
return key, None, None
key_without_modifier = stripped_key[:open_paren_index].rstrip()
parsed_description = description.strip() if separator else None
return key_without_modifier, modifier, parsed_description or None
def _parse_type_and_description(value: str) -> tuple[str, str | None]:
@@ -170,7 +214,7 @@ def parse_picoschema(yaml_dict: dict) -> list[SchemaField]:
fields: list[SchemaField] = []
for key, value in yaml_dict.items():
name, required, is_array, is_enum, is_object = _parse_field_key(key)
name, required, is_array, is_enum, is_object, key_description = _parse_field_key_parts(key)
# --- Enum fields ---
# Trigger: value is a list or a string containing bracketed enum values
@@ -179,11 +223,12 @@ def parse_picoschema(yaml_dict: dict) -> list[SchemaField]:
# in YAML to avoid parse errors)
# Outcome: SchemaField with is_enum=True and enum_values populated
if is_enum:
description = None
description = key_description
if isinstance(value, list):
enum_values = [str(v) for v in value]
else:
enum_values, description = _parse_enum_string(str(value))
enum_values, value_description = _parse_enum_string(str(value))
description = description or value_description
fields.append(
SchemaField(
name=name,
@@ -207,13 +252,15 @@ def parse_picoschema(yaml_dict: dict) -> list[SchemaField]:
name=name,
type="object",
required=required,
description=key_description,
children=children,
)
)
continue
# --- Scalar and entity ref fields ---
type_str, description = _parse_type_and_description(str(value))
type_str, value_description = _parse_type_and_description(str(value))
description = key_description or value_description
is_entity_ref = _is_entity_ref_type(type_str)
fields.append(