Skip to content

Commit cf00fb9

Browse files
authored
Merge pull request #267 from egohygiene/copilot/fix-196492251-1187580231-71119746-da01-476c-a368-de62b65c775c
feat(graph): implement transform definition registry with pluggable format conversions
2 parents 914a47b + 3cdb9db commit cf00fb9

3 files changed

Lines changed: 572 additions & 0 deletions

File tree

src/graph/definition.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use super::{Format, TransformEdge};
2+
3+
/// A pluggable definition of a format-to-format transformation.
4+
///
5+
/// Unlike [`TransformEdge`], which is a graph artifact used internally by
6+
/// [`TransformGraph`](super::TransformGraph), a `TransformDefinition`
7+
/// describes a concrete conversion capability that can be registered at runtime
8+
/// and later materialized into graph edges via
9+
/// [`TransformDefinitionRegistry::build_graph`](super::TransformDefinitionRegistry::build_graph).
10+
///
11+
/// The optional `label` field identifies the underlying tool or method (e.g.
12+
/// `"pandoc"` or `"wkhtmltopdf"`), which helps with diagnostics and lets
13+
/// callers register multiple competing definitions for the same format pair.
14+
///
15+
/// # Example
16+
///
17+
/// ```rust
18+
/// use renderflow::graph::{Format, TransformDefinition};
19+
///
20+
/// let def = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "pandoc");
21+
/// assert_eq!(def.from, Format::Markdown);
22+
/// assert_eq!(def.to, Format::Html);
23+
/// assert_eq!(def.label, "pandoc");
24+
/// ```
25+
#[derive(Debug, Clone, PartialEq)]
26+
pub struct TransformDefinition {
27+
/// Source format for this definition.
28+
pub from: Format,
29+
/// Target format produced by this definition.
30+
pub to: Format,
31+
/// Relative cost of applying this transformation (lower is cheaper).
32+
pub cost: f32,
33+
/// Expected quality of the output on a 0.0–1.0 scale (higher is better).
34+
pub quality: f32,
35+
/// Human-readable label identifying the tool or method (e.g. `"pandoc"`).
36+
pub label: String,
37+
}
38+
39+
impl TransformDefinition {
40+
/// Create a new `TransformDefinition`.
41+
///
42+
/// # Parameters
43+
///
44+
/// * `from` – source [`Format`]
45+
/// * `to` – target [`Format`]
46+
/// * `cost` – relative execution cost (lower is cheaper)
47+
/// * `quality` – expected output quality in the range `[0.0, 1.0]`; values
48+
/// outside this range are clamped automatically.
49+
/// * `label` – human-readable name identifying the conversion tool or method
50+
pub fn new(from: Format, to: Format, cost: f32, quality: f32, label: impl Into<String>) -> Self {
51+
Self {
52+
from,
53+
to,
54+
cost,
55+
quality: quality.clamp(0.0, 1.0),
56+
label: label.into(),
57+
}
58+
}
59+
60+
/// Convert this definition into a [`TransformEdge`] for use in a
61+
/// [`TransformGraph`](super::TransformGraph).
62+
pub fn to_edge(&self) -> TransformEdge {
63+
TransformEdge::new(self.from, self.to, self.cost, self.quality)
64+
}
65+
}
66+
67+
#[cfg(test)]
68+
mod tests {
69+
use super::*;
70+
71+
// ── construction ──────────────────────────────────────────────────────────
72+
73+
#[test]
74+
fn test_definition_fields() {
75+
let def = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "pandoc");
76+
assert_eq!(def.from, Format::Markdown);
77+
assert_eq!(def.to, Format::Html);
78+
assert_eq!(def.cost, 0.5);
79+
assert!((def.quality - 1.0).abs() < 1e-5);
80+
assert_eq!(def.label, "pandoc");
81+
}
82+
83+
#[test]
84+
fn test_quality_clamped_above_one() {
85+
let def = TransformDefinition::new(Format::Markdown, Format::Pdf, 1.0, 1.5, "tool");
86+
assert!((def.quality - 1.0).abs() < 1e-5, "quality above 1.0 must be clamped");
87+
}
88+
89+
#[test]
90+
fn test_quality_clamped_below_zero() {
91+
let def = TransformDefinition::new(Format::Markdown, Format::Pdf, 1.0, -0.5, "tool");
92+
assert!((def.quality - 0.0).abs() < 1e-5, "quality below 0.0 must be clamped");
93+
}
94+
95+
#[test]
96+
fn test_quality_at_boundary_zero_not_clamped() {
97+
let def = TransformDefinition::new(Format::Markdown, Format::Html, 1.0, 0.0, "tool");
98+
assert!((def.quality - 0.0).abs() < 1e-5);
99+
}
100+
101+
#[test]
102+
fn test_quality_at_boundary_one_not_clamped() {
103+
let def = TransformDefinition::new(Format::Markdown, Format::Html, 1.0, 1.0, "tool");
104+
assert!((def.quality - 1.0).abs() < 1e-5);
105+
}
106+
107+
// ── clone / equality ──────────────────────────────────────────────────────
108+
109+
#[test]
110+
fn test_definition_clone() {
111+
let def = TransformDefinition::new(Format::Html, Format::Pdf, 0.8, 0.85, "wkhtmltopdf");
112+
let cloned = def.clone();
113+
assert_eq!(def, cloned);
114+
}
115+
116+
#[test]
117+
fn test_definition_equality() {
118+
let a = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "pandoc");
119+
let b = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "pandoc");
120+
assert_eq!(a, b);
121+
}
122+
123+
#[test]
124+
fn test_definition_inequality_by_label() {
125+
let a = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "pandoc");
126+
let b = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "other");
127+
assert_ne!(a, b);
128+
}
129+
130+
#[test]
131+
fn test_definition_inequality_by_format() {
132+
let a = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.0, "pandoc");
133+
let b = TransformDefinition::new(Format::Markdown, Format::Pdf, 0.5, 1.0, "pandoc");
134+
assert_ne!(a, b);
135+
}
136+
137+
// ── to_edge ───────────────────────────────────────────────────────────────
138+
139+
#[test]
140+
fn test_to_edge_preserves_from_to_cost_quality() {
141+
let def = TransformDefinition::new(Format::Html, Format::Pdf, 0.8, 0.85, "wkhtmltopdf");
142+
let edge = def.to_edge();
143+
assert_eq!(edge.from, Format::Html);
144+
assert_eq!(edge.to, Format::Pdf);
145+
assert!((edge.cost - 0.8).abs() < 1e-5);
146+
assert!((edge.quality - 0.85).abs() < 1e-5);
147+
}
148+
149+
#[test]
150+
fn test_to_edge_from_definition_with_clamped_quality() {
151+
// Quality of 1.5 is clamped to 1.0 at construction time; edge must reflect that.
152+
let def = TransformDefinition::new(Format::Markdown, Format::Html, 0.5, 1.5, "tool");
153+
let edge = def.to_edge();
154+
assert!((edge.quality - 1.0).abs() < 1e-5);
155+
}
156+
}

0 commit comments

Comments
 (0)