use crate::ConfigRequirementsToml; use crate::ManagedHooksRequirementsToml; use crate::RequirementSource; use crate::RequirementsExecPolicyToml; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBufGuard; use toml::Value as TomlValue; use super::stack::RequirementsCompositionError; #[derive(Clone, Debug)] pub struct RequirementsLayerEntry { pub(super) source: RequirementSource, toml: RequirementsLayerToml, base_dir: Option, } impl RequirementsLayerEntry { pub fn from_toml(source: RequirementSource, contents: impl Into) -> Self { Self { source, toml: RequirementsLayerToml::String(contents.into()), base_dir: None, } } pub fn from_toml_value(source: RequirementSource, value: TomlValue) -> Self { Self { source, toml: RequirementsLayerToml::Value(value), base_dir: None, } } pub fn with_base_dir(mut self, base_dir: AbsolutePathBuf) -> Self { self.base_dir = Some(base_dir); self } } #[derive(Clone, Debug)] enum RequirementsLayerToml { String(String), Value(TomlValue), } #[derive(Clone, Debug)] pub(super) struct ComposableRequirementsLayer { pub(super) source: RequirementSource, pub(super) regular_toml: TomlValue, pub(super) domain_fields: DomainMergedRequirementsFields, } impl ComposableRequirementsLayer { pub(super) fn from_entry( layer: RequirementsLayerEntry, hostname: Option<&str>, ) -> Result { let RequirementsLayerEntry { source, toml, base_dir, } = layer; let (mut regular_toml, mut requirements) = { let _guard = base_dir .as_ref() .map(|base_dir| AbsolutePathBufGuard::new(base_dir.as_path())); let regular_toml = parse_layer_toml(&toml, &source)?; let requirements = parse_layer_requirements(&toml, &source)?; (regular_toml, requirements) }; requirements.apply_remote_sandbox_config(hostname); materialize_remote_sandbox_config(&mut regular_toml, &requirements)?; strip_special_fields(&mut regular_toml); Ok(Self { source, regular_toml, domain_fields: DomainMergedRequirementsFields { rules: requirements.rules, hooks: requirements.hooks, permissions: requirements.permissions, }, }) } } #[derive(Clone, Debug)] pub(super) struct DomainMergedRequirementsFields { pub(super) rules: Option, pub(super) hooks: Option, pub(super) permissions: Option, } fn parse_layer_toml( toml: &RequirementsLayerToml, source: &RequirementSource, ) -> Result { match toml { RequirementsLayerToml::String(contents) => { toml::from_str(contents).map_err(|err: toml::de::Error| { RequirementsCompositionError::Parse { layer_source: source.clone(), message: err.to_string(), } }) } RequirementsLayerToml::Value(value) => Ok(value.clone()), } } fn parse_layer_requirements( toml: &RequirementsLayerToml, source: &RequirementSource, ) -> Result { match toml { RequirementsLayerToml::String(contents) => { toml::from_str(contents).map_err(|err: toml::de::Error| { RequirementsCompositionError::Parse { layer_source: source.clone(), message: err.to_string(), } }) } RequirementsLayerToml::Value(value) => { value.clone().try_into().map_err(|err: toml::de::Error| { RequirementsCompositionError::Parse { layer_source: source.clone(), message: err.to_string(), } }) } } } fn materialize_remote_sandbox_config( layer_toml: &mut TomlValue, requirements: &ConfigRequirementsToml, ) -> Result<(), RequirementsCompositionError> { remove_top_level_field(layer_toml, "remote_sandbox_config"); let Some(allowed_sandbox_modes) = requirements.allowed_sandbox_modes.as_ref() else { return Ok(()); }; let Some(table) = layer_toml.as_table_mut() else { return Ok(()); }; table.insert( "allowed_sandbox_modes".to_string(), toml_value_from_serializable(allowed_sandbox_modes)?, ); Ok(()) } fn toml_value_from_serializable( value: T, ) -> Result { TomlValue::try_from(value).map_err(|err| RequirementsCompositionError::ComposedParse { message: err.to_string(), }) } fn strip_special_fields(layer_toml: &mut TomlValue) { remove_top_level_field(layer_toml, "rules"); remove_top_level_field(layer_toml, "hooks"); remove_nested_field_and_prune_empty(layer_toml, &["permissions", "filesystem", "deny_read"]); } fn remove_top_level_field(value: &mut TomlValue, key: &str) -> Option { value.as_table_mut()?.remove(key) } fn remove_nested_field_and_prune_empty(value: &mut TomlValue, path: &[&str]) -> Option { let (key, remaining) = path.split_first()?; let table = value.as_table_mut()?; if remaining.is_empty() { return table.remove(*key); } let removed = table .get_mut(*key) .and_then(|child| remove_nested_field_and_prune_empty(child, remaining)); if table .get(*key) .and_then(TomlValue::as_table) .is_some_and(toml::map::Map::is_empty) { table.remove(*key); } removed }