Skip to main content

state_engine/manifest/
mod.rs

1// Manifest impl
2use serde_json::Value;
3use std::collections::HashMap;
4use std::fs;
5use std::path::PathBuf;
6
7use crate::common::DotArrayAccessor;
8use crate::ports::provided;
9
10pub struct Manifest {
11    manifest_dir: PathBuf,
12    cache: HashMap<String, Value>,
13    missing_keys: Vec<String>,
14}
15
16impl Manifest {
17    pub fn new(manifest_dir: &str) -> Self {
18        Self {
19            manifest_dir: PathBuf::from(manifest_dir),
20            cache: HashMap::new(),
21            missing_keys: Vec::new(),
22        }
23    }
24
25    /// キーからデータを取得
26    /// 形式: "filename.path.to.key"
27    /// 例: "connection.common.host"
28    pub fn get(&mut self, key: &str, default: Option<Value>) -> Value {
29        let mut parts = key.splitn(2, '.');
30        let file = parts.next().unwrap_or("").to_string();
31        let path = parts.next().unwrap_or("").to_string();
32
33        if let Err(_e) = self.load_file(&file) {
34            self.missing_keys.push(key.to_string());
35            return default.unwrap_or(Value::Null);
36        }
37
38        let value = if path.is_empty() {
39            // ファイル全体を返す
40            self.cache.get(&file).cloned()
41        } else {
42            // ドット記法でアクセス
43            self.cache.get(&file).and_then(|data| {
44                let mut accessor = DotArrayAccessor::new();
45                accessor.get(data, &path).cloned()
46            })
47        };
48
49        match value {
50            Some(v) => {
51                self.remove_meta(&v)
52            }
53            None => {
54                self.missing_keys.push(key.to_string());
55                default.unwrap_or(Value::Null)
56            }
57        }
58    }
59
60    /// メタデータを取得
61    /// 指定されたキーのパス上のすべての_始まりキーを収集
62    /// _load.mapのキーとplaceholderを完全修飾パスに変換
63    pub fn get_meta(&mut self, key: &str) -> HashMap<String, Value> {
64        use regex::Regex;
65
66        let mut parts = key.splitn(2, '.');
67        let file = parts.next().unwrap_or("").to_string();
68        let path = parts.next().unwrap_or("").to_string();
69
70        if self.load_file(&file).is_err() {
71            self.missing_keys.push(key.to_string());
72            return HashMap::new();
73        }
74
75        let Some(root) = self.cache.get(&file) else {
76            return HashMap::new();
77        };
78
79        // ルートから指定Nodeまでのパス上のすべてのNodeを収集
80        let mut nodes = vec![root.clone()];
81        if !path.is_empty() {
82            let mut accessor = DotArrayAccessor::new();
83            let mut current = root;
84            for segment in path.split('.') {
85                current = match accessor.get(current, segment) {
86                    Some(node) => node,
87                    None => {
88                        self.missing_keys.push(key.to_string());
89                        return HashMap::new();
90                    }
91                };
92                nodes.push(current.clone());
93            }
94        }
95
96        let mut meta: HashMap<String, Value> = HashMap::new();
97        let segments: Vec<&str> = if path.is_empty() {
98            vec![]
99        } else {
100            path.split('.').collect()
101        };
102
103        // 完全修飾用のパス情報を記録
104        let mut meta_paths: HashMap<String, String> = HashMap::new();
105
106        // すべてのNodeから_始まりのキーを抽出してメタデータを構築
107        for (depth, node) in nodes.iter().enumerate() {
108            let Value::Object(obj) = node else {
109                continue;
110            };
111
112            // この node のパスを構築
113            let node_path = if depth == 0 {
114                file.clone()
115            } else {
116                let node_segments: Vec<&str> = segments.iter().copied().take(depth).collect();
117                if node_segments.is_empty() {
118                    file.clone()
119                } else {
120                    format!("{}.{}", file, node_segments.join("."))
121                }
122            };
123
124            // metadata を収集
125            for (k, v) in obj {
126                if k.starts_with('_') {
127                    // メタブロックのマージ/上書きルール
128                    if let Some(existing_value) = meta.get(k).cloned() {
129                        if existing_value.is_object() && v.is_object() {
130                            if let (Value::Object(existing_obj), Value::Object(new_obj)) = (&existing_value, v) {
131                                let mut merged = existing_obj.clone();
132                                for (child_key, child_value) in new_obj {
133                                    merged.insert(child_key.clone(), child_value.clone());
134                                }
135                                meta.insert(k.clone(), Value::Object(merged));
136                            }
137                        } else {
138                            meta.insert(k.clone(), v.clone());
139                        }
140                    } else {
141                        meta.insert(k.clone(), v.clone());
142                    }
143
144                    // パス情報を記録: _load.map のパスを記録
145                    if k == "_load" {
146                        if let Value::Object(load_obj) = v {
147                            if load_obj.contains_key("map") {
148                                meta_paths.insert("_load.map".to_string(), node_path.clone());
149                            }
150                        }
151                    }
152                }
153            }
154        }
155
156        // _load.map のキーを完全修飾
157        if let Some(map_parent) = meta_paths.get("_load.map") {
158            if let Some(Value::Object(load_obj)) = meta.get_mut("_load") {
159                if let Some(Value::Object(map_obj)) = load_obj.get("map").cloned() {
160                    let mut qualified_map = serde_json::Map::new();
161                    for (relative_key, db_column) in map_obj {
162                        qualified_map.insert(format!("{}.{}", map_parent, relative_key), db_column);
163                    }
164                    load_obj.insert("map".to_string(), Value::Object(qualified_map));
165                }
166            }
167        }
168
169        // placeholder を完全修飾
170        self.load_file(&file).ok();
171        let re = Regex::new(r"\$\{([^}]+)\}").unwrap();
172        let parent_path = path.rfind('.').map_or(String::new(), |pos| path[..pos].to_string());
173
174        for (_meta_key, meta_value) in meta.iter_mut() {
175            self.qualify_value(meta_value, &re, &file, &parent_path);
176        }
177
178        meta
179    }
180
181    /// Value内のplaceholderを再帰的に完全修飾(get_meta内でインライン使用)
182    fn qualify_value(
183        &mut self,
184        value: &mut Value,
185        re: &regex::Regex,
186        owner_file: &str,
187        parent_path: &str,
188    ) {
189        match value {
190            Value::String(s) => {
191                *s = re.replace_all(s, |caps: &regex::Captures| {
192                    let placeholder = &caps[1];
193
194                    // owner file内に存在するか
195                    if let Some(owner_data) = self.cache.get(owner_file) {
196                        if DotArrayAccessor::has(owner_data, placeholder) {
197                            return caps[0].to_string();
198                        }
199                    }
200
201                    // 別ファイル参照か
202                    let mut ph_parts = placeholder.splitn(2, '.');
203                    let ph_file = ph_parts.next().unwrap_or("").to_string();
204                    let ph_path = ph_parts.next().unwrap_or("").to_string();
205                    self.load_file(&ph_file).ok();
206
207                    if let Some(ph_data) = self.cache.get(&ph_file) {
208                        if let Some(obj) = ph_data.as_object() {
209                            if !obj.is_empty() && (ph_path.is_empty() || DotArrayAccessor::has(ph_data, &ph_path)) {
210                                return caps[0].to_string();
211                            }
212                        }
213                    }
214
215                    // 相対パス → 完全修飾
216                    if parent_path.is_empty() {
217                        caps[0].to_string()
218                    } else {
219                        format!("${{{}.{}.{}}}", owner_file, parent_path, placeholder)
220                    }
221                }).to_string();
222            }
223            Value::Object(obj) => {
224                for (_k, v) in obj.iter_mut() {
225                    self.qualify_value(v, re, owner_file, parent_path);
226                }
227            }
228            Value::Array(arr) => {
229                for v in arr.iter_mut() {
230                    self.qualify_value(v, re, owner_file, parent_path);
231                }
232            }
233            _ => {}
234        }
235    }
236
237    /// YAMLファイルをロード
238    /// .yml と .yaml の両方をサポート
239    /// 同名で両方の拡張子が存在する場合はエラー
240    fn load_file(&mut self, file: &str) -> Result<(), String> {
241        if self.cache.contains_key(file) {
242            return Ok(());
243        }
244
245        let yml_path = self.manifest_dir.join(format!("{}.yml", file));
246        let yaml_path = self.manifest_dir.join(format!("{}.yaml", file));
247
248        let yml_exists = yml_path.exists();
249        let yaml_exists = yaml_path.exists();
250
251        // 両方存在する場合はエラー
252        if yml_exists && yaml_exists {
253            self.cache.insert(file.to_string(), Value::Object(serde_json::Map::new()));
254            return Err(format!(
255                "Ambiguous file: both '{}.yml' and '{}.yaml' exist. Please use only one extension.",
256                file, file
257            ));
258        }
259
260        // どちらか存在する方を使用
261        let file_path = if yml_exists {
262            yml_path
263        } else if yaml_exists {
264            yaml_path
265        } else {
266            self.cache.insert(file.to_string(), Value::Object(serde_json::Map::new()));
267            return Err(format!("File not found: '{}.yml' or '{}.yaml'", file, file));
268        };
269
270        let content = fs::read_to_string(&file_path)
271            .map_err(|e| format!("Failed to read file: {}", e))?;
272
273        let yaml: serde_yaml_ng::Value = serde_yaml_ng::from_str(&content)
274            .map_err(|e| format!("Failed to parse YAML: {}", e))?;
275
276        let json_value = serde_json::to_value(&yaml)
277            .unwrap_or_else(|_| Value::Object(serde_json::Map::new()));
278        self.cache.insert(file.to_string(), json_value);
279
280        Ok(())
281    }
282
283    fn remove_meta(&self, value: &Value) -> Value {
284        match value {
285            Value::Object(obj) => {
286                let mut filtered = serde_json::Map::new();
287                for (k, v) in obj {
288                    if !k.starts_with('_') {
289                        filtered.insert(k.clone(), self.remove_meta(v));
290                    }
291                }
292                Value::Object(filtered)
293            }
294            Value::Array(arr) => {
295                Value::Array(arr.iter().map(|v| self.remove_meta(v)).collect())
296            }
297            _ => value.clone(),
298        }
299    }
300
301    pub fn get_missing_keys(&self) -> &[String] {
302        &self.missing_keys
303    }
304
305    pub fn clear_missing_keys(&mut self) {
306        self.missing_keys.clear();
307    }
308}
309
310// Provided::Manifest trait の実装
311impl provided::Manifest for Manifest {
312    fn get(&mut self, key: &str, default: Option<Value>) -> Value {
313        Manifest::get(self, key, default)
314    }
315
316    fn get_meta(&mut self, key: &str) -> HashMap<String, Value> {
317        Manifest::get_meta(self, key)
318    }
319
320    fn get_missing_keys(&self) -> &[String] {
321        Manifest::get_missing_keys(self)
322    }
323
324    fn clear_missing_keys(&mut self) {
325        Manifest::clear_missing_keys(self)
326    }
327}
328