data_modelling_sdk/model/
saver.rs

1//! Model saving functionality
2//!
3//! Saves models to storage backends, handling YAML serialization.
4
5use crate::storage::{StorageBackend, StorageError};
6use anyhow::Result;
7use serde_yaml;
8use tracing::info;
9use uuid::Uuid;
10
11/// Model saver that uses a storage backend
12pub struct ModelSaver<B: StorageBackend> {
13    storage: B,
14}
15
16impl<B: StorageBackend> ModelSaver<B> {
17    /// Create a new model saver with the given storage backend
18    pub fn new(storage: B) -> Self {
19        Self { storage }
20    }
21
22    /// Save a table to storage
23    ///
24    /// Saves the table as a YAML file in the workspace's `tables/` directory.
25    /// The filename will be based on the table name if yaml_file_path is not provided.
26    pub async fn save_table(
27        &self,
28        workspace_path: &str,
29        table: &TableData,
30    ) -> Result<(), StorageError> {
31        let tables_dir = format!("{}/tables", workspace_path);
32
33        // Ensure tables directory exists
34        if !self.storage.dir_exists(&tables_dir).await? {
35            self.storage.create_dir(&tables_dir).await?;
36        }
37
38        // Determine file path
39        let file_path = if let Some(ref yaml_path) = table.yaml_file_path {
40            format!(
41                "{}/{}",
42                workspace_path,
43                yaml_path.strip_prefix('/').unwrap_or(yaml_path)
44            )
45        } else {
46            // Generate filename from table name
47            let sanitized_name = sanitize_filename(&table.name);
48            format!("{}/tables/{}.yaml", workspace_path, sanitized_name)
49        };
50
51        // Serialize table to YAML
52        let yaml_content = serde_yaml::to_string(&table.yaml_value).map_err(|e| {
53            StorageError::SerializationError(format!("Failed to serialize table: {}", e))
54        })?;
55
56        // Write to storage
57        self.storage
58            .write_file(&file_path, yaml_content.as_bytes())
59            .await?;
60
61        info!("Saved table '{}' to {}", table.name, file_path);
62        Ok(())
63    }
64
65    /// Save relationships to storage
66    ///
67    /// Saves relationships to `relationships.yaml` in the workspace directory.
68    pub async fn save_relationships(
69        &self,
70        workspace_path: &str,
71        relationships: &[RelationshipData],
72    ) -> Result<(), StorageError> {
73        let file_path = format!("{}/relationships.yaml", workspace_path);
74
75        // Serialize relationships to YAML
76        let mut yaml_map = serde_yaml::Mapping::new();
77        let mut rels_array = serde_yaml::Sequence::new();
78        for rel in relationships {
79            rels_array.push(rel.yaml_value.clone());
80        }
81        yaml_map.insert(
82            serde_yaml::Value::String("relationships".to_string()),
83            serde_yaml::Value::Sequence(rels_array),
84        );
85        let yaml_value = serde_yaml::Value::Mapping(yaml_map);
86
87        let yaml_content = serde_yaml::to_string(&yaml_value).map_err(|e| {
88            StorageError::SerializationError(format!("Failed to write YAML: {}", e))
89        })?;
90
91        // Write to storage
92        self.storage
93            .write_file(&file_path, yaml_content.as_bytes())
94            .await?;
95
96        info!(
97            "Saved {} relationships to {}",
98            relationships.len(),
99            file_path
100        );
101        Ok(())
102    }
103}
104
105/// Table data to save
106#[derive(Debug, Clone)]
107pub struct TableData {
108    pub id: Uuid,
109    pub name: String,
110    pub yaml_file_path: Option<String>,
111    pub yaml_value: serde_yaml::Value,
112}
113
114/// Relationship data to save
115#[derive(Debug, Clone)]
116pub struct RelationshipData {
117    pub id: Uuid,
118    pub source_table_id: Uuid,
119    pub target_table_id: Uuid,
120    pub yaml_value: serde_yaml::Value,
121}
122
123/// Sanitize a filename by removing invalid characters
124fn sanitize_filename(name: &str) -> String {
125    name.chars()
126        .map(|c| match c {
127            '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
128            _ => c,
129        })
130        .collect()
131}