mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-15 04:21:44 +00:00
Fabro's own DOT parser was left with one job after create-time compile moved to Petri: walking a workflow's file references for the bundler and the workflow-version store, and reading a name, a goal and two counts. Petri's frontend parses the same language, so the parser goes and a small crate reads the graph through Petri's. `fabro-dot` is that crate: `WorkflowGraph::parse` over `petri_frontend_attractor::dot` and its semantic model (defaults applied, subgraphs flattened, chains expanded), `references(position)` as the one walker over the static-reference vocabulary (each reference with its node and position, file references checked to be template-free), and `normalize_for_graphviz`, the re-emit of Fabro DOT with dotted attribute keys quoted, which the SVG render needs. It sits beside `fabro-petri` rather than inside it because `fabro-petri` depends on `fabro-workflow`, which depends on `fabro-workflow-version`: the version store cannot reach `fabro-petri` without a cycle, and the bundler should not pull the engine in to read a graph. Deleted: `fabro-graphviz`'s lexer, grammar, AST, semantic pass and `parse_ast` (1,829 lines, plus the `nom` dependency); the DOT model in `fabro-types::graph` (`Graph`, `Node`, `Edge`, `AttrValue`, `shape_to_handler_type`), with only `ReferenceKind` kept, moved to `fabro_types::reference`; `fabro-template`'s `visit_graph_references` and the `GraphReference`/`GraphPosition` types, with the template-syntax rule (`validate_static_reference`) kept there; the pull-request body's DOT fallback summary, which was unreachable because the DOT source only travels with the run spec whose display graph the summary already reads. `fabro-graphviz` is now the render alone, over `fabro-dot`. Parity: the old and new walkers were run over every `.fabro` and `.dot` file in the repository (118) before the deletion. Every reference set is identical. Five files differ in what Petri reads more correctly: a backslash before a newline inside a quoted string is a line continuation (four files, inline prompt text only), and a node named only by an edge counts as a node (`test/edge_only_node.fabro`, 3 nodes rather than 2, so the `fabro validate` snapshot moves). The checked-in bundles' shapes and references are pinned by a snapshot in `fabro-dot`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
81 lines
2.3 KiB
Rust
81 lines
2.3 KiB
Rust
//! Static file references in workflow packages.
|
|
//!
|
|
//! A workflow names other files from its graph (`import`,
|
|
//! `stack.child_workflow`, `@`-prefixed `prompt`, `output_schema` and `goal`
|
|
//! values) and from its `workflow.toml` (a Dockerfile, a run goal file).
|
|
//! These references are *static*: they may not contain template syntax,
|
|
//! because they are resolved before template rendering. The graph walker that
|
|
//! finds them lives in `fabro-dot`; this module owns the one rule every
|
|
//! consumer applies to a reference before resolving it.
|
|
|
|
use fabro_types::ReferenceKind;
|
|
|
|
use crate::contains_template_syntax;
|
|
|
|
/// A static file reference that unexpectedly contains template syntax.
|
|
#[derive(Debug, thiserror::Error)]
|
|
#[error("templates are not supported in {kind}s: {value}")]
|
|
pub struct StaticReferenceError {
|
|
kind: ReferenceKind,
|
|
value: String,
|
|
}
|
|
|
|
impl StaticReferenceError {
|
|
#[must_use]
|
|
pub fn new(kind: ReferenceKind, value: impl Into<String>) -> Self {
|
|
Self {
|
|
kind,
|
|
value: value.into(),
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn kind(&self) -> ReferenceKind {
|
|
self.kind
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn value(&self) -> &str {
|
|
&self.value
|
|
}
|
|
}
|
|
|
|
/// Reject static file references (imports, child workflows, `@` file values)
|
|
/// that contain template syntax.
|
|
pub fn validate_static_reference(
|
|
value: &str,
|
|
kind: ReferenceKind,
|
|
) -> Result<(), StaticReferenceError> {
|
|
if contains_template_syntax(value) {
|
|
return Err(StaticReferenceError::new(kind, value));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use fabro_types::ReferenceKind;
|
|
|
|
use super::validate_static_reference;
|
|
|
|
#[test]
|
|
fn static_reference_rejects_template_syntax() {
|
|
let error = validate_static_reference(
|
|
"@schemas/{{ inputs.schema }}.json",
|
|
ReferenceKind::FileInline,
|
|
)
|
|
.unwrap_err();
|
|
|
|
assert_eq!(error.kind(), ReferenceKind::FileInline);
|
|
assert_eq!(error.value(), "@schemas/{{ inputs.schema }}.json");
|
|
assert!(
|
|
error
|
|
.to_string()
|
|
.contains("templates are not supported in file inline references"),
|
|
"unexpected error: {error}",
|
|
);
|
|
assert!(
|
|
validate_static_reference("@schemas/result.json", ReferenceKind::FileInline).is_ok()
|
|
);
|
|
}
|
|
}
|