use std::path::Path;
use crate::containment::PathGuard;
use crate::ops;
use crate::ops::doc::query::{QueryResult, query_get, query_has};
use crate::plan::Operation;
use super::{ApplyMode, EditResult};
#[cfg(any(feature = "cli", feature = "files"))]
fn cwd_from_path(path: &Path) -> &Path {
path.parent().unwrap_or_else(|| Path::new("."))
}
fn load_doc_value(path: &Path) -> anyhow::Result<serde_json::Value> {
let path_str = path.to_string_lossy();
let format = ops::doc::detect_format(&path_str)?;
let original = crate::files::load_text_strict(path, &path_str)?;
ops::doc::parse_doc(&original, &format)
}
#[cfg(any(feature = "cli", feature = "files"))]
fn doc_write(
mut op: Operation,
path: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
action: &'static str,
) -> anyhow::Result<EditResult> {
let abs = super::absolute_for_engine(path).map_err(|e| {
crate::fallback::EditError::new(
crate::fallback::EditErrorKind::OperationFailed,
format!("failed to resolve path {}: {e}", path.display()),
)
})?;
rewrite_op_path(&mut op, abs.to_string_lossy().as_ref());
let display = path.to_string_lossy();
super::execute_as_edit_result_with_path(
op,
mode,
cwd_from_path(&abs),
guard,
action,
None,
Some(display.as_ref()),
)
}
#[cfg(any(feature = "cli", feature = "files"))]
fn rewrite_op_path(op: &mut Operation, abs: &str) {
match op {
Operation::DocSet { path, .. }
| Operation::DocDelete { path, .. }
| Operation::DocMerge { path, .. }
| Operation::DocAppend { path, .. }
| Operation::DocPrepend { path, .. }
| Operation::DocUpdate { path, .. }
| Operation::DocMove { path, .. }
| Operation::DocEnsure { path, .. }
| Operation::DocDeleteWhere { path, .. } => {
*path = abs.into();
}
_ => {}
}
}
#[cfg(not(any(feature = "cli", feature = "files")))]
fn doc_write(
op: Operation,
path: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
action: &'static str,
) -> anyhow::Result<EditResult> {
use crate::ops::doc::MutationResult;
use crate::write::WritePolicy;
let (_, mutation) = crate::plan::op_to_doc_mutation(&op)
.ok_or_else(|| anyhow::anyhow!("doc_write called with non-doc operation"))?;
let path_str = path.to_string_lossy().into_owned();
let format = ops::doc::detect_format(&path_str)?;
let original = crate::files::load_text_strict(path, &path_str)?;
let value = ops::doc::parse_doc(&original, &format)?;
let mut new_value = value.clone();
let result = ops::doc::apply_doc_mutation(&mut new_value, mutation)?;
if let MutationResult::TypeError(msg) = result {
return Err(anyhow::Error::new(crate::exit::TypeErrorError { msg }));
}
let removed = match &result {
MutationResult::Removed(n) => *n,
MutationResult::NoMatch if matches!(action, "doc.delete" | "doc.delete_where") => 0,
_ => 0,
};
let new_content = ops::doc::serialize_value_preserving(&original, &value, &new_value, &format)?;
let policy = WritePolicy::default();
let content_changed = original != new_content;
let (applied, backup_session) = if content_changed {
super::write_if_apply(path, &new_content, mode, &policy, guard)?
} else {
(false, None)
};
let mut edit =
super::build_edit_result(&path_str, original, new_content, applied, action, None);
edit.removed = removed;
edit.backup_session = backup_session;
Ok(edit)
}
pub fn doc_set(
path: &Path,
selector: &str,
value: serde_json::Value,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocSet {
path: path.to_string_lossy().into(),
selector: selector.into(),
value,
};
doc_write(op, path, mode, guard, "doc.set")
}
pub fn doc_delete(
path: &Path,
selector: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocDelete {
path: path.to_string_lossy().into(),
selector: selector.into(),
};
doc_write(op, path, mode, guard, "doc.delete")
}
pub fn doc_merge(
path: &Path,
value: serde_json::Value,
mode: ApplyMode,
guard: Option<&PathGuard>,
selector: Option<&str>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocMerge {
path: path.to_string_lossy().into(),
selector: selector.map(|s| s.into()),
value,
};
doc_write(op, path, mode, guard, "doc.merge")
}
pub fn doc_get(path: &Path, selector: &str) -> anyhow::Result<serde_json::Value> {
let value = load_doc_value(path)?;
match query_get(&value, selector)? {
QueryResult::NoMatch => Err(crate::exit::NoMatchError {
msg: format!("selector '{}' matched nothing", selector),
}
.into()),
QueryResult::Values(vals) if vals.len() == 1 => Ok(vals
.into_iter()
.next()
.expect("len()==1 guarantees element")),
QueryResult::Values(vals) => Ok(serde_json::Value::Array(vals)),
}
}
pub fn doc_has(path: &Path, selector: &str) -> anyhow::Result<bool> {
let value = load_doc_value(path)?;
query_has(&value, selector)
}
pub fn doc_append(
path: &Path,
selector: &str,
value: serde_json::Value,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocAppend {
path: path.to_string_lossy().into(),
selector: selector.into(),
value,
};
doc_write(op, path, mode, guard, "doc.append")
}
pub fn doc_prepend(
path: &Path,
selector: &str,
value: serde_json::Value,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocPrepend {
path: path.to_string_lossy().into(),
selector: selector.into(),
value,
};
doc_write(op, path, mode, guard, "doc.prepend")
}
pub fn doc_update(
path: &Path,
selector: &str,
value: serde_json::Value,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocUpdate {
path: path.to_string_lossy().into(),
selector: selector.into(),
value,
};
doc_write(op, path, mode, guard, "doc.update")
}
pub fn doc_ensure(
path: &Path,
selector: &str,
value: serde_json::Value,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocEnsure {
path: path.to_string_lossy().into(),
selector: selector.into(),
value,
};
doc_write(op, path, mode, guard, "doc.ensure")
}
pub fn doc_delete_where(
path: &Path,
selector: &str,
predicate: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocDeleteWhere {
path: path.to_string_lossy().into(),
selector: selector.into(),
predicate: predicate.into(),
};
doc_write(op, path, mode, guard, "doc.delete_where")
}
pub fn doc_move(
path: &Path,
from_selector: &str,
to_selector: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocMove {
path: path.to_string_lossy().into(),
from: from_selector.into(),
to: to_selector.into(),
};
doc_write(op, path, mode, guard, "doc.move")
}