codex_config/
shell_environment_policy.rs1use codex_protocol::config_types::EnvironmentVariablePattern;
2use codex_protocol::config_types::ShellEnvironmentPolicy;
3use codex_protocol::config_types::ShellEnvironmentPolicyFilter;
4use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
5use schemars::JsonSchema;
6use serde::Deserialize;
7use serde::Serialize;
8use std::collections::BTreeMap;
9use std::collections::HashMap;
10use toml::Value as TomlValue;
11
12#[derive(Serialize, Debug, Clone, PartialEq, Default, JsonSchema)]
14#[schemars(deny_unknown_fields)]
15pub struct ShellEnvironmentPolicyToml {
16 pub inherit: Option<ShellEnvironmentPolicyInherit>,
17
18 pub ignore_default_excludes: Option<bool>,
19
20 pub exclude: Option<Vec<String>>,
22
23 pub r#set: Option<HashMap<String, String>>,
24
25 pub include_only: Option<Vec<String>>,
27
28 pub filters: Option<BTreeMap<String, ShellEnvironmentPolicyFilter>>,
36
37 pub experimental_use_profile: Option<bool>,
38}
39
40#[derive(Deserialize)]
41struct ShellEnvironmentPolicyTomlRaw {
42 inherit: Option<ShellEnvironmentPolicyInherit>,
43 ignore_default_excludes: Option<bool>,
44 exclude: Option<Vec<String>>,
45 r#set: Option<HashMap<String, String>>,
46 include_only: Option<Vec<String>>,
47 filters: Option<BTreeMap<String, ShellEnvironmentPolicyFilter>>,
48 experimental_use_profile: Option<bool>,
49}
50
51#[derive(Deserialize)]
52pub(crate) struct ShellEnvironmentPolicyFilterConfigToml {
53 #[serde(
54 default,
55 rename = "shell_environment_policy",
56 deserialize_with = "deserialize_shell_environment_policy_filters"
57 )]
58 _shell_environment_policy: (),
59}
60
61pub fn validate_shell_environment_policy_filter_config(
63 value: &TomlValue,
64) -> Result<(), toml::de::Error> {
65 let _: ShellEnvironmentPolicyFilterConfigToml = value.clone().try_into()?;
66 Ok(())
67}
68
69fn deserialize_shell_environment_policy_filters<'de, D>(deserializer: D) -> Result<(), D::Error>
70where
71 D: serde::Deserializer<'de>,
72{
73 let value = TomlValue::deserialize(deserializer)?;
74 let Some(policy) = value.as_table() else {
75 return Ok(());
76 };
77 let filter_fields = ["exclude", "include_only", "filters"]
78 .into_iter()
79 .filter_map(|field| {
80 policy
81 .get(field)
82 .cloned()
83 .map(|value| (field.to_string(), value))
84 })
85 .collect();
86 let _: ShellEnvironmentPolicyToml = TomlValue::Table(filter_fields)
87 .try_into()
88 .map_err(|error: toml::de::Error| serde::de::Error::custom(error.message()))?;
89 Ok(())
90}
91
92impl<'de> Deserialize<'de> for ShellEnvironmentPolicyToml {
93 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94 where
95 D: serde::Deserializer<'de>,
96 {
97 let ShellEnvironmentPolicyTomlRaw {
98 inherit,
99 ignore_default_excludes,
100 exclude,
101 r#set,
102 include_only,
103 filters,
104 experimental_use_profile,
105 } = ShellEnvironmentPolicyTomlRaw::deserialize(deserializer)?;
106 if filters.is_some() && (exclude.is_some() || include_only.is_some()) {
107 return Err(serde::de::Error::custom(
108 "cannot mix `filters` with legacy `exclude` or `include_only`",
109 ));
110 }
111 if let Some(filters) = filters.as_ref() {
112 let mut patterns = std::collections::HashSet::new();
113 for pattern in filters.keys() {
114 if !patterns.insert(pattern.to_lowercase()) {
115 return Err(serde::de::Error::custom(format!(
116 "duplicate shell environment filter `{pattern}` ignoring case"
117 )));
118 }
119 }
120 }
121 Ok(Self {
122 inherit,
123 ignore_default_excludes,
124 exclude,
125 r#set,
126 include_only,
127 filters,
128 experimental_use_profile,
129 })
130 }
131}
132
133impl From<ShellEnvironmentPolicyToml> for ShellEnvironmentPolicy {
134 fn from(toml: ShellEnvironmentPolicyToml) -> Self {
135 let inherit = toml.inherit.unwrap_or(ShellEnvironmentPolicyInherit::All);
136 let ignore_default_excludes = toml.ignore_default_excludes.unwrap_or(true);
137 let (exclude, include_only) = match toml.filters {
138 Some(filters) => filters.into_iter().fold(
139 (Vec::new(), Vec::new()),
140 |(mut exclude, mut include_only), (pattern, filter)| {
141 match filter {
142 ShellEnvironmentPolicyFilter::Include => include_only.push(pattern),
143 ShellEnvironmentPolicyFilter::Exclude => exclude.push(pattern),
144 }
145 (exclude, include_only)
146 },
147 ),
148 None => (
149 toml.exclude.unwrap_or_default(),
150 toml.include_only.unwrap_or_default(),
151 ),
152 };
153
154 Self {
155 inherit,
156 ignore_default_excludes,
157 exclude: exclude
158 .into_iter()
159 .map(|pattern| EnvironmentVariablePattern::new_case_insensitive(&pattern))
160 .collect(),
161 r#set: toml.r#set.unwrap_or_default(),
162 include_only: include_only
163 .into_iter()
164 .map(|pattern| EnvironmentVariablePattern::new_case_insensitive(&pattern))
165 .collect(),
166 use_profile: toml.experimental_use_profile.unwrap_or(false),
167 }
168 }
169}
170
171#[cfg(test)]
172#[path = "shell_environment_policy_tests.rs"]
173mod tests;