use serde_json::{json, Value};
use crate::command::cmd::cmd_compare_and_set;
use crate::command::mutate::{build_raw_update, build_set_data, has_raw_operators};
use crate::command::write_links::attach_write_links;
use crate::command::{
ensure_context, forbid_t2q, ERR_NO_BATCH_WRITE, MODE_ATOMIC_COMMANDS,
};
use crate::permission::{can_write_schema, Context};
use crate::rbac::{ensure_write, merge_row_condition, WriteAction};
use crate::schema::Registry;
use crate::types::{validate_condition, validate_condition_shape};
use super::claim::ensure_fields_declared;
pub fn plan_compare_and_set(
schema_name: &str,
registry: &Registry,
ctx: Option<&Context>,
filter: &Value,
update: &Value,
now: i64,
) -> Result<Value, String> {
ensure_context(registry, ctx)?;
forbid_t2q(registry, "compareAndSet")?;
let schema = registry.get(schema_name)?;
validate_condition(filter)?;
validate_condition_shape(schema, filter, registry.profile())?;
if update.as_object().map(|o| o.is_empty()).unwrap_or(true) {
return Err(
"compareAndSet 的 update 必须为非空对象(无条件写入属编程错误)".to_string(),
);
}
if let Some(c) = ctx {
let deny_write =
crate::permission::has_any_role(c, ®istry.role_rules().deny_write_roles);
if deny_write || !can_write_schema(registry.role_rules(), schema, ctx) {
return Err(ERR_NO_BATCH_WRITE.to_string());
}
}
ensure_write(registry, schema, ctx, WriteAction::Update)?;
let eff_condition =
merge_row_condition(registry, schema, ctx, "update", Some(filter.clone()))
.unwrap_or_else(|| json!({}));
let update_doc = if has_raw_operators(update) {
build_raw_update(registry, schema, ctx, update, now)
} else {
let mut set_data = build_set_data(registry, schema, ctx, update);
if schema.timestamps {
set_data.insert("updatedAt".to_string(), json!(now));
}
json!({ "$set": Value::Object(set_data) })
};
ensure_fields_declared(schema, &update_doc)?;
let cmd = cmd_compare_and_set(schema, &eff_condition, &update_doc, now);
let plan = json!({
"mode": MODE_ATOMIC_COMMANDS,
"schema": schema_name,
"commands": [cmd],
});
attach_write_links(plan, registry, &[schema_name], registry.write_link_policy())
}