Skip to main content

nounsql_core/
config.rs

1use std::collections::HashMap;
2
3use crate::ast::{ConfigBlock, Document, Value};
4use crate::diag::Diagnostic;
5use crate::dict::Compound;
6use crate::span::Span;
7use crate::template::Template;
8
9/// `naming` の既定値。仕様の `### naming` と一致させる。
10const DEFAULT_NAMING: &[(&str, &str)] = &[
11    ("primary_key", "id"),
12    ("foreign_key", "${singular(table)}_id"),
13    ("index", "idx_${table}_${columns}"),
14    ("unique_index", "uq_${table}_${columns}"),
15    ("column_separator", "_"),
16    ("noun_separator", "_"),
17    ("belongs_to", "${singular(table)}"),
18    ("has_many", "${plural(table)}"),
19    ("has_one", "${singular(table)}"),
20];
21
22const NAMING_KEYS: &[&str] = &[
23    "table_name",
24    "primary_key",
25    "foreign_key",
26    "index",
27    "unique_index",
28    "column_separator",
29    "noun_separator",
30    "belongs_to",
31    "has_many",
32    "has_one",
33];
34
35const CONSTRAINT_KEYS: &[&str] = &[
36    "null_default",
37    "on_delete_default",
38    "on_update_default",
39    "foreign_key_index",
40];
41
42const REFERENTIAL_ACTIONS: &[&str] = &["cascade", "restrict", "set_null", "no_action"];
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum TableNameStyle {
46    Plural,
47    Singular,
48}
49
50#[derive(Debug, Clone)]
51pub struct Naming {
52    pub table_name: TableNameStyle,
53    pub primary_key: String,
54    pub foreign_key: Template,
55    pub index: Template,
56    pub unique_index: Template,
57    pub column_separator: String,
58    pub noun_separator: String,
59    pub belongs_to: Template,
60    pub has_many: Template,
61    pub has_one: Template,
62}
63
64#[derive(Debug, Clone)]
65pub struct Constraints {
66    pub null_default: bool,
67    pub on_delete_default: String,
68    pub on_update_default: String,
69    pub foreign_key_index: bool,
70}
71
72#[derive(Debug, Clone)]
73pub struct Config {
74    pub naming: Naming,
75    pub constraints: Constraints,
76}
77
78impl Default for Config {
79    fn default() -> Self {
80        let t = |key: &str| {
81            let src = DEFAULT_NAMING
82                .iter()
83                .find(|(k, _)| *k == key)
84                .map(|(_, v)| *v)
85                .unwrap_or_default();
86            Template::parse(src).unwrap_or(Template { segments: vec![] })
87        };
88        Config {
89            naming: Naming {
90                table_name: TableNameStyle::Singular,
91                primary_key: "id".into(),
92                foreign_key: t("foreign_key"),
93                index: t("index"),
94                unique_index: t("unique_index"),
95                column_separator: "_".into(),
96                noun_separator: "_".into(),
97                belongs_to: t("belongs_to"),
98                has_many: t("has_many"),
99                has_one: t("has_one"),
100            },
101            constraints: Constraints {
102                null_default: false,
103                on_delete_default: "cascade".into(),
104                on_update_default: "cascade".into(),
105                foreign_key_index: true,
106            },
107        }
108    }
109}
110
111impl Config {
112    pub fn from_document(doc: &Document, diags: &mut Vec<Diagnostic>) -> Config {
113        let mut config = Config::default();
114        if let Some(block) = &doc.naming {
115            config.apply_naming(block, diags);
116        }
117        if let Some(block) = &doc.constraints {
118            config.apply_constraints(block, diags);
119        }
120        config
121    }
122
123    fn apply_naming(&mut self, block: &ConfigBlock, diags: &mut Vec<Diagnostic>) {
124        for entry in &block.entries {
125            let key = entry.key.value.as_str();
126            if !NAMING_KEYS.contains(&key) {
127                diags.push(Diagnostic::error(
128                    entry.key.span,
129                    format!("知らない `naming` のキー `{key}`"),
130                ));
131                continue;
132            }
133            let span = entry.value.span;
134            match key {
135                "table_name" => match value_ident(&entry.value.value) {
136                    Some("plural") => self.naming.table_name = TableNameStyle::Plural,
137                    Some("singular") => self.naming.table_name = TableNameStyle::Singular,
138                    _ => diags.push(Diagnostic::error(
139                        span,
140                        "`table_name` は `plural` か `singular`",
141                    )),
142                },
143                "primary_key" | "column_separator" | "noun_separator" => {
144                    match value_string(&entry.value.value) {
145                        Some(s) => match key {
146                            "primary_key" => self.naming.primary_key = s.into(),
147                            "column_separator" => self.naming.column_separator = s.into(),
148                            _ => self.naming.noun_separator = s.into(),
149                        },
150                        None => {
151                            diags.push(Diagnostic::error(span, format!("`{key}` には文字列を書く")))
152                        }
153                    }
154                }
155                _ => {
156                    let Some(s) = value_string(&entry.value.value) else {
157                        diags.push(Diagnostic::error(span, format!("`{key}` には文字列を書く")));
158                        continue;
159                    };
160                    match Template::parse(s) {
161                        Ok(t) => match key {
162                            "foreign_key" => self.naming.foreign_key = t,
163                            "index" => self.naming.index = t,
164                            "unique_index" => self.naming.unique_index = t,
165                            "belongs_to" => self.naming.belongs_to = t,
166                            "has_many" => self.naming.has_many = t,
167                            "has_one" => self.naming.has_one = t,
168                            _ => {}
169                        },
170                        Err(msg) => diags.push(Diagnostic::error(span, msg)),
171                    }
172                }
173            }
174        }
175    }
176
177    fn apply_constraints(&mut self, block: &ConfigBlock, diags: &mut Vec<Diagnostic>) {
178        for entry in &block.entries {
179            let key = entry.key.value.as_str();
180            if !CONSTRAINT_KEYS.contains(&key) {
181                diags.push(Diagnostic::error(
182                    entry.key.span,
183                    format!("知らない `constraints` のキー `{key}`"),
184                ));
185                continue;
186            }
187            let span = entry.value.span;
188            match key {
189                "null_default" | "foreign_key_index" => match value_bool(&entry.value.value) {
190                    Some(b) => {
191                        if key == "null_default" {
192                            self.constraints.null_default = b;
193                        } else {
194                            self.constraints.foreign_key_index = b;
195                        }
196                    }
197                    None => diags.push(Diagnostic::error(
198                        span,
199                        format!("`{key}` は `true` か `false`"),
200                    )),
201                },
202                _ => match value_ident(&entry.value.value) {
203                    Some(a) if REFERENTIAL_ACTIONS.contains(&a) => {
204                        if key == "on_delete_default" {
205                            self.constraints.on_delete_default = a.into();
206                        } else {
207                            self.constraints.on_update_default = a.into();
208                        }
209                    }
210                    _ => diags.push(Diagnostic::error(
211                        span,
212                        format!("`{key}` は {} のいずれか", REFERENTIAL_ACTIONS.join(" / ")),
213                    )),
214                },
215            }
216        }
217    }
218}
219
220fn value_ident(v: &Value) -> Option<&str> {
221    match v {
222        Value::Ident(s) => Some(s),
223        _ => None,
224    }
225}
226
227fn value_string(v: &Value) -> Option<&str> {
228    match v {
229        Value::Str(s) => Some(s),
230        _ => None,
231    }
232}
233
234fn value_bool(v: &Value) -> Option<bool> {
235    match value_ident(v)? {
236        "true" => Some(true),
237        "false" => Some(false),
238        _ => None,
239    }
240}
241
242/// テンプレート展開の変数束縛。
243pub type Vars = HashMap<&'static str, Compound>;
244
245pub fn missing_var(span: Span, name: &str) -> Diagnostic {
246    Diagnostic::error(
247        span,
248        format!("テンプレート変数 `{name}` はここでは使えない"),
249    )
250}