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_prefix.is_empty() {
67 doc.insert("_id".to_string(), json!(new_id));
68 } else {
69 return Err(format!(
70 "schema \"{}\" 未配置 idPrefix 且未提供有效 _id:请显式提供 _id,或为 schema 配置 idPrefix",
71 schema.name
72 ));
73 }
74 }
75
76 if has_creator_permission(schema) && !doc.get("createdBy").map(is_truthy).unwrap_or(false) {
78 if let Some(uid) = ctx.and_then(|c| c.user_id.clone()) {
79 doc.insert("createdBy".to_string(), json!(uid));
80 }
81 }
82
83 if schema.timestamps {
85 if !doc.get("createdAt").map(is_truthy).unwrap_or(false) {
86 doc.insert("createdAt".to_string(), json!(now));
87 }
88 doc.insert("updatedAt".to_string(), json!(now));
89 }
90 Ok(doc)
91}
92
93pub fn plan_exists(
95 schema_name: &str,
96 registry: &Registry,
97 condition: &Value,
98) -> Result<Value, String> {
99 validate_condition(condition)?;
101 let schema = registry.get(schema_name)?;
102 validate_condition_shape(schema, condition, registry.profile())?;
104 if has_relation_predicate(schema, condition) {
106 return Err(
107 "exists 不支持关系聚合谓词条件(§9.6):标量查询无法表达关系谓词,\
108 请改用 query 并自行判断是否有结果"
109 .to_string(),
110 );
111 }
112 Ok(cmd_find_one(schema, condition, Some(&json!({ "_id": 1 }))))
113}
114
115pub fn plan_count(
120 schema_name: &str,
121 registry: &Registry,
122 filter: Option<&Value>,
123 ctx: Option<&Context>,
124) -> Result<Value, String> {
125 if let Some(f) = filter {
126 validate_condition(f)?;
128 }
129 let schema = registry.get(schema_name)?;
130 if let Some(f) = filter {
132 validate_condition_shape(schema, f, registry.profile())?;
133 if has_relation_predicate(schema, f) {
135 return Err(
136 "count 不支持关系聚合谓词条件(§9.6):标量计数无法表达关系谓词,\
137 请改用 query 并在调用方自行统计"
138 .to_string(),
139 );
140 }
141 }
142 let base = match filter {
143 None | Some(Value::Null) => json!({}),
144 Some(v) => v.clone(),
145 };
146 let filter = merge_owner_condition(schema, ctx, Some(base)).unwrap_or_else(|| json!({}));
147 Ok(cmd_count_documents(schema, &filter))
148}
149
150pub(super) fn has_creator_permission(schema: &Schema) -> bool {
151 let has = |list: &Option<Vec<String>>| {
152 list.as_ref()
153 .map(|l| l.iter().any(|r| r == "creator"))
154 .unwrap_or(false)
155 };
156 has(&schema.read) || has(&schema.write)
157}
158
159#[derive(Debug, Clone, Copy)]
166pub enum Probe<'a> {
167 NotProbed,
168 NoResult,
169 Found(&'a Value),
170}
171
172pub fn check_write_perm(
178 schema: &Schema,
179 ctx: Option<&Context>,
180 condition: &Value,
181 deny_msg: &str,
182 probe: Probe,
183) -> Result<Option<Value>, String> {
184 let Some(c) = ctx else {
186 return Ok(None);
187 };
188 if c.roles
189 .clone()
190 .unwrap_or_default()
191 .iter()
192 .any(|r| r == "guest")
193 {
194 return Err(deny_msg.to_string());
195 }
196 if can_write_schema(schema, Some(c)) {
197 return Ok(None);
198 }
199 let creator_only = schema
200 .write
201 .as_ref()
202 .map(|w| w.iter().any(|r| r == "creator"))
203 .unwrap_or(false);
204 if creator_only && is_truthy(condition) {
205 return match probe {
206 Probe::NotProbed => Ok(Some(cmd_find_one(
207 schema,
208 condition,
209 Some(&json!({ "_id": 1, "createdBy": 1 })),
210 ))),
211 Probe::NoResult => Err(deny_msg.to_string()),
212 Probe::Found(doc) => {
213 if evaluate(Some(c), schema.write.as_deref(), Doc::Doc(doc)) {
214 Ok(None)
215 } else {
216 Err(deny_msg.to_string())
217 }
218 }
219 };
220 }
221 Err(deny_msg.to_string())
222}