use crate::core::output_model::{
ColumnAlignment, OutputItems, OutputMeta, OutputResult,
compute_key_index as core_compute_key_index, output_items_to_rows,
};
use crate::core::plugin::{ColumnAlignmentV1, ResponseMetaV1};
use crate::core::row::Row;
pub(crate) fn rows_to_output_result(rows: Vec<Row>) -> OutputResult {
OutputResult::from_rows(rows)
}
pub(crate) fn output_to_rows(output: &OutputResult) -> Vec<Row> {
output_items_to_rows(&output.items)
}
pub(crate) fn plugin_data_to_output_result(
data: serde_json::Value,
meta: Option<&ResponseMetaV1>,
) -> OutputResult {
let rows = response_to_rows(data);
let key_index = meta
.and_then(|value| value.columns.clone())
.filter(|columns| !columns.is_empty())
.unwrap_or_else(|| compute_key_index(&rows));
OutputResult {
items: OutputItems::Rows(rows),
document: None,
meta: OutputMeta {
key_index,
column_align: meta
.map(|value| {
value
.column_align
.iter()
.copied()
.map(column_alignment_from_plugin)
.collect()
})
.unwrap_or_default(),
wants_copy: false,
grouped: false,
render_recommendation: None,
},
}
}
fn column_alignment_from_plugin(value: ColumnAlignmentV1) -> ColumnAlignment {
match value {
ColumnAlignmentV1::Default => ColumnAlignment::Default,
ColumnAlignmentV1::Left => ColumnAlignment::Left,
ColumnAlignmentV1::Center => ColumnAlignment::Center,
ColumnAlignmentV1::Right => ColumnAlignment::Right,
}
}
fn response_to_rows(data: serde_json::Value) -> Vec<Row> {
match data {
serde_json::Value::Array(items)
if items
.iter()
.all(|item| matches!(item, serde_json::Value::Object(_))) =>
{
items
.into_iter()
.filter_map(|item| item.as_object().cloned())
.collect::<Vec<Row>>()
}
serde_json::Value::Object(map) => vec![map],
scalar => vec![crate::row! { "value" => scalar }],
}
}
fn compute_key_index(rows: &[Row]) -> Vec<String> {
core_compute_key_index(rows)
}
#[cfg(test)]
mod tests {
use super::{output_to_rows, plugin_data_to_output_result, rows_to_output_result};
use crate::core::output_model::{
ColumnAlignment, Group, OutputItems, OutputMeta, OutputResult,
};
use crate::core::plugin::{ColumnAlignmentV1, ResponseMetaV1};
use serde_json::Value;
use serde_json::json;
#[test]
fn plugin_meta_and_data_shapes_preserve_alignment_and_normalize_rows_unit() {
let output = plugin_data_to_output_result(
json!([{ "name": "alice", "count": 2 }]),
Some(&ResponseMetaV1 {
format_hint: Some("table".to_string()),
columns: Some(vec!["name".to_string(), "count".to_string()]),
column_align: vec![ColumnAlignmentV1::Left, ColumnAlignmentV1::Right],
}),
);
assert_eq!(
output.meta.key_index,
vec!["name".to_string(), "count".to_string()]
);
assert_eq!(
output.meta.column_align,
vec![ColumnAlignment::Left, ColumnAlignment::Right]
);
let output = plugin_data_to_output_result(
json!([{ "name": "alice", "count": 2, "status": "ok", "notes": "ready" }]),
Some(&ResponseMetaV1 {
format_hint: Some("table".to_string()),
columns: Some(vec![
"name".to_string(),
"count".to_string(),
"status".to_string(),
"notes".to_string(),
]),
column_align: vec![
ColumnAlignmentV1::Default,
ColumnAlignmentV1::Left,
ColumnAlignmentV1::Center,
ColumnAlignmentV1::Right,
],
}),
);
assert_eq!(
output.meta.column_align,
vec![
ColumnAlignment::Default,
ColumnAlignment::Left,
ColumnAlignment::Center,
ColumnAlignment::Right,
]
);
let scalar = plugin_data_to_output_result(json!("hello"), None);
let object = plugin_data_to_output_result(json!({ "uid": "alice", "count": 2 }), None);
let scalar_rows = output_to_rows(&scalar);
let object_rows = output_to_rows(&object);
assert_eq!(scalar_rows, vec![crate::row! { "value" => "hello" }]);
assert_eq!(
object_rows,
vec![crate::row! { "uid" => "alice", "count" => 2 }]
);
}
#[test]
fn output_row_helpers_round_trip_and_flatten_groups_unit() {
let rows = vec![
crate::row! { "uid" => "alice", "count" => 2 },
crate::row! { "uid" => "bob", "count" => 3 },
];
let output = rows_to_output_result(rows.clone());
assert_eq!(output_to_rows(&output), rows);
assert_eq!(
output.meta.key_index,
vec!["uid".to_string(), "count".to_string()]
);
let output = OutputResult {
items: OutputItems::Groups(vec![
Group {
groups: crate::row! { "team" => "ops" },
aggregates: crate::row! { "count" => 2 },
rows: vec![
crate::row! { "user" => "alice" },
crate::row! { "user" => "bob" },
],
},
Group {
groups: crate::row! { "team" => "infra" },
aggregates: crate::row! { "count" => 0 },
rows: Vec::new(),
},
]),
document: None,
meta: OutputMeta {
key_index: vec!["team".to_string(), "count".to_string(), "user".to_string()],
column_align: Vec::new(),
wants_copy: false,
grouped: true,
render_recommendation: None,
},
};
let rows = output_to_rows(&output);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["team"], Value::String("ops".to_string()));
assert_eq!(rows[0]["count"], Value::from(2));
assert_eq!(rows[0]["user"], Value::String("alice".to_string()));
assert_eq!(rows[1]["user"], Value::String("bob".to_string()));
assert_eq!(rows[2]["team"], Value::String("infra".to_string()));
assert_eq!(rows[2]["count"], Value::from(0));
assert_eq!(rows[2].get("user"), None);
}
}