1use std::collections::HashMap;
4
5use serde_json::{Map, Value};
6
7use crate::datasource::DEFAULT_SOURCE;
8use crate::types::{is_truthy, str_list};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Role {
14 Primary,
16 Replica,
18 Cache,
20 Archive,
22}
23
24impl Role {
25 pub fn parse(s: &str) -> Result<Role, String> {
27 match s {
28 "primary" => Ok(Role::Primary),
29 "replica" => Ok(Role::Replica),
30 "cache" => Ok(Role::Cache),
31 "archive" => Ok(Role::Archive),
32 other => Err(format!(
33 "未知链路角色: {other}(仅 primary/replica/cache/archive)"
34 )),
35 }
36 }
37
38 pub fn as_str(&self) -> &'static str {
39 match self {
40 Role::Primary => "primary",
41 Role::Replica => "replica",
42 Role::Cache => "cache",
43 Role::Archive => "archive",
44 }
45 }
46
47 pub fn rank(&self) -> u8 {
49 match self {
50 Role::Primary => 0,
51 Role::Replica => 1,
52 Role::Cache => 2,
53 Role::Archive => 3,
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Location {
63 pub source: String,
65 pub database: Option<String>,
67 pub schema: Option<String>,
69 pub role: Option<Role>,
71 pub priority: Option<i64>,
73}
74
75impl Default for Location {
76 fn default() -> Self {
77 Self {
78 source: DEFAULT_SOURCE.to_string(),
79 database: None,
80 schema: None,
81 role: None,
82 priority: None,
83 }
84 }
85}
86
87impl Location {
88 pub fn same_place(&self, other: &Location) -> bool {
92 self.source == other.source
93 && self.database == other.database
94 && self.schema == other.schema
95 }
96}
97
98#[derive(Debug, Clone)]
99pub struct FieldDef {
100 pub field_type: String,
101 pub required: bool,
102 pub default: Option<Value>,
103 pub read: Option<Vec<String>>,
104 pub write: Option<Vec<String>>,
105 pub fields: Option<Value>,
107 pub strategy: Option<String>,
111}
112
113#[derive(Debug, Clone)]
114pub struct RelationDef {
115 pub model: String,
116 pub rel_type: String,
117 pub local_field: String,
118 pub foreign_field: String,
119 pub read: Option<Vec<String>>,
120}
121
122#[derive(Debug, Clone)]
123pub struct ComputeDef {
124 pub comp_type: String,
125 pub has_fn: bool,
126 pub has_async_fn: bool,
127 pub fn_ref: Option<String>,
129 pub agg: Option<Value>,
132 pub depends: Vec<String>,
133 pub read: Option<Vec<String>>,
134}
135
136#[derive(Debug, Clone)]
137pub struct Schema {
138 pub name: String,
139 pub collection: String,
140 pub id_prefix: String,
141 pub timestamps: bool,
142 pub fields: HashMap<String, FieldDef>,
143 pub relations: HashMap<String, RelationDef>,
144 pub computes: Vec<(String, ComputeDef)>,
146 pub triggers: super::triggers::Triggers,
148 pub read: Option<Vec<String>>,
149 pub write: Option<Vec<String>>,
150 pub indexes: Vec<Value>,
152 pub source: Option<String>,
155 pub database: Option<String>,
158 pub schema: Option<String>,
160}
161
162impl Schema {
163 pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
164 self.computes
165 .iter()
166 .find(|(k, _)| k == name)
167 .map(|(_, c)| c)
168 }
169
170 pub fn source(&self) -> &str {
172 self.source
173 .as_deref()
174 .unwrap_or(crate::datasource::DEFAULT_SOURCE)
175 }
176
177 pub fn database(&self) -> Option<&str> {
179 self.database.as_deref().filter(|s| !s.is_empty())
180 }
181
182 pub fn schema(&self) -> Option<&str> {
184 self.schema.as_deref().filter(|s| !s.is_empty())
185 }
186
187 pub fn location(&self) -> Location {
191 Location {
192 source: self.source().to_string(),
193 database: self.database().map(String::from),
194 schema: self.schema().map(String::from),
195 role: None,
196 priority: None,
197 }
198 }
199
200 pub fn id_is_autoincrement(&self) -> bool {
202 self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
203 }
204}
205
206pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
208 let mut out = HashMap::new();
209 let Some(Value::Object(map)) = v else {
210 return Ok(out);
211 };
212 for (key, val) in map {
213 let fd = if let Some(s) = val.as_str() {
214 FieldDef {
215 field_type: s.to_string(),
216 required: false,
217 default: None,
218 read: None,
219 write: None,
220 fields: None,
221 strategy: None,
222 }
223 } else if let Some(o) = val.as_object() {
224 FieldDef {
225 field_type: o
226 .get("type")
227 .and_then(|v| v.as_str())
228 .unwrap_or("")
229 .to_string(),
230 required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
231 default: o.get("default").filter(|v| !v.is_null()).cloned(),
232 read: str_list(o.get("read")),
233 write: str_list(o.get("write")),
234 fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
235 strategy: match o.get("strategy").and_then(|v| v.as_str()) {
236 None => None,
237 Some(v @ "autoincrement") => Some(v.to_string()),
238 Some(other) => {
240 return Err(format!(
241 "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
242 ))
243 }
244 },
245 }
246 } else {
247 return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
251 };
252 out.insert(key.clone(), fd);
253 }
254 Ok(out)
255}
256
257pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
259 let mut m = Map::new();
260 for (k, v) in pairs {
261 m.insert(k.to_string(), v);
262 }
263 m
264}