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 read: Option<Vec<String>>,
81 pub write: Option<Vec<String>>,
82 pub indexes: Vec<Value>,
84 pub source: Option<String>,
87 pub database: Option<String>,
90 pub schema: Option<String>,
92}
93
94impl Schema {
95 pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
96 self.computes
97 .iter()
98 .find(|(k, _)| k == name)
99 .map(|(_, c)| c)
100 }
101
102 pub fn source(&self) -> &str {
104 self.source
105 .as_deref()
106 .unwrap_or(crate::datasource::DEFAULT_SOURCE)
107 }
108
109 pub fn database(&self) -> Option<&str> {
111 self.database.as_deref().filter(|s| !s.is_empty())
112 }
113
114 pub fn schema(&self) -> Option<&str> {
116 self.schema.as_deref().filter(|s| !s.is_empty())
117 }
118
119 pub fn location(&self) -> Location {
123 Location {
124 source: self.source().to_string(),
125 database: self.database().map(String::from),
126 schema: self.schema().map(String::from),
127 }
128 }
129
130 pub fn id_is_autoincrement(&self) -> bool {
132 self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
133 }
134}
135
136pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
138 let mut out = HashMap::new();
139 let Some(Value::Object(map)) = v else {
140 return Ok(out);
141 };
142 for (key, val) in map {
143 let fd = if let Some(s) = val.as_str() {
144 FieldDef {
145 field_type: s.to_string(),
146 required: false,
147 default: None,
148 read: None,
149 write: None,
150 fields: None,
151 strategy: None,
152 }
153 } else if let Some(o) = val.as_object() {
154 FieldDef {
155 field_type: o
156 .get("type")
157 .and_then(|v| v.as_str())
158 .unwrap_or("")
159 .to_string(),
160 required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
161 default: o.get("default").filter(|v| !v.is_null()).cloned(),
162 read: str_list(o.get("read")),
163 write: str_list(o.get("write")),
164 fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
165 strategy: match o.get("strategy").and_then(|v| v.as_str()) {
166 None => None,
167 Some(v @ "autoincrement") => Some(v.to_string()),
168 Some(other) => {
170 return Err(format!(
171 "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
172 ))
173 }
174 },
175 }
176 } else {
177 return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
181 };
182 out.insert(key.clone(), fd);
183 }
184 Ok(out)
185}
186
187pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
189 let mut m = Map::new();
190 for (k, v) in pairs {
191 m.insert(k.to_string(), v);
192 }
193 m
194}