rust_store_core/schema/
definition.rs1use 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, PartialEq, Eq)]
13pub struct Location {
14 pub source: String,
16 pub database: Option<String>,
18 pub schema: Option<String>,
20}
21
22impl Default for Location {
23 fn default() -> Self {
24 Self {
25 source: DEFAULT_SOURCE.to_string(),
26 database: None,
27 schema: None,
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
33pub struct FieldDef {
34 pub field_type: String,
35 pub required: bool,
36 pub default: Option<Value>,
37 pub read: Option<Vec<String>>,
38 pub write: Option<Vec<String>>,
39 pub fields: Option<Value>,
41 pub strategy: Option<String>,
45}
46
47#[derive(Debug, Clone)]
48pub struct RelationDef {
49 pub model: String,
50 pub rel_type: String,
51 pub local_field: String,
52 pub foreign_field: String,
53 pub read: Option<Vec<String>>,
54}
55
56#[derive(Debug, Clone)]
57pub struct ComputeDef {
58 pub comp_type: String,
59 pub has_fn: bool,
60 pub has_async_fn: bool,
61 pub fn_ref: Option<String>,
63 pub agg: Option<Value>,
66 pub depends: Vec<String>,
67 pub read: Option<Vec<String>>,
68}
69
70#[derive(Debug, Clone)]
71pub struct Schema {
72 pub name: String,
73 pub collection: String,
74 pub id_prefix: String,
75 pub timestamps: bool,
76 pub fields: HashMap<String, FieldDef>,
77 pub relations: HashMap<String, RelationDef>,
78 pub computes: Vec<(String, ComputeDef)>,
80 pub triggers: super::triggers::Triggers,
82 pub read: Option<Vec<String>>,
83 pub write: Option<Vec<String>>,
84 pub indexes: Vec<Value>,
86 pub source: Option<String>,
89 pub database: Option<String>,
92 pub schema: Option<String>,
94}
95
96impl Schema {
97 pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
98 self.computes
99 .iter()
100 .find(|(k, _)| k == name)
101 .map(|(_, c)| c)
102 }
103
104 pub fn source(&self) -> &str {
106 self.source
107 .as_deref()
108 .unwrap_or(crate::datasource::DEFAULT_SOURCE)
109 }
110
111 pub fn database(&self) -> Option<&str> {
113 self.database.as_deref().filter(|s| !s.is_empty())
114 }
115
116 pub fn schema(&self) -> Option<&str> {
118 self.schema.as_deref().filter(|s| !s.is_empty())
119 }
120
121 pub fn location(&self) -> Location {
125 Location {
126 source: self.source().to_string(),
127 database: self.database().map(String::from),
128 schema: self.schema().map(String::from),
129 }
130 }
131
132 pub fn id_is_autoincrement(&self) -> bool {
134 self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
135 }
136}
137
138pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
140 let mut out = HashMap::new();
141 let Some(Value::Object(map)) = v else {
142 return Ok(out);
143 };
144 for (key, val) in map {
145 let fd = if let Some(s) = val.as_str() {
146 FieldDef {
147 field_type: s.to_string(),
148 required: false,
149 default: None,
150 read: None,
151 write: None,
152 fields: None,
153 strategy: None,
154 }
155 } else if let Some(o) = val.as_object() {
156 FieldDef {
157 field_type: o
158 .get("type")
159 .and_then(|v| v.as_str())
160 .unwrap_or("")
161 .to_string(),
162 required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
163 default: o.get("default").filter(|v| !v.is_null()).cloned(),
164 read: str_list(o.get("read")),
165 write: str_list(o.get("write")),
166 fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
167 strategy: match o.get("strategy").and_then(|v| v.as_str()) {
168 None => None,
169 Some(v @ "autoincrement") => Some(v.to_string()),
170 Some(other) => {
172 return Err(format!(
173 "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
174 ))
175 }
176 },
177 }
178 } else {
179 return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
183 };
184 out.insert(key.clone(), fd);
185 }
186 Ok(out)
187}
188
189pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
191 let mut m = Map::new();
192 for (k, v) in pairs {
193 m.insert(k.to_string(), v);
194 }
195 m
196}