pub fn shape_value(v: Value, shape: &OutputShape) -> Option<Value> {
if !shape.filters.is_empty() {
let obj = v.as_object()?;
for pred in &shape.filters {
let actual = obj.get(&pred.key).cloned().unwrap_or(Value::Null);
let eq = json_values_equal(&actual, &pred.value);
if pred.not_equal {
if eq {
return None;
}
} else if !eq {
return None;
}
}
}
let mut v = project_select(v, shape);
v = apply_lean(v, shape);
if shape.select.is_some() && v.as_object().is_some_and(serde_json::Map::is_empty) {
return None;
}
apply_value_ops(&mut v, shape);
Some(v)
}
pub fn shape_error_value(mut v: Value, shape: &OutputShape) -> Value {
let original = v.clone();
if !shape.verbose {
drop_diagnostic_keys(&mut v);
}
let mut v = project_select(v, shape);
v = keep_terminal_keys(v, &original);
scrub_envelope_keys(&mut v);
if shape.redact {
redact_secrets(&mut v);
}
v
}
fn drop_diagnostic_keys(v: &mut Value) {
let Some(obj) = v.as_object_mut() else {
return;
};
for key in ["message", "error_class", "retryable", "suggestion_lang"] {
obj.remove(key);
}
}
pub fn keep_terminal_keys(projected: Value, original: &Value) -> Value {
let (Some(obj), Some(src)) = (projected.as_object(), original.as_object()) else {
return projected;
};
let mut out = obj.clone();
for key in ["type", crate::output::RESULT_KEY] {
if !out.contains_key(key)
&& let Some(val) = src.get(key)
{
out.insert(key.to_string(), val.clone());
}
}
Value::Object(out)
}
fn apply_value_ops(v: &mut Value, shape: &OutputShape) {
scrub_envelope_keys(v);
if shape.redact {
redact_secrets(v);
}
if shape.max_output_bytes.is_some() || !shape.truncate_specs.is_empty() {
if apply_string_limits(v, shape) {
if let Some(obj) = v.as_object_mut() {
obj.insert("truncated".to_string(), Value::Bool(true));
}
note_truncated_record();
}
}
if shape.normalize_encoding {
normalize_encoding(v);
}
}
pub fn shape_terminal_value(v: Value, shape: &OutputShape) -> Value {
let original = v.clone();
let mut v = project_select(v, shape);
v = apply_lean(v, shape);
keep_terminal_keys(v, &original)
}
fn apply_lean(v: Value, shape: &OutputShape) -> Value {
if shape.profile != OutputProfile::Lean {
return v;
}
let Value::Object(mut obj) = v else {
return v;
};
let ok = obj
.get(crate::output::RESULT_KEY)
.and_then(Value::as_bool)
.unwrap_or(true);
obj.retain(|key, value| {
if value.is_null() {
return false;
}
let dead_container = match value {
Value::Array(items) => items.is_empty(),
Value::Object(map) => map.is_empty(),
Value::String(text) => text.is_empty(),
_ => false,
};
if dead_container {
return false;
}
if ok && matches!(key.as_str(), "diff_preview" | "suggestion_lang") {
return false;
}
if ok
&& matches!(
key.as_str(),
"checksum_before"
| "checksum_after"
| "bytes_before"
| "elapsed_ms"
| "format_detected"
)
{
return false;
}
if key == "risk_assessment" && risk_is_low(value) {
return false;
}
true
});
Value::Object(obj)
}
fn risk_is_low(value: &Value) -> bool {
let Some(obj) = value.as_object() else {
return false;
};
for key in ["level", "risk_level", "risk"] {
match obj.get(key) {
Some(Value::String(level)) => {
if level.eq_ignore_ascii_case("low") || level.eq_ignore_ascii_case("none") {
return true;
}
}
Some(Value::Number(n)) => {
if n.as_u64() == Some(0) {
return true;
}
}
Some(Value::Bool(false)) => return true,
_ => {}
}
}
false
}
fn project_select(mut v: Value, shape: &OutputShape) -> Value {
if let Some(keys) = &shape.select {
let Some(obj) = v.as_object_mut() else {
return v;
};
let mut out = serde_json::Map::new();
for k in keys {
if let Some(val) = obj.remove(k) {
out.insert(k.clone(), val);
}
}
return Value::Object(out);
}
v
}
fn json_values_equal(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Number(x), Value::Number(y)) => {
if let (Some(xf), Some(yf)) = (x.as_f64(), y.as_f64()) {
(xf - yf).abs() < f64::EPSILON
} else {
x == y
}
}
_ => a == b,
}
}