codex_config/
cloud_config_layers.rs1use crate::ConfigLayerEntry;
8use crate::ConfigLayerSource;
9use crate::TomlValue;
10use crate::config_toml::ConfigToml;
11use crate::loader::resolve_relative_paths_in_config_toml;
12use crate::strict_config::config_error_from_ignored_toml_value_fields_for_source_name;
13use codex_utils_absolute_path::AbsolutePathBuf;
14use codex_utils_absolute_path::AbsolutePathBufGuard;
15use serde::Deserialize;
16use serde::Serialize;
17use std::fmt;
18use std::io;
19use thiserror::Error;
20
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
27pub struct CloudConfigFragment {
28 pub id: String,
29 pub name: String,
30 pub contents: String,
31}
32
33impl CloudConfigFragment {
34 fn source_ref(&self) -> CloudConfigFragmentSource {
35 CloudConfigFragmentSource {
36 id: self.id.clone(),
37 name: self.name.clone(),
38 }
39 }
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct CloudConfigFragmentSource {
44 pub id: String,
45 pub name: String,
46}
47
48impl fmt::Display for CloudConfigFragmentSource {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "{} ({})", self.name, self.id)
51 }
52}
53
54#[derive(Debug, Error, PartialEq, Eq)]
55pub enum CloudConfigLayerError {
56 #[error("failed to parse cloud config fragment {fragment}: {message}")]
57 Parse {
58 fragment: CloudConfigFragmentSource,
59 message: String,
60 },
61 #[error("invalid cloud config fragment {fragment}: {message}")]
62 Invalid {
63 fragment: CloudConfigFragmentSource,
64 message: String,
65 },
66}
67
68pub fn cloud_config_layers_from_fragments(
69 fragments: impl IntoIterator<Item = CloudConfigFragment>,
70 base_dir: &AbsolutePathBuf,
71) -> Result<Vec<ConfigLayerEntry>, CloudConfigLayerError> {
72 cloud_config_layers_from_fragments_impl(fragments, base_dir, false)
73}
74
75pub(crate) fn cloud_config_layers_from_fragments_strict(
76 fragments: impl IntoIterator<Item = CloudConfigFragment>,
77 base_dir: &AbsolutePathBuf,
78) -> Result<Vec<ConfigLayerEntry>, CloudConfigLayerError> {
79 cloud_config_layers_from_fragments_impl(fragments, base_dir, true)
80}
81
82fn cloud_config_layers_from_fragments_impl(
83 fragments: impl IntoIterator<Item = CloudConfigFragment>,
84 base_dir: &AbsolutePathBuf,
85 strict_config: bool,
86) -> Result<Vec<ConfigLayerEntry>, CloudConfigLayerError> {
87 let mut layers = Vec::new();
88 for fragment in fragments {
89 let source_ref = fragment.source_ref();
90 let raw_toml = fragment.contents;
91 let value: TomlValue =
92 toml::from_str(&raw_toml).map_err(|err| CloudConfigLayerError::Parse {
93 fragment: source_ref.clone(),
94 message: err.to_string(),
95 })?;
96 if strict_config {
97 validate_fragment_strictly(&source_ref, &raw_toml, &value, base_dir)?;
98 }
99 let resolved =
100 resolve_relative_paths_in_config_toml(value, base_dir.as_path()).map_err(|err| {
101 CloudConfigLayerError::Invalid {
102 fragment: source_ref.clone(),
103 message: err.to_string(),
104 }
105 })?;
106 layers.push(ConfigLayerEntry::new_with_raw_toml(
107 ConfigLayerSource::EnterpriseManaged {
108 id: fragment.id,
109 name: fragment.name,
110 },
111 resolved,
112 raw_toml,
113 base_dir.clone(),
114 ));
115 }
116
117 layers.reverse();
120 Ok(layers)
121}
122
123fn validate_fragment_strictly(
124 source_ref: &CloudConfigFragmentSource,
125 raw_toml: &str,
126 value: &TomlValue,
127 base_dir: &AbsolutePathBuf,
128) -> Result<(), CloudConfigLayerError> {
129 let _guard = AbsolutePathBufGuard::new(base_dir.as_path());
130 if let Some(config_error) = config_error_from_ignored_toml_value_fields_for_source_name::<
131 ConfigToml,
132 >(&source_ref.to_string(), raw_toml, value.clone())
133 {
134 return Err(CloudConfigLayerError::Invalid {
135 fragment: source_ref.clone(),
136 message: config_error.message,
137 });
138 }
139
140 Ok(())
141}
142
143impl From<CloudConfigLayerError> for io::Error {
144 fn from(error: CloudConfigLayerError) -> Self {
145 io::Error::new(io::ErrorKind::InvalidData, error)
146 }
147}
148
149#[cfg(test)]
150#[path = "cloud_config_layers_tests.rs"]
151mod tests;