1use std::path::Path;
4
5use crate::errors::MdqlError;
6use crate::parser::parse_file;
7use crate::schema::MDQL_FILENAME;
8
9#[derive(Debug, Clone)]
10pub struct ForeignKey {
11 pub from_table: String,
12 pub from_column: String,
13 pub to_table: String,
14 pub to_column: String,
15}
16
17#[derive(Debug, Clone)]
18pub struct DatabaseConfig {
19 pub name: String,
20 pub foreign_keys: Vec<ForeignKey>,
21}
22
23pub fn load_database_config(db_dir: &Path) -> crate::errors::Result<DatabaseConfig> {
24 let db_path = db_dir.join(MDQL_FILENAME);
25 if !db_path.exists() {
26 return Err(MdqlError::DatabaseConfig(format!(
27 "No {} in {}",
28 MDQL_FILENAME,
29 db_dir.display()
30 )));
31 }
32
33 let parsed = parse_file(&db_path, Some(db_dir), false)?;
34
35 if !parsed.parse_errors.is_empty() {
36 return Err(MdqlError::DatabaseConfig(format!(
37 "Cannot parse {}: {}",
38 MDQL_FILENAME,
39 parsed.parse_errors.join("; ")
40 )));
41 }
42
43 let fm = &parsed.raw_frontmatter;
44 let fm_map = fm.as_mapping().ok_or_else(|| {
45 MdqlError::DatabaseConfig(format!(
46 "{}: frontmatter must be a mapping",
47 MDQL_FILENAME
48 ))
49 })?;
50
51 let type_val = fm_map.get(&serde_yaml::Value::String("type".into()));
52 if type_val.and_then(|v| v.as_str()) != Some("database") {
53 return Err(MdqlError::DatabaseConfig(format!(
54 "{}: frontmatter must have 'type: database'",
55 MDQL_FILENAME
56 )));
57 }
58
59 let name = fm_map
60 .get(&serde_yaml::Value::String("name".into()))
61 .and_then(|v| v.as_str())
62 .ok_or_else(|| {
63 MdqlError::DatabaseConfig(format!(
64 "{}: frontmatter must have 'name' as a string",
65 MDQL_FILENAME
66 ))
67 })?
68 .to_string();
69
70 let mut fks = Vec::new();
71 if let Some(fk_list) = fm_map.get(&serde_yaml::Value::String("foreign_keys".into())) {
72 if let Some(seq) = fk_list.as_sequence() {
73 for fk_def in seq {
74 let fk_map = fk_def.as_mapping().ok_or_else(|| {
75 MdqlError::DatabaseConfig(format!(
76 "{}: each foreign_key must be a mapping",
77 MDQL_FILENAME
78 ))
79 })?;
80
81 let from_spec = fk_map
82 .get(&serde_yaml::Value::String("from".into()))
83 .and_then(|v| v.as_str())
84 .unwrap_or("");
85 let to_spec = fk_map
86 .get(&serde_yaml::Value::String("to".into()))
87 .and_then(|v| v.as_str())
88 .unwrap_or("");
89
90 if !from_spec.contains('.') || !to_spec.contains('.') {
91 return Err(MdqlError::DatabaseConfig(format!(
92 "{}: foreign_key 'from' and 'to' must be 'table.column' format",
93 MDQL_FILENAME
94 )));
95 }
96
97 let (from_table, from_col) = from_spec.split_once('.').unwrap();
98 let (to_table, to_col) = to_spec.split_once('.').unwrap();
99
100 fks.push(ForeignKey {
101 from_table: from_table.to_string(),
102 from_column: from_col.to_string(),
103 to_table: to_table.to_string(),
104 to_column: to_col.to_string(),
105 });
106 }
107 }
108 }
109
110 Ok(DatabaseConfig {
111 name,
112 foreign_keys: fks,
113 })
114}