systemprompt_cli/commands/cloud/profile/
edit_document.rs1use std::path::{Path, PathBuf};
15
16use anyhow::{Context, Result};
17use serde_yaml::Value;
18use systemprompt_manifest::Profile;
19
20use crate::commands::admin::config::config_section::read_yaml_file;
21
22#[derive(Debug)]
24pub struct ProfileDocument {
25 path: PathBuf,
26 raw: Value,
27}
28
29impl ProfileDocument {
30 pub fn open(path: &Path) -> Result<Self> {
31 Ok(Self {
32 path: path.to_path_buf(),
33 raw: read_yaml_file(path)?,
34 })
35 }
36
37 pub fn apply_changes(&mut self, before: &Profile, after: &Profile) -> Result<()> {
38 let before = serde_yaml::to_value(before).context("Failed to serialize profile")?;
39 let after = serde_yaml::to_value(after).context("Failed to serialize profile")?;
40 merge_changed_leaves(&mut self.raw, &before, &after);
41 Ok(())
42 }
43
44 pub fn save(&self) -> Result<()> {
45 let content = serde_yaml::to_string(&self.raw).context("Failed to serialize profile")?;
46 Profile::from_yaml(&content, &self.path)
47 .with_context(|| format!("Edited profile is invalid: {}", self.path.display()))?;
48 std::fs::write(&self.path, content)
49 .with_context(|| format!("Failed to write {}", self.path.display()))
50 }
51}
52
53fn merge_changed_leaves(raw: &mut Value, before: &Value, after: &Value) {
54 let (Some(before_map), Some(after_map), Some(raw_map)) = (
55 before.as_mapping(),
56 after.as_mapping(),
57 raw.as_mapping_mut(),
58 ) else {
59 if before != after {
60 *raw = after.clone();
61 }
62 return;
63 };
64
65 for (key, after_value) in after_map {
66 let Some(before_value) = before_map.get(key) else {
67 raw_map.insert(key.clone(), after_value.clone());
68 continue;
69 };
70 if before_value == after_value {
71 continue;
72 }
73 if let Some(slot) = raw_map.get_mut(key) {
74 merge_changed_leaves(slot, before_value, after_value);
75 } else {
76 raw_map.insert(key.clone(), after_value.clone());
77 }
78 }
79 for key in before_map.keys() {
80 if !after_map.contains_key(key) {
81 raw_map.remove(key);
82 }
83 }
84}