use serde_json::Value;
use crate::types::{new_key_path, ProcessHandling, TypedValue, ValueType};
fn typed_value_from_json(val: &Value) -> Option<(TypedValue, ValueType)> {
match val {
Value::Bool(b) => Some((TypedValue::Bool(*b), ValueType::Bool)),
Value::String(s) => Some((TypedValue::String(s.clone()), ValueType::String)),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
if n.is_i64() {
return Some((TypedValue::Integer(i), ValueType::Integer));
}
}
n.as_f64()
.map(|f| (TypedValue::Number(f), ValueType::Number))
}
Value::Null => Some((TypedValue::Null, ValueType::Null)),
_ => None, }
}
fn typed_value_to_json(typed: TypedValue) -> Value {
match typed {
TypedValue::Bool(b) => Value::Bool(b),
TypedValue::Integer(i) => Value::Number(i.into()),
TypedValue::Number(f) => Value::Number(
serde_json::Number::from_f64(f).unwrap_or_else(|| 0i64.into()),
),
TypedValue::String(s) => Value::String(s),
TypedValue::Null => Value::Null,
}
}
type Processor<'a> = &'a mut dyn FnMut(
TypedValue,
ValueType,
&str,
) -> Result<(TypedValue, ValueType, ProcessHandling), Box<dyn std::error::Error>>;
fn traverse(
val: &mut Value,
key_path: &str,
processor: Processor<'_>,
filter_keys: &[String],
) -> Result<bool, Box<dyn std::error::Error>> {
if !filter_keys.is_empty()
&& filter_keys.iter().any(|k| k.as_str() == key_path)
&& matches!(val, Value::Object(_) | Value::Array(_))
{
let serialized = format!("__subnode__\n{}", serde_json::to_string(val)?);
match processor(TypedValue::String(serialized), ValueType::String, key_path) {
Ok((new_typed, _, ProcessHandling::Process)) => {
*val = typed_value_to_json(new_typed);
return Ok(true);
}
Ok((_, _, ProcessHandling::Cancel)) => return Ok(false),
Ok((_, _, ProcessHandling::Skip)) => {
}
Err(e) => {
eprintln!("error processing key '{}': {}", key_path, e);
return Ok(false);
}
}
}
match val {
Value::Object(map) => {
let keys: Vec<String> = map.keys().cloned().collect();
for key in &keys {
let new_path = new_key_path(key_path, key);
let child = map.get_mut(key).unwrap();
if !traverse(child, &new_path, processor, filter_keys)? {
return Ok(false);
}
}
}
Value::Array(arr) => {
for item in arr.iter_mut() {
if !traverse(item, key_path, processor, filter_keys)? {
return Ok(false);
}
}
}
scalar => {
if !filter_keys.is_empty()
&& !filter_keys.iter().any(|k| k.as_str() == key_path)
{
return Ok(true);
}
if let Some((typed, vt)) = typed_value_from_json(scalar) {
match processor(typed, vt, key_path) {
Ok((new_typed, _, ProcessHandling::Process)) => {
let mut restored = false;
if let TypedValue::String(ref s) = new_typed {
if let Some(inner) = s.strip_prefix("__subnode__\n") {
if let Ok(parsed) = serde_json::from_str::<Value>(inner) {
if matches!(parsed, Value::Object(_) | Value::Array(_)) {
*scalar = parsed;
restored = true;
}
}
}
}
if !restored {
*scalar = typed_value_to_json(new_typed);
}
}
Ok((_, _, ProcessHandling::Cancel)) => return Ok(false),
Ok((_, _, ProcessHandling::Skip)) => {}
Err(e) => {
eprintln!("error processing key '{}': {}", key_path, e);
return Ok(false);
}
}
}
}
}
Ok(true)
}
pub fn process_in_memory(
input: &str,
filter_keys: &[String],
processor: Processor<'_>,
) -> Result<Value, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(input)
.map_err(|e| format!("error reading '{}': {}", input, e))?;
let mut root: Value = serde_json::from_str(&content)
.map_err(|e| format!("error parsing JSON from '{}': {}", input, e))?;
if !traverse(&mut root, "", processor, filter_keys)? {
return Err("processing canceled".into());
}
Ok(root)
}
pub fn process_file(
input: &str,
output: &str,
filter_keys: &[String],
processor: Processor<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(input)
.map_err(|e| format!("error reading '{}': {}", input, e))?;
let mut root: Value = serde_json::from_str(&content)
.map_err(|e| format!("error parsing JSON from '{}': {}", input, e))?;
if !traverse(&mut root, "", processor, filter_keys)? {
return Err("processing canceled".into());
}
let output_str = serde_json::to_string_pretty(&root)?;
if output == "stdout" {
println!("{}", output_str);
} else {
std::fs::write(output, &output_str)
.map_err(|e| format!("error writing '{}': {}", output, e))?;
}
Ok(())
}
pub fn get_encrypted_keys(input: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(input)?;
let root: Value = serde_json::from_str(&content)?;
let mut encrypted = Vec::new();
collect_encrypted(&root, "", &mut encrypted);
Ok(encrypted)
}
fn collect_encrypted(val: &Value, key_path: &str, out: &mut Vec<String>) {
match val {
Value::Object(map) => {
for (key, child) in map.iter() {
collect_encrypted(child, &new_key_path(key_path, key), out);
}
}
Value::Array(arr) => {
for item in arr.iter() {
collect_encrypted(item, key_path, out);
}
}
Value::String(s) if s.contains("$ANSIBLE_VAULT;") => {
out.push(key_path.to_string());
}
_ => {}
}
}
pub fn collect_paths_at_level(input: &str, level: u32) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(input)
.map_err(|e| format!("error reading '{}': {}", input, e))?;
let root: Value = serde_json::from_str(&content)
.map_err(|e| format!("error parsing JSON from '{}': {}", input, e))?;
let mut paths = Vec::new();
collect_at_level(&root, "", level, &mut paths);
Ok(paths)
}
fn collect_at_level(val: &Value, key_path: &str, remaining: u32, out: &mut Vec<String>) {
if remaining == 0 {
out.push(key_path.to_string());
return;
}
if let Value::Object(map) = val {
for (key, child) in map.iter() {
collect_at_level(child, &new_key_path(key_path, key), remaining - 1, out);
}
}
}