""" Validation for ResolutionFlow tree markdown. Validates markdown without saving, returning detailed errors with line numbers. """ from app.services.tree_markdown_parser import parse_markdown_to_tree, ParseError def validate_tree_markdown(markdown: str) -> list[ParseError]: """Validate tree markdown and return all errors/warnings. This wraps the parser and adds additional semantic checks. Args: markdown: The markdown string to validate. Returns: List of ParseError objects with line, column, message, severity. """ result = parse_markdown_to_tree(markdown) errors = list(result.errors) # If parsing completely failed, return early if result.tree_structure is None: return errors # Additional semantic validation on the parsed tree _validate_tree_semantics(result.tree_structure, errors) return errors def _validate_tree_semantics(tree: dict, errors: list[ParseError]) -> None: """Run semantic checks on a parsed tree structure.""" all_ids: set[str] = set() has_solution = False def _collect_ids(node: dict) -> None: nonlocal has_solution all_ids.add(node.get("id", "")) if node.get("type") == "solution": has_solution = True for child in node.get("children", []): _collect_ids(child) _collect_ids(tree) if not has_solution: errors.append(ParseError( line=1, column=1, message="Tree must have at least one solution node", severity="warning" )) # Check for empty required fields def _validate_node(node: dict) -> None: ntype = node.get("type", "") nid = node.get("id", "") if ntype == "decision": if not node.get("question", "").strip(): errors.append(ParseError( line=1, column=1, message=f"Decision node '{nid}' has an empty question", severity="warning" )) if not node.get("options"): errors.append(ParseError( line=1, column=1, message=f"Decision node '{nid}' has no options", severity="warning" )) elif ntype == "action": if not node.get("title", "").strip(): errors.append(ParseError( line=1, column=1, message=f"Action node '{nid}' has an empty title", severity="warning" )) elif ntype == "solution": if not node.get("title", "").strip(): errors.append(ParseError( line=1, column=1, message=f"Solution node '{nid}' has an empty title", severity="warning" )) for child in node.get("children", []): _validate_node(child) _validate_node(tree)