use std::path::Path;
use anyhow::{Context, bail};
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 = std::fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path.display()))?;
ops::doc::parse_doc(&original, &format)
}
#[cfg(any(feature = "cli", feature = "files"))]
fn doc_write(
op: Operation,
path: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
action: &'static str,
) -> anyhow::Result<EditResult> {
super::execute_as_edit_result(op, mode, cwd_from_path(path), guard, action)
}
#[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 = std::fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path.display()))?;
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 {
bail!("{msg}");
}
let new_content = ops::doc::serialize_value_preserving(&original, &value, &new_value, &format)?;
let policy = WritePolicy::default();
let applied = super::write_if_apply(path, &new_content, mode, &policy, guard)?;
Ok(super::build_edit_result(
&path_str,
original,
new_content,
applied,
action,
None,
))
}
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(),
key: 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(),
key: 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>,
) -> anyhow::Result<EditResult> {
let op = Operation::DocMerge {
path: path.to_string_lossy().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 => bail!("selector '{}' matched nothing", selector),
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(),
key: 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(),
key: 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(),
key: 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(),
key: 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(),
key: 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")
}