1use std::path::Path;
10
11use crate::containment::PathGuard;
12use crate::ops;
13use crate::ops::doc::query::{QueryResult, query_get, query_has};
14use crate::plan::Operation;
15
16use super::{ApplyMode, EditResult};
17
18fn load_doc_value(path: &Path) -> anyhow::Result<serde_json::Value> {
23 ops::doc::load_for_query(path)
24}
25
26#[cfg(any(feature = "cli", feature = "files"))]
29fn doc_write(
30 op: Operation,
31 path: &Path,
32 mode: ApplyMode,
33 guard: Option<&PathGuard>,
34 action: &'static str,
35) -> anyhow::Result<EditResult> {
36 let abs = super::library_abs_path(path, guard)?;
37 let mut op = op;
38 rewrite_op_path(&mut op, &super::library_op_path(path, &abs, guard));
39 let display = path.to_string_lossy();
40 super::execute_as_edit_result_with_path(
41 op,
42 mode,
43 super::library_project_root(&abs, guard),
44 guard,
45 action,
46 None,
47 Some(display.as_ref()),
48 )
49}
50
51#[cfg(any(feature = "cli", feature = "files"))]
53fn rewrite_op_path(op: &mut Operation, dest: &str) {
54 match op {
55 Operation::DocSet { path, .. }
56 | Operation::DocDelete { path, .. }
57 | Operation::DocMerge { path, .. }
58 | Operation::DocAppend { path, .. }
59 | Operation::DocPrepend { path, .. }
60 | Operation::DocUpdate { path, .. }
61 | Operation::DocMove { path, .. }
62 | Operation::DocEnsure { path, .. }
63 | Operation::DocDeleteWhere { path, .. } => {
64 *path = dest.into();
65 }
66 _ => {}
67 }
68}
69
70#[cfg(not(any(feature = "cli", feature = "files")))]
71fn doc_write(
72 op: Operation,
73 path: &Path,
74 mode: ApplyMode,
75 guard: Option<&PathGuard>,
76 action: &'static str,
77) -> anyhow::Result<EditResult> {
78 use crate::ops::doc::MutationResult;
79 use crate::write::WritePolicy;
80
81 let (_, mutation) = crate::plan::op_to_doc_mutation(&op)
83 .ok_or_else(|| anyhow::anyhow!("doc_write called with non-doc operation"))?;
84
85 let display = path.to_string_lossy().into_owned();
86 let path_owned = super::library_abs_path(path, guard)?;
87 let path = path_owned.as_path();
88 let path_str = display;
89 let format = ops::doc::detect_format(&path_str)?;
90 let if_exists_set = matches!(
91 op,
92 Operation::DocSet {
93 if_exists: true,
94 ..
95 }
96 );
97 let original = match crate::files::load_text_strict(path, &path_str) {
98 Ok(s) => s,
99 Err(e) if if_exists_set && crate::exit::is_io_not_found(&e) => {
100 return Ok(super::build_edit_result(
101 &path_str,
102 String::new(),
103 String::new(),
104 false,
105 action,
106 None,
107 ));
108 }
109 Err(e) => return Err(e),
110 };
111 let value = ops::doc::parse_doc(&original, &format)?;
112 if if_exists_set
113 && let Operation::DocSet { selector, .. } = &op
114 && !ops::doc::query::query_has(&value, selector)?
115 {
116 return Ok(super::build_edit_result(
117 &path_str,
118 original.clone(),
119 original,
120 false,
121 action,
122 None,
123 ));
124 }
125 let mut new_value = value.clone();
126
127 let result = ops::doc::apply_doc_mutation(&mut new_value, mutation)?;
128 if let MutationResult::TypeError(msg) = result {
129 return Err(anyhow::Error::new(crate::exit::TypeErrorError { msg }));
130 }
131 let removed = match &result {
132 MutationResult::Removed(n) => *n,
133 MutationResult::NoMatch if matches!(action, "doc.delete" | "doc.delete_where") => 0,
134 _ => 0,
135 };
136
137 let new_content = ops::doc::serialize_value_preserving(&original, &value, &new_value, &format)?;
138 let policy = WritePolicy::default();
139 let content_changed = original != new_content;
141 let (applied, backup_session) = if content_changed {
142 super::write_if_apply(path, &new_content, mode, &policy, guard)?
143 } else {
144 (false, None)
145 };
146 let mut edit =
147 super::build_edit_result(&path_str, original, new_content, applied, action, None);
148 edit.removed = removed;
149 edit.backup_session = backup_session;
150 Ok(edit)
151}
152
153pub fn doc_set(
158 path: &Path,
159 selector: &str,
160 value: serde_json::Value,
161 mode: ApplyMode,
162 guard: Option<&PathGuard>,
163) -> anyhow::Result<EditResult> {
164 let op = Operation::DocSet {
165 path: path.to_string_lossy().into(),
166 selector: selector.into(),
167 value,
168 if_exists: false,
169 };
170 doc_write(op, path, mode, guard, "doc.set")
171}
172
173pub fn doc_delete(
175 path: &Path,
176 selector: &str,
177 mode: ApplyMode,
178 guard: Option<&PathGuard>,
179) -> anyhow::Result<EditResult> {
180 let op = Operation::DocDelete {
181 path: path.to_string_lossy().into(),
182 selector: selector.into(),
183 };
184 doc_write(op, path, mode, guard, "doc.delete")
185}
186
187pub fn doc_merge(
222 path: &Path,
223 value: serde_json::Value,
224 mode: ApplyMode,
225 guard: Option<&PathGuard>,
226 selector: Option<&str>,
227) -> anyhow::Result<EditResult> {
228 let op = Operation::DocMerge {
229 path: path.to_string_lossy().into(),
230 selector: selector.map(|s| s.into()),
231 value,
232 };
233 doc_write(op, path, mode, guard, "doc.merge")
234}
235
236pub fn doc_get(path: &Path, selector: &str) -> anyhow::Result<serde_json::Value> {
240 let value = load_doc_value(path)?;
241
242 match query_get(&value, selector)? {
243 QueryResult::NoMatch => Err(crate::exit::NoMatchError {
244 msg: crate::ops::doc::query::with_similar_object_key_hint(
245 format!("selector '{selector}' matched nothing"),
246 &value,
247 selector,
248 ),
249 }
250 .into()),
251 QueryResult::Values(vals) if vals.len() == 1 => Ok(vals
252 .into_iter()
253 .next()
254 .expect("len()==1 guarantees element")),
255 QueryResult::Values(vals) => Ok(serde_json::Value::Array(vals)),
256 }
257}
258
259pub fn doc_has(path: &Path, selector: &str) -> anyhow::Result<bool> {
261 let value = load_doc_value(path)?;
262 query_has(&value, selector)
263}
264
265pub fn doc_keys(path: &Path, selector: &str) -> anyhow::Result<Vec<String>> {
274 let value = load_doc_value(path)?;
275 crate::ops::doc::query::keys_at(&value, selector)
276}
277
278pub fn doc_len(path: &Path, selector: &str) -> anyhow::Result<usize> {
286 let value = load_doc_value(path)?;
287 crate::ops::doc::query::len_at(&value, selector)
288}
289
290pub fn doc_append(
292 path: &Path,
293 selector: &str,
294 value: serde_json::Value,
295 mode: ApplyMode,
296 guard: Option<&PathGuard>,
297) -> anyhow::Result<EditResult> {
298 let op = Operation::DocAppend {
299 path: path.to_string_lossy().into(),
300 selector: selector.into(),
301 value,
302 };
303 doc_write(op, path, mode, guard, "doc.append")
304}
305
306pub fn doc_prepend(
308 path: &Path,
309 selector: &str,
310 value: serde_json::Value,
311 mode: ApplyMode,
312 guard: Option<&PathGuard>,
313) -> anyhow::Result<EditResult> {
314 let op = Operation::DocPrepend {
315 path: path.to_string_lossy().into(),
316 selector: selector.into(),
317 value,
318 };
319 doc_write(op, path, mode, guard, "doc.prepend")
320}
321
322pub fn doc_update(
327 path: &Path,
328 selector: &str,
329 value: serde_json::Value,
330 mode: ApplyMode,
331 guard: Option<&PathGuard>,
332) -> anyhow::Result<EditResult> {
333 let op = Operation::DocUpdate {
334 path: path.to_string_lossy().into(),
335 selector: selector.into(),
336 value,
337 };
338 doc_write(op, path, mode, guard, "doc.update")
339}
340
341pub fn doc_ensure(
343 path: &Path,
344 selector: &str,
345 value: serde_json::Value,
346 mode: ApplyMode,
347 guard: Option<&PathGuard>,
348) -> anyhow::Result<EditResult> {
349 let op = Operation::DocEnsure {
350 path: path.to_string_lossy().into(),
351 selector: selector.into(),
352 value,
353 };
354 doc_write(op, path, mode, guard, "doc.ensure")
355}
356
357pub fn doc_delete_where(
359 path: &Path,
360 selector: &str,
361 predicate: &str,
362 mode: ApplyMode,
363 guard: Option<&PathGuard>,
364) -> anyhow::Result<EditResult> {
365 let op = Operation::DocDeleteWhere {
366 path: path.to_string_lossy().into(),
367 selector: selector.into(),
368 predicate: predicate.into(),
369 };
370 doc_write(op, path, mode, guard, "doc.delete_where")
371}
372
373pub fn doc_move(
375 path: &Path,
376 from_selector: &str,
377 to_selector: &str,
378 mode: ApplyMode,
379 guard: Option<&PathGuard>,
380) -> anyhow::Result<EditResult> {
381 let op = Operation::DocMove {
382 path: path.to_string_lossy().into(),
383 from: from_selector.into(),
384 to: to_selector.into(),
385 };
386 doc_write(op, path, mode, guard, "doc.move")
387}