codex_config/
constraint.rs1use std::fmt;
2use std::sync::Arc;
3
4use crate::config_requirements::RequirementSource;
5use thiserror::Error;
6
7#[derive(Debug, Error, PartialEq, Eq)]
8pub enum ConstraintError {
9 #[error(
10 "invalid value for `{field_name}`: `{candidate}` is not in the allowed set {allowed} (set by {requirement_source})"
11 )]
12 InvalidValue {
13 field_name: &'static str,
14 candidate: String,
15 allowed: String,
16 requirement_source: RequirementSource,
17 },
18
19 #[error("field `{field_name}` cannot be empty")]
20 EmptyField { field_name: String },
21
22 #[error("invalid rules in requirements (set by {requirement_source}): {reason}")]
23 ExecPolicyParse {
24 requirement_source: RequirementSource,
25 reason: String,
26 },
27
28 #[error(
29 "invalid requirement for MCP server `{server_name}` (set by {requirement_source}): {reason}"
30 )]
31 McpServerRequirementParse {
32 server_name: String,
33 requirement_source: RequirementSource,
34 reason: String,
35 },
36}
37
38impl ConstraintError {
39 pub fn empty_field(field_name: impl Into<String>) -> Self {
40 Self::EmptyField {
41 field_name: field_name.into(),
42 }
43 }
44}
45
46pub type ConstraintResult<T> = Result<T, ConstraintError>;
47
48impl From<ConstraintError> for std::io::Error {
49 fn from(err: ConstraintError) -> Self {
50 std::io::Error::new(std::io::ErrorKind::InvalidInput, err)
51 }
52}
53
54type ConstraintValidator<T> = dyn Fn(&T) -> ConstraintResult<()> + Send + Sync;
55type ConstraintNormalizer<T> = dyn Fn(T) -> T + Send + Sync;
58
59#[derive(Clone)]
60pub struct Constrained<T> {
61 value: T,
62 validator: Arc<ConstraintValidator<T>>,
63 normalizer: Option<Arc<ConstraintNormalizer<T>>>,
64}
65
66impl<T: Send + Sync> Constrained<T> {
67 pub fn new(
68 initial_value: T,
69 validator: impl Fn(&T) -> ConstraintResult<()> + Send + Sync + 'static,
70 ) -> ConstraintResult<Self> {
71 let validator: Arc<ConstraintValidator<T>> = Arc::new(validator);
72 validator(&initial_value)?;
73 Ok(Self {
74 value: initial_value,
75 validator,
76 normalizer: None,
77 })
78 }
79
80 pub fn normalized(
82 initial_value: T,
83 normalizer: impl Fn(T) -> T + Send + Sync + 'static,
84 ) -> ConstraintResult<Self> {
85 let validator: Arc<ConstraintValidator<T>> = Arc::new(|_| Ok(()));
86 let normalizer: Arc<ConstraintNormalizer<T>> = Arc::new(normalizer);
87 let normalized = normalizer(initial_value);
88 validator(&normalized)?;
89 Ok(Self {
90 value: normalized,
91 validator,
92 normalizer: Some(normalizer),
93 })
94 }
95
96 pub fn allow_any(initial_value: T) -> Self {
97 Self {
98 value: initial_value,
99 validator: Arc::new(|_| Ok(())),
100 normalizer: None,
101 }
102 }
103
104 pub fn allow_only(only_value: T) -> Self
105 where
106 T: Clone + fmt::Debug + PartialEq + 'static,
107 {
108 let allowed_value = only_value.clone();
109 Self {
110 value: only_value,
111 validator: Arc::new(move |candidate| {
112 if candidate == &allowed_value {
113 Ok(())
114 } else {
115 Err(ConstraintError::InvalidValue {
116 field_name: "<unknown>",
117 candidate: format!("{candidate:?}"),
118 allowed: format!("[{allowed_value:?}]"),
119 requirement_source: RequirementSource::Unknown,
120 })
121 }
122 }),
123 normalizer: None,
124 }
125 }
126
127 pub fn allow_any_from_default() -> Self
129 where
130 T: Default,
131 {
132 Self::allow_any(T::default())
133 }
134
135 pub fn get(&self) -> &T {
136 &self.value
137 }
138
139 pub fn value(&self) -> T
140 where
141 T: Copy,
142 {
143 self.value
144 }
145
146 pub fn can_set(&self, candidate: &T) -> ConstraintResult<()> {
147 (self.validator)(candidate)
148 }
149
150 pub fn add_validator(
154 &mut self,
155 validator: impl Fn(&T) -> ConstraintResult<()> + Send + Sync + 'static,
156 ) -> ConstraintResult<()>
157 where
158 T: 'static,
159 {
160 let existing_validator = self.validator.clone();
161 let combined_validator: Arc<ConstraintValidator<T>> = Arc::new(move |candidate| {
162 existing_validator(candidate)?;
163 validator(candidate)
164 });
165
166 combined_validator(&self.value)?;
167 self.validator = combined_validator;
168 Ok(())
169 }
170
171 pub fn set(&mut self, value: T) -> ConstraintResult<()> {
172 let value = if let Some(normalizer) = &self.normalizer {
173 normalizer(value)
174 } else {
175 value
176 };
177 (self.validator)(&value)?;
178 self.value = value;
179 Ok(())
180 }
181}
182
183impl<T> std::ops::Deref for Constrained<T> {
184 type Target = T;
185
186 fn deref(&self) -> &Self::Target {
187 &self.value
188 }
189}
190
191impl<T: fmt::Debug> fmt::Debug for Constrained<T> {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.debug_struct("Constrained")
194 .field("value", &self.value)
195 .finish()
196 }
197}
198
199impl<T: PartialEq> PartialEq for Constrained<T> {
200 fn eq(&self, other: &Self) -> bool {
201 self.value == other.value
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use pretty_assertions::assert_eq;
209
210 fn invalid_value(candidate: impl Into<String>, allowed: impl Into<String>) -> ConstraintError {
211 ConstraintError::InvalidValue {
212 field_name: "<unknown>",
213 candidate: candidate.into(),
214 allowed: allowed.into(),
215 requirement_source: RequirementSource::Unknown,
216 }
217 }
218
219 #[test]
220 fn constrained_allow_any_accepts_any_value() {
221 let mut constrained = Constrained::allow_any(5);
222 constrained
223 .set(-10)
224 .expect("allow any accepts all values");
225 assert_eq!(constrained.value(), -10);
226 }
227
228 #[test]
229 fn constrained_allow_any_default_uses_default_value() {
230 let constrained = Constrained::<i32>::allow_any_from_default();
231 assert_eq!(constrained.value(), 0);
232 }
233
234 #[test]
235 fn constrained_allow_only_rejects_different_values() {
236 let mut constrained = Constrained::allow_only(5);
237 constrained
238 .set(5)
239 .expect("allowed value should be accepted");
240
241 let err = constrained
242 .set(6)
243 .expect_err("different value should be rejected");
244 assert_eq!(err, invalid_value("6", "[5]"));
245 assert_eq!(constrained.value(), 5);
246 }
247
248 #[test]
249 fn constrained_normalizer_applies_on_init_and_set() -> anyhow::Result<()> {
250 let mut constrained =
251 Constrained::normalized(-1, |value| value.max(0))?;
252 assert_eq!(constrained.value(), 0);
253 constrained.set(-5)?;
254 assert_eq!(constrained.value(), 0);
255 constrained.set(10)?;
256 assert_eq!(constrained.value(), 10);
257 Ok(())
258 }
259
260 #[test]
261 fn constrained_add_validator_composes_with_existing_validator() -> anyhow::Result<()> {
262 let mut constrained = Constrained::new(5, |value: &i32| {
263 if *value >= 0 {
264 Ok(())
265 } else {
266 Err(ConstraintError::empty_field("value"))
267 }
268 })?;
269 constrained.add_validator(|value| {
270 if *value <= 10 {
271 Ok(())
272 } else {
273 Err(ConstraintError::empty_field("value"))
274 }
275 })?;
276
277 assert_eq!(constrained.can_set(&7), Ok(()));
278 assert_eq!(
279 constrained.can_set(&11),
280 Err(ConstraintError::empty_field("value"))
281 );
282 assert_eq!(
283 constrained.can_set(&-1),
284 Err(ConstraintError::empty_field("value"))
285 );
286
287 Ok(())
288 }
289
290 #[test]
291 fn constrained_new_rejects_invalid_initial_value() {
292 let result = Constrained::new(0, |value| {
293 if *value > 0 {
294 Ok(())
295 } else {
296 Err(invalid_value(value.to_string(), "positive values"))
297 }
298 });
299
300 assert_eq!(result, Err(invalid_value("0", "positive values")));
301 }
302
303 #[test]
304 fn constrained_set_rejects_invalid_value_and_leaves_previous() {
305 let mut constrained = Constrained::new(1, |value| {
306 if *value > 0 {
307 Ok(())
308 } else {
309 Err(invalid_value(value.to_string(), "positive values"))
310 }
311 })
312 .expect("initial value should be accepted");
313
314 let err = constrained
315 .set(-5)
316 .expect_err("negative values should be rejected");
317 assert_eq!(err, invalid_value("-5", "positive values"));
318 assert_eq!(constrained.value(), 1);
319 }
320
321 #[test]
322 fn constrained_can_set_allows_probe_without_setting() {
323 let constrained = Constrained::new(1, |value| {
324 if *value > 0 {
325 Ok(())
326 } else {
327 Err(invalid_value(value.to_string(), "positive values"))
328 }
329 })
330 .expect("initial value should be accepted");
331
332 constrained
333 .can_set(&2)
334 .expect("can_set should accept positive value");
335 let err = constrained
336 .can_set(&-1)
337 .expect_err("can_set should reject negative value");
338 assert_eq!(err, invalid_value("-1", "positive values"));
339 assert_eq!(constrained.value(), 1);
340 }
341}