Files
pikl/crates/pikl-core/src/model/output.rs
J. Champagne 2729e7e1d2
Some checks failed
CI / Check (macos-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Clippy (strict) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
feat(core): Add Quicklist function to return all visible via ctrl+Q.
2026-03-14 01:49:24 -04:00

124 lines
3.7 KiB
Rust

//! Structured output for selected items. Wraps the original
//! item value with action context and original index so
//! hooks and downstream tools know what happened.
use serde::ser::SerializeMap;
use serde::Serialize;
use serde_json::Value;
/// What the user did to produce this output.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum OutputAction {
Select,
Quicklist,
Cancel,
}
/// A selected item wrapped with output context. For object
/// values, the original fields are merged at the top level
/// alongside `action` and `index`. For non-object values
/// (plain strings, numbers), the value appears as a `value`
/// field.
#[derive(Debug, Clone)]
pub struct OutputItem {
pub value: Value,
pub action: OutputAction,
pub index: usize,
}
impl Serialize for OutputItem {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match &self.value {
Value::Object(map) => {
// Flatten: merge object fields with action/index
let mut s = serializer.serialize_map(Some(map.len() + 2))?;
for (k, v) in map {
s.serialize_entry(k, v)?;
}
s.serialize_entry("action", &self.action)?;
s.serialize_entry("index", &self.index)?;
s.end()
}
_ => {
// Non-object: put value in a "value" field
let mut s = serializer.serialize_map(Some(3))?;
s.serialize_entry("value", &self.value)?;
s.serialize_entry("action", &self.action)?;
s.serialize_entry("index", &self.index)?;
s.end()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn output_item_flattens_object() {
let item = OutputItem {
value: json!({"label": "Firefox", "url": "https://firefox.com"}),
action: OutputAction::Select,
index: 3,
};
let json = serde_json::to_value(&item).unwrap_or_default();
assert_eq!(json["label"], "Firefox");
assert_eq!(json["url"], "https://firefox.com");
assert_eq!(json["action"], "select");
assert_eq!(json["index"], 3);
}
#[test]
fn output_item_string_value() {
let item = OutputItem {
value: json!("hello"),
action: OutputAction::Select,
index: 0,
};
let json = serde_json::to_value(&item).unwrap_or_default();
assert_eq!(json["value"], "hello");
assert_eq!(json["action"], "select");
assert_eq!(json["index"], 0);
}
#[test]
fn output_item_cancel_action() {
let item = OutputItem {
value: json!(null),
action: OutputAction::Cancel,
index: 0,
};
let json = serde_json::to_value(&item).unwrap_or_default();
assert_eq!(json["action"], "cancel");
}
#[test]
fn output_item_quicklist_action() {
let item = OutputItem {
value: json!("test"),
action: OutputAction::Quicklist,
index: 2,
};
let json = serde_json::to_value(&item).unwrap_or_default();
assert_eq!(json["action"], "quicklist");
assert_eq!(json["index"], 2);
}
#[test]
fn output_item_string_contains_value_text() {
let item = OutputItem {
value: json!("alpha"),
action: OutputAction::Select,
index: 0,
};
let serialized = serde_json::to_string(&item).unwrap_or_default();
assert!(
serialized.contains("alpha"),
"output should contain the value text: {serialized}"
);
}
}