1use serde_json::{json, Map, Value};
4
5use crate::computes::{apply_defaults_and_computes, FnRegistry};
6use crate::permission::{
7 can_write_schema, evaluate, filter_writable_data, merge_owner_condition, Context, Doc,
8};
9use crate::schema::{Registry, Schema};
10use crate::types::{
11 has_relation_predicate, is_truthy, validate_condition, validate_condition_shape,
12};
13
14use super::cmd::{cmd_count_documents, cmd_find_one, cmd_insert_one};
15use super::{ensure_context, ERR_NO_WRITE};
16
17pub fn plan_insert(
21 schema_name: &str,
22 registry: &Registry,
23 ctx: Option<&Context>,
24 data: &Value,
25 now: i64,
26 new_id: &str,
27 fn_registry: Option<&dyn FnRegistry>,
28) -> Result<Value, String> {
29 ensure_context(registry, ctx)?;
30 let schema = registry.get(schema_name)?;
31
32 if !can_write_schema(schema, ctx) {
33 return Err(ERR_NO_WRITE.to_string());
34 }
35
36 let doc = build_insert_doc(schema, ctx, data, now, new_id)?;
37 let doc = Value::Object(doc);
38 Ok(json!({
39 "command": cmd_insert_one(schema, &doc),
40 "returns": apply_defaults_and_computes(&doc, schema, fn_registry)?,
41 }))
42}
43
44pub(super) fn build_insert_doc(
47 schema: &Schema,
48 ctx: Option<&Context>,
49 data: &Value,
50 now: i64,
51 new_id: &str,
52) -> Result<Map<String, Value>, String> {
53 let filtered = match ctx {
54 Some(c) => filter_writable_data(schema, Some(c), data),
55 None => data.clone(),
56 };
57
58 let mut doc: Map<String, Value> = filtered.as_object().cloned().unwrap_or_default();
61
62 if !doc.get("_id").map(is_truthy).unwrap_or(false) {
66 if schema.id_is_autoincrement() {
67 } else if !schema.id_prefix.is_empty() {
72 doc.insert("_id".to_string(), json!(new_id));
73 } else {
74 return Err(format!(
75 "schema \"{}\" 未配置 idPrefix 且未提供有效 _id:请显式提供 _id,或为 schema 配置 idPrefix",
76 schema.name
77 ));
78 }
79 }
80
81 if has_creator_permission(schema) && !doc.get("createdBy").map(is_truthy).unwrap_or(false) {
83 if let Some(uid) = ctx.and_then(|c| c.user_id.clone()) {
84 doc.insert("createdBy".to_string(), json!(uid));
85 }
86 }
87
88 if schema.timestamps {
90 if !doc.get("createdAt").map(is_truthy).unwrap_or(false) {
91 doc.insert("createdAt".to_string(), json!(now));
92 }
93 doc.insert("updatedAt".to_string(), json!(now));
94 }
95 Ok(doc)
96}
97
98pub fn plan_exists(
100 schema_name: &str,
101 registry: &Registry,
102 condition: &Value,
103) -> Result<Value, String> {
104 validate_condition(condition)?;
106 let schema = registry.get(schema_name)?;
107 validate_condition_shape(schema, condition, registry.profile())?;
109 if has_relation_predicate(schema, condition) {
111 return Err(
112 "exists 不支持关系聚合谓词条件(§9.6):标量查询无法表达关系谓词,\
113 请改用 query 并自行判断是否有结果"
114 .to_string(),
115 );
116 }
117 Ok(cmd_find_one(schema, condition, Some(&json!({ "_id": 1 }))))
118}
119
120pub fn plan_count(
125 schema_name: &str,
126 registry: &Registry,
127 filter: Option<&Value>,
128 ctx: Option<&Context>,
129) -> Result<Value, String> {
130 if let Some(f) = filter {
131 validate_condition(f)?;
133 }
134 let schema = registry.get(schema_name)?;
135 if let Some(f) = filter {
137 validate_condition_shape(schema, f, registry.profile())?;
138 if has_relation_predicate(schema, f) {
140 return Err(
141 "count 不支持关系聚合谓词条件(§9.6):标量计数无法表达关系谓词,\
142 请改用 query 并在调用方自行统计"
143 .to_string(),
144 );
145 }
146 }
147 let base = match filter {
148 None | Some(Value::Null) => json!({}),
149 Some(v) => v.clone(),
150 };
151 let filter = merge_owner_condition(schema, ctx, Some(base)).unwrap_or_else(|| json!({}));
152 Ok(cmd_count_documents(schema, &filter))
153}
154
155pub(super) fn has_creator_permission(schema: &Schema) -> bool {
156 let has = |list: &Option<Vec<String>>| {
157 list.as_ref()
158 .map(|l| l.iter().any(|r| r == "creator"))
159 .unwrap_or(false)
160 };
161 has(&schema.read) || has(&schema.write)
162}
163
164#[derive(Debug, Clone, Copy)]
171pub enum Probe<'a> {
172 NotProbed,
173 NoResult,
174 Found(&'a Value),
175}
176
177pub fn check_write_perm(
183 schema: &Schema,
184 ctx: Option<&Context>,
185 condition: &Value,
186 deny_msg: &str,
187 probe: Probe,
188) -> Result<Option<Value>, String> {
189 let Some(c) = ctx else {
191 return Ok(None);
192 };
193 if c.roles
194 .clone()
195 .unwrap_or_default()
196 .iter()
197 .any(|r| r == "guest")
198 {
199 return Err(deny_msg.to_string());
200 }
201 if can_write_schema(schema, Some(c)) {
202 return Ok(None);
203 }
204 let creator_only = schema
205 .write
206 .as_ref()
207 .map(|w| w.iter().any(|r| r == "creator"))
208 .unwrap_or(false);
209 if creator_only && is_truthy(condition) {
210 return match probe {
211 Probe::NotProbed => Ok(Some(cmd_find_one(
212 schema,
213 condition,
214 Some(&json!({ "_id": 1, "createdBy": 1 })),
215 ))),
216 Probe::NoResult => Err(deny_msg.to_string()),
217 Probe::Found(doc) => {
218 if evaluate(Some(c), schema.write.as_deref(), Doc::Doc(doc)) {
219 Ok(None)
220 } else {
221 Err(deny_msg.to_string())
222 }
223 }
224 };
225 }
226 Err(deny_msg.to_string())
227}