feat(core): Add error types, config, and data model for items and events.
This commit is contained in:
131
crates/pikl-core/src/model/item.rs
Normal file
131
crates/pikl-core/src/model/item.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
//! The core data unit. An [`Item`] is a single entry in the
|
||||
//! menu, either a plain-text string or a JSON object with
|
||||
//! structured fields.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
/// A menu entry wrapping a JSON value. Plain text is stored
|
||||
/// as `Value::String`, structured entries as
|
||||
/// `Value::Object`. The label is extracted and cached at
|
||||
/// construction time so display calls are just a pointer
|
||||
/// dereference.
|
||||
///
|
||||
/// Serializes as the inner Value (transparent for output)
|
||||
/// but construction always requires label extraction.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Item {
|
||||
pub value: Value,
|
||||
label_cache: String,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
/// Create an Item from a JSON value, extracting the display
|
||||
/// label from the given key. String values use the string
|
||||
/// itself. Object values look up the key.
|
||||
pub fn new(value: Value, label_key: &str) -> Self {
|
||||
let label_cache = extract_label(&value, label_key).to_string();
|
||||
Self { value, label_cache }
|
||||
}
|
||||
|
||||
/// Wrap a plain-text string as an Item. Stored internally
|
||||
/// as `Value::String` with the label cached.
|
||||
pub fn from_plain_text(line: &str) -> Self {
|
||||
Self {
|
||||
value: Value::String(line.to_string()),
|
||||
label_cache: line.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the display label for this item. Cached at
|
||||
/// construction time, so this is just a borrow.
|
||||
pub fn label(&self) -> &str {
|
||||
&self.label_cache
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the display label from a JSON value.
|
||||
fn extract_label<'a>(value: &'a Value, key: &str) -> &'a str {
|
||||
match value {
|
||||
Value::String(s) => s.as_str(),
|
||||
Value::Object(map) => map.get(key).and_then(|v| v.as_str()).unwrap_or(""),
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Item {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.value.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Item {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
// Default to "label" key when deserializing. Callers that need
|
||||
// a different key should construct via Item::new() instead.
|
||||
Ok(Item::new(value, "label"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn label_from_string() {
|
||||
let item = Item::new(json!("hello"), "label");
|
||||
assert_eq!(item.label(), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_from_object_with_key() {
|
||||
let item = Item::new(json!({"label": "foo", "value": 42}), "label");
|
||||
assert_eq!(item.label(), "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_from_object_missing_key() {
|
||||
let item = Item::new(json!({"name": "bar"}), "label");
|
||||
assert_eq!(item.label(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_from_object_custom_key() {
|
||||
let item = Item::new(json!({"name": "bar", "label": "ignored"}), "name");
|
||||
assert_eq!(item.label(), "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_from_number() {
|
||||
let item = Item::new(json!(42), "label");
|
||||
assert_eq!(item.label(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_from_null() {
|
||||
let item = Item::new(json!(null), "label");
|
||||
assert_eq!(item.label(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_plain_text() {
|
||||
let item = Item::from_plain_text("hello world");
|
||||
assert_eq!(item.label(), "hello world");
|
||||
assert!(item.value.is_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_string_item() {
|
||||
let item = Item::from_plain_text("test");
|
||||
let json = serde_json::to_string(&item).unwrap_or_default();
|
||||
assert_eq!(json, "\"test\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_object_item() {
|
||||
let item = Item::new(json!({"label": "foo"}), "label");
|
||||
let json = serde_json::to_string(&item).unwrap_or_default();
|
||||
assert_eq!(json, r#"{"label":"foo"}"#);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user