Skip to main content

kcode_k1_chat_persistence_values/
lib.rs

1#![forbid(unsafe_code)]
2
3pub use kcode_k1_chat_boxes::{BoxId, ChatBox, ToolCallId};
4pub use kcode_k1_transaction_id::TxId;
5use serde_json::Value;
6use std::io;
7
8pub type SessionId = [u8; 12];
9type Result<T, E = Error> = std::result::Result<T, E>;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct EventRecord {
13    pub after_box_id: u64,
14    pub event_index: u64,
15    pub connected_box_id: u64,
16    pub handler: String,
17    pub data: Value,
18}
19
20impl EventRecord {
21    pub fn new(
22        after_box_id: u64,
23        event_index: u64,
24        connected_box_id: u64,
25        handler: String,
26        data: Value,
27    ) -> Result<Self> {
28        if handler.is_empty() {
29            return Err(Error::Invalid("empty event handler"));
30        }
31        Ok(Self {
32            after_box_id,
33            event_index,
34            connected_box_id,
35            handler,
36            data,
37        })
38    }
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub enum Record {
43    Box(ChatBox),
44    Event(EventRecord),
45}
46
47impl Record {
48    pub fn chat_box(value: ChatBox) -> Self {
49        Self::Box(value)
50    }
51
52    pub fn event(value: EventRecord) -> Self {
53        Self::Event(value)
54    }
55}
56
57#[derive(Clone, Debug, Default, PartialEq, Eq)]
58pub struct SessionLog {
59    pub boxes: Vec<ChatBox>,
60    pub events: Vec<EventRecord>,
61    pub records: Vec<Record>,
62}
63
64#[derive(Debug)]
65pub enum Error {
66    Io(io::Error),
67    Json(serde_json::Error),
68    Invalid(&'static str),
69}
70
71impl std::fmt::Display for Error {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        match self {
74            Self::Io(error) => write!(f, "I/O error: {error}"),
75            Self::Json(error) => write!(f, "JSON error: {error}"),
76            Self::Invalid(error) => f.write_str(error),
77        }
78    }
79}
80
81impl std::error::Error for Error {}
82
83impl From<io::Error> for Error {
84    fn from(value: io::Error) -> Self {
85        Self::Io(value)
86    }
87}
88
89impl From<serde_json::Error> for Error {
90    fn from(value: serde_json::Error) -> Self {
91        Self::Json(value)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use serde_json::json;
99
100    fn chat_box() -> ChatBox {
101        ChatBox::new(
102            BoxId::new(7),
103            "Future Kind".into(),
104            "opaque".into(),
105            "future/v1".into(),
106            "hidden".into(),
107        )
108    }
109
110    #[test]
111    fn constructors_preserve_values_and_reject_only_empty_handlers() {
112        let event = EventRecord::new(7, 2, 6, "handler".into(), json!({"key": 1})).unwrap();
113        assert_eq!(event.after_box_id, 7);
114        assert_eq!(event.event_index, 2);
115        assert_eq!(event.connected_box_id, 6);
116        assert_eq!(event.handler, "handler");
117        assert_eq!(event.data, json!({"key": 1}));
118        assert!(matches!(
119            EventRecord::new(0, 0, 0, String::new(), Value::Null),
120            Err(Error::Invalid("empty event handler"))
121        ));
122
123        let value = chat_box();
124        assert_eq!(Record::chat_box(value.clone()), Record::Box(value));
125        assert_eq!(Record::event(event.clone()), Record::Event(event));
126    }
127
128    #[test]
129    fn session_log_and_canonical_reexports_keep_the_established_shapes() {
130        let value = chat_box();
131        let log = SessionLog {
132            boxes: vec![value.clone()],
133            events: Vec::new(),
134            records: vec![Record::Box(value)],
135        };
136        assert_eq!(log.boxes[0].id(), BoxId::new(7));
137        assert_eq!(ToolCallId::new([3; 12], 9).nonce(), [3; 12]);
138        assert_eq!(*TxId::from_bytes([4; 12]).as_bytes(), [4; 12]);
139        let session: SessionId = [5; 12];
140        assert_eq!(session, [5; 12]);
141    }
142
143    #[test]
144    fn errors_preserve_display_and_conversion_behavior() {
145        let io_error = Error::from(io::Error::other("x"));
146        assert_eq!(io_error.to_string(), "I/O error: x");
147        assert!(std::error::Error::source(&io_error).is_none());
148
149        let json_error = Error::from(serde_json::from_str::<Value>("{").unwrap_err());
150        assert!(json_error.to_string().starts_with("JSON error: "));
151        assert!(std::error::Error::source(&json_error).is_none());
152
153        let invalid = Error::Invalid("invalid value");
154        assert_eq!(invalid.to_string(), "invalid value");
155        assert!(std::error::Error::source(&invalid).is_none());
156    }
157}