objectiveai_cli/filesystem/config/
client.rs1use super::super::{Client, Error};
2
3impl Client {
4 pub async fn read_config(&self) -> Result<super::Config, Error> {
5 let path = self.config_path();
6 match tokio::fs::read(&path).await {
7 Ok(bytes) => serde_json::from_slice(&bytes)
8 .map_err(|e| Error::Parse(path, e)),
9 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
10 Ok(super::Config::default())
11 }
12 Err(e) => Err(Error::Read(path, e)),
13 }
14 }
15
16 pub async fn write_config(
17 &self,
18 config: &super::Config,
19 ) -> Result<(), Error> {
20 let path = self.config_path();
21 if let Some(parent) = path.parent() {
22 tokio::fs::create_dir_all(parent)
23 .await
24 .map_err(|e| Error::Write(parent.to_path_buf(), e))?;
25 }
26 let bytes =
27 serde_json::to_vec_pretty(config).map_err(Error::Serialize)?;
28 crate::filesystem::util::write_atomic(&path, &bytes)
32 .await
33 .map_err(|e| Error::Write(path, e))?;
34 Ok(())
35 }
36
37 pub async fn read_config_at(
41 &self,
42 scope: objectiveai_sdk::cli::command::SetScope,
43 ) -> Result<super::Config, Error> {
44 let path = match scope {
45 objectiveai_sdk::cli::command::SetScope::Global => self.global_config_path(),
46 objectiveai_sdk::cli::command::SetScope::State => self.config_path(),
47 };
48 read_config_file(path).await
49 }
50
51 pub async fn write_config_at(
53 &self,
54 scope: objectiveai_sdk::cli::command::SetScope,
55 config: &super::Config,
56 ) -> Result<(), Error> {
57 let path = match scope {
58 objectiveai_sdk::cli::command::SetScope::Global => self.global_config_path(),
59 objectiveai_sdk::cli::command::SetScope::State => self.config_path(),
60 };
61 write_config_file(path, config).await
62 }
63
64 pub async fn read_config_view(
71 &self,
72 scope: objectiveai_sdk::cli::command::GetScope,
73 ) -> Result<super::Config, Error> {
74 match scope {
75 objectiveai_sdk::cli::command::GetScope::Global => {
76 read_config_file(self.global_config_path()).await
77 }
78 objectiveai_sdk::cli::command::GetScope::State => {
79 read_config_file(self.config_path()).await
80 }
81 objectiveai_sdk::cli::command::GetScope::Final => {
82 let global = read_config_file(self.global_config_path()).await?;
83 let state = read_config_file(self.config_path()).await?;
84 merge_final(global, state)
85 }
86 }
87 }
88}
89
90async fn read_config_file(
91 path: std::path::PathBuf,
92) -> Result<super::Config, Error> {
93 match tokio::fs::read(&path).await {
94 Ok(bytes) => {
95 serde_json::from_slice(&bytes).map_err(|e| Error::Parse(path, e))
96 }
97 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
98 Ok(super::Config::default())
99 }
100 Err(e) => Err(Error::Read(path, e)),
101 }
102}
103
104async fn write_config_file(
105 path: std::path::PathBuf,
106 config: &super::Config,
107) -> Result<(), Error> {
108 if let Some(parent) = path.parent() {
109 tokio::fs::create_dir_all(parent)
110 .await
111 .map_err(|e| Error::Write(parent.to_path_buf(), e))?;
112 }
113 let bytes = serde_json::to_vec_pretty(config).map_err(Error::Serialize)?;
114 crate::filesystem::util::write_atomic(&path, &bytes)
116 .await
117 .map_err(|e| Error::Write(path, e))?;
118 Ok(())
119}
120
121fn merge_final(
130 global: super::Config,
131 state: super::Config,
132) -> Result<super::Config, Error> {
133 let mut base = serde_json::to_value(&global).map_err(Error::Serialize)?;
134 let overlay = serde_json::to_value(&state).map_err(Error::Serialize)?;
135 merge_override(&mut base, &overlay);
136 serde_json::from_value(base)
137 .map_err(|e| Error::Parse(std::path::PathBuf::from("<final merge>"), e))
138}
139
140fn merge_override(base: &mut serde_json::Value, overlay: &serde_json::Value) {
143 match (&mut *base, overlay) {
144 (serde_json::Value::Object(b), serde_json::Value::Object(o)) => {
145 for (k, v) in o {
146 match b.get_mut(k) {
147 Some(bv) => merge_override(bv, v),
148 None => {
149 b.insert(k.clone(), v.clone());
150 }
151 }
152 }
153 }
154 (b, o) => *b = o.clone(),
155 }
156}