rlx_runtime/
validators.rs1use std::fmt;
22
23#[derive(Debug, Clone)]
24pub struct ValidationError {
25 pub rule: &'static str,
26 pub message: String,
27}
28
29impl fmt::Display for ValidationError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 write!(f, "[{}] {}", self.rule, self.message)
32 }
33}
34
35impl std::error::Error for ValidationError {}
36
37pub type ValidationResult = Result<(), ValidationError>;
38
39pub trait Validator<C>: Send + Sync {
41 fn check(&self, ctx: &C) -> ValidationResult;
42 fn name(&self) -> &'static str;
43}
44
45pub fn run_chain<C>(ctx: &C, chain: &[&dyn Validator<C>]) -> ValidationResult {
47 for v in chain {
48 v.check(ctx)?;
49 }
50 Ok(())
51}
52
53#[derive(Debug, Clone)]
57pub struct TextContext {
58 pub seq_len: usize,
59 pub batch_size: usize,
60 pub vocab_id_max: usize,
61 pub max_token_id_seen: usize,
62}
63
64pub struct MaxSeqLen(pub usize);
65impl Validator<TextContext> for MaxSeqLen {
66 fn check(&self, ctx: &TextContext) -> ValidationResult {
67 if ctx.seq_len > self.0 {
68 Err(ValidationError {
69 rule: self.name(),
70 message: format!("seq_len {} exceeds max {}", ctx.seq_len, self.0),
71 })
72 } else {
73 Ok(())
74 }
75 }
76 fn name(&self) -> &'static str {
77 "max_seq_len"
78 }
79}
80
81pub struct MaxBatchSize(pub usize);
82impl Validator<TextContext> for MaxBatchSize {
83 fn check(&self, ctx: &TextContext) -> ValidationResult {
84 if ctx.batch_size > self.0 {
85 Err(ValidationError {
86 rule: self.name(),
87 message: format!("batch_size {} exceeds max {}", ctx.batch_size, self.0),
88 })
89 } else {
90 Ok(())
91 }
92 }
93 fn name(&self) -> &'static str {
94 "max_batch_size"
95 }
96}
97
98pub struct TokenIdsInVocab;
99impl Validator<TextContext> for TokenIdsInVocab {
100 fn check(&self, ctx: &TextContext) -> ValidationResult {
101 if ctx.max_token_id_seen >= ctx.vocab_id_max {
102 Err(ValidationError {
103 rule: self.name(),
104 message: format!(
105 "saw token_id {} but vocab is {}",
106 ctx.max_token_id_seen, ctx.vocab_id_max
107 ),
108 })
109 } else {
110 Ok(())
111 }
112 }
113 fn name(&self) -> &'static str {
114 "token_ids_in_vocab"
115 }
116}
117
118#[derive(Debug, Clone)]
121pub struct ImageContext {
122 pub width: u32,
123 pub height: u32,
124 pub channels: u32,
125}
126
127pub struct ImageMaxBounds {
128 pub max_w: u32,
129 pub max_h: u32,
130}
131impl Validator<ImageContext> for ImageMaxBounds {
132 fn check(&self, ctx: &ImageContext) -> ValidationResult {
133 if ctx.width > self.max_w || ctx.height > self.max_h {
134 Err(ValidationError {
135 rule: self.name(),
136 message: format!(
137 "{}×{} exceeds max {}×{}",
138 ctx.width, ctx.height, self.max_w, self.max_h
139 ),
140 })
141 } else {
142 Ok(())
143 }
144 }
145 fn name(&self) -> &'static str {
146 "image_max_bounds"
147 }
148}
149
150pub struct ChannelsAllowed(pub &'static [u32]);
151impl Validator<ImageContext> for ChannelsAllowed {
152 fn check(&self, ctx: &ImageContext) -> ValidationResult {
153 if !self.0.contains(&ctx.channels) {
154 Err(ValidationError {
155 rule: self.name(),
156 message: format!("channels={} not in allowed set {:?}", ctx.channels, self.0),
157 })
158 } else {
159 Ok(())
160 }
161 }
162 fn name(&self) -> &'static str {
163 "channels_allowed"
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn text_chain_short_circuits() {
173 let ctx = TextContext {
174 seq_len: 600,
175 batch_size: 1,
176 vocab_id_max: 30000,
177 max_token_id_seen: 100,
178 };
179 let max_seq = MaxSeqLen(512);
180 let max_batch = MaxBatchSize(64);
181 let tok = TokenIdsInVocab;
182 let chain: Vec<&dyn Validator<TextContext>> = vec![&max_seq, &max_batch, &tok];
183 let err = run_chain(&ctx, &chain).unwrap_err();
184 assert_eq!(err.rule, "max_seq_len");
185 }
186
187 #[test]
188 fn image_chain_passes() {
189 let ctx = ImageContext {
190 width: 224,
191 height: 224,
192 channels: 3,
193 };
194 let bounds = ImageMaxBounds {
195 max_w: 1024,
196 max_h: 1024,
197 };
198 let chans = ChannelsAllowed(&[1, 3, 4]);
199 let chain: Vec<&dyn Validator<ImageContext>> = vec![&bounds, &chans];
200 assert!(run_chain(&ctx, &chain).is_ok());
201 }
202
203 #[test]
204 fn image_chain_catches_bad_channels() {
205 let ctx = ImageContext {
206 width: 224,
207 height: 224,
208 channels: 2,
209 };
210 let chans = ChannelsAllowed(&[1, 3, 4]);
211 let chain: Vec<&dyn Validator<ImageContext>> = vec![&chans];
212 let err = run_chain(&ctx, &chain).unwrap_err();
213 assert_eq!(err.rule, "channels_allowed");
214 }
215}