| """Minimal output-schema validation for AI generators. |
| |
| A spec is a dict: field name -> rule dict with keys: |
| type: "str" | "list" |
| min_len / max_len: for str, character bounds; for list, item-count bounds |
| count: exact list length (e.g. exactly 3 DM hooks) |
| item_min / item_max: character bounds for each list item (str items) |
| |
| validate() returns a list of human-readable problems (empty = valid). |
| Deliberately dependency-free so every Space stays light. |
| """ |
|
|
|
|
| def validate(data, spec) -> list[str]: |
| problems = [] |
| if not isinstance(data, dict): |
| return [f"expected a JSON object, got {type(data).__name__}"] |
| for field, rule in spec.items(): |
| if field not in data: |
| problems.append(f"missing field '{field}'") |
| continue |
| value = data[field] |
| ftype = rule.get("type", "str") |
| if ftype == "str": |
| if not isinstance(value, str): |
| problems.append(f"'{field}' must be a string") |
| continue |
| v = value.strip() |
| if rule.get("min_len") and len(v) < rule["min_len"]: |
| problems.append(f"'{field}' too short (min {rule['min_len']} chars)") |
| if rule.get("max_len") and len(v) > rule["max_len"]: |
| problems.append(f"'{field}' too long (max {rule['max_len']} chars)") |
| elif ftype == "list": |
| if not isinstance(value, list): |
| problems.append(f"'{field}' must be a list") |
| continue |
| if "count" in rule and len(value) != rule["count"]: |
| problems.append(f"'{field}' must have exactly {rule['count']} items") |
| if rule.get("min_len") and len(value) < rule["min_len"]: |
| problems.append(f"'{field}' needs at least {rule['min_len']} items") |
| if rule.get("max_len") and len(value) > rule["max_len"]: |
| problems.append(f"'{field}' allows at most {rule['max_len']} items") |
| for i, item in enumerate(value): |
| if not isinstance(item, str): |
| problems.append(f"'{field}[{i}]' must be a string") |
| continue |
| if rule.get("item_min") and len(item.strip()) < rule["item_min"]: |
| problems.append(f"'{field}[{i}]' too short") |
| if rule.get("item_max") and len(item.strip()) > rule["item_max"]: |
| problems.append(f"'{field}[{i}]' too long") |
| return problems |
|
|