traitclaw_core/traits/
output_processor.rs1#[deprecated(
14 since = "0.3.0",
15 note = "Use OutputTransformer instead. OutputProcessor will be removed in v1.0.0. \
16 Existing impls work automatically via blanket impl."
17)]
18pub trait OutputProcessor: Send + Sync {
19 fn process(&self, output: String) -> String;
21}
22
23pub struct NoopProcessor;
25
26#[allow(deprecated)]
27impl OutputProcessor for NoopProcessor {
28 fn process(&self, output: String) -> String {
29 output
30 }
31}
32
33pub struct TruncateProcessor {
35 max_chars: usize,
37}
38
39impl Default for TruncateProcessor {
40 fn default() -> Self {
41 Self { max_chars: 10_000 }
42 }
43}
44
45impl TruncateProcessor {
46 #[must_use]
48 pub fn new(max_chars: usize) -> Self {
49 Self { max_chars }
50 }
51}
52
53#[allow(deprecated)]
54impl OutputProcessor for TruncateProcessor {
55 fn process(&self, output: String) -> String {
56 let char_count = output.chars().count();
58 if char_count <= self.max_chars {
59 output
60 } else {
61 let byte_offset = output
63 .char_indices()
64 .nth(self.max_chars)
65 .map_or(output.len(), |(idx, _)| idx);
66 let mut truncated = output[..byte_offset].to_string();
67 truncated.push_str("...\n[output truncated]");
68 truncated
69 }
70 }
71}
72
73#[allow(deprecated)]
75pub struct ChainProcessor {
76 processors: Vec<Box<dyn OutputProcessor>>,
77}
78
79#[allow(deprecated)]
80impl ChainProcessor {
81 #[must_use]
83 pub fn new(processors: Vec<Box<dyn OutputProcessor>>) -> Self {
84 Self { processors }
85 }
86}
87
88#[allow(deprecated)]
89impl OutputProcessor for ChainProcessor {
90 fn process(&self, mut output: String) -> String {
91 for p in &self.processors {
92 output = p.process(output);
93 }
94 output
95 }
96}
97
98#[cfg(test)]
99#[allow(deprecated)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_noop_returns_unchanged() {
105 let p = NoopProcessor;
106 let input = "hello world".to_string();
107 assert_eq!(p.process(input.clone()), input);
108 }
109
110 #[test]
111 fn test_truncate_at_boundary() {
112 let p = TruncateProcessor::new(10);
113 let short = "12345".to_string();
114 assert_eq!(
115 p.process(short.clone()),
116 short,
117 "should not truncate short input"
118 );
119
120 let exact = "1234567890".to_string();
121 assert_eq!(
122 p.process(exact.clone()),
123 exact,
124 "should not truncate exact-length input"
125 );
126
127 let long = "12345678901".to_string();
128 let result = p.process(long);
129 assert!(
130 result.ends_with("[output truncated]"),
131 "should truncate: {result}"
132 );
133 assert!(
134 result.starts_with("1234567890"),
135 "should keep first 10 chars"
136 );
137 }
138
139 #[test]
140 fn test_chain_applies_in_order() {
141 struct UpperCase;
142 impl OutputProcessor for UpperCase {
143 fn process(&self, output: String) -> String {
144 output.to_uppercase()
145 }
146 }
147
148 let chain = ChainProcessor::new(vec![
149 Box::new(UpperCase),
150 Box::new(TruncateProcessor::new(5)),
151 ]);
152
153 let result = chain.process("hello world".to_string());
154 assert!(result.starts_with("HELLO"), "got: {result}");
156 assert!(result.contains("[output truncated]"));
157 }
158}