Skip to main content

codex_config/
constraint.rs

1use 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;
55/// A ConstraintNormalizer is a function which transforms a value into another of the same type.
56/// `Constrained` uses normalizers to transform values to satisfy constraints or enforce values.
57type 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    /// normalized creates a `Constrained` value with a normalizer function and a validator that allows any value.
81    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    /// Allow any value of T, using T's Default as the initial value.
128    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    /// Composes an additional validator onto the current constraint.
151    ///
152    /// The existing value must satisfy the combined validator before it is installed.
153    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(/*initial_value*/ 5);
222        constrained
223            .set(/*value*/ -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(/*only_value*/ 5);
237        constrained
238            .set(/*value*/ 5)
239            .expect("allowed value should be accepted");
240
241        let err = constrained
242            .set(/*value*/ 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(/*initial_value*/ -1, |value| value.max(0))?;
252        assert_eq!(constrained.value(), 0);
253        constrained.set(/*value*/ -5)?;
254        assert_eq!(constrained.value(), 0);
255        constrained.set(/*value*/ 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(/*initial_value*/ 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(/*initial_value*/ 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(/*initial_value*/ 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(/*value*/ -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(/*initial_value*/ 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}