Skip to main content

kcode_kennedy_session_kmap_services/
lib.rs

1//! Typed access to a Kennedy logical session's Kmap capability.
2
3#![forbid(unsafe_code)]
4
5use chrono::{DateTime, Utc};
6use kcode_kweb_db::{Node, NodeId, ObjectId, Provenance};
7use kcode_kweb_manager::{ErrorKind, KwebManager};
8use kcode_server_object_envelopes::{StoredFile, decode_file, encode_file};
9
10const GENERATED_IMAGE_ENVELOPE_KIND: &str = "generated-image";
11const GENERATED_IMAGE_TRANSPORT_KIND: &str = "image";
12const GENERATED_IMAGE_SOURCE: &str = "kennedy-generated-image";
13const GENERATED_IMAGE_PROVENANCE_DATA: &str =
14    "Image generated or modified through Kennedy intelligence.";
15const UNEXPECTED_KMAP_ERROR: &str = "An unexpected Kmap database error occurred.";
16
17#[derive(Debug, Clone)]
18pub struct Error {
19    message: String,
20}
21
22impl std::fmt::Display for Error {
23    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        formatter.write_str(&self.message)
25    }
26}
27
28impl std::error::Error for Error {}
29
30#[derive(Clone)]
31pub struct Service {
32    manager: KwebManager,
33}
34
35impl Service {
36    pub fn new(manager: KwebManager) -> Self {
37        Self { manager }
38    }
39
40    pub fn manager(&self) -> &KwebManager {
41        &self.manager
42    }
43
44    pub fn node(&self, node_id: &str) -> Result<Node, Error> {
45        let node_id = parse_node_id(node_id)?;
46        self.manager.get_node(node_id).map_err(manager_error)
47    }
48
49    pub fn file(&self, object_id: &str) -> Result<StoredFile, Error> {
50        let object_id = parse_object_id(object_id)?;
51        let bytes = self.manager.get_object(object_id).map_err(manager_error)?;
52        decode_stored_file(object_id, bytes)
53    }
54
55    pub fn commit_session(
56        &self,
57        input: kcode_commit_session::CommitRequest,
58    ) -> Result<kcode_commit_session::CommitReceipt, Error> {
59        self.manager.commit_session(input).map_err(manager_error)
60    }
61
62    pub fn save_generated_image(
63        &self,
64        bytes: Vec<u8>,
65        file_name: &str,
66        media_type: &str,
67        model: &str,
68    ) -> Result<String, Error> {
69        let bytes = generated_image_envelope(bytes, file_name, media_type)?;
70        self.manager
71            .store_object(generated_image_provenance(model, Utc::now()), bytes)
72            .map(|id| id.to_string())
73            .map_err(manager_error)
74    }
75}
76
77fn parse_node_id(node_id: &str) -> Result<NodeId, Error> {
78    node_id.parse().map_err(local_error)
79}
80
81fn parse_object_id(object_id: &str) -> Result<ObjectId, Error> {
82    object_id.parse().map_err(local_error)
83}
84
85fn decode_stored_file(object_id: ObjectId, bytes: Vec<u8>) -> Result<StoredFile, Error> {
86    decode_file(object_id, bytes).map_err(local_error)
87}
88
89fn generated_image_envelope(
90    bytes: Vec<u8>,
91    file_name: &str,
92    media_type: &str,
93) -> Result<Vec<u8>, Error> {
94    encode_file(
95        GENERATED_IMAGE_ENVELOPE_KIND,
96        Some(file_name),
97        media_type,
98        Some(GENERATED_IMAGE_TRANSPORT_KIND),
99        bytes,
100    )
101    .map_err(local_error)
102}
103
104fn generated_image_provenance(model: &str, source_created_at: DateTime<Utc>) -> Provenance {
105    Provenance {
106        author: model.into(),
107        source: GENERATED_IMAGE_SOURCE.into(),
108        source_created_at,
109        data: GENERATED_IMAGE_PROVENANCE_DATA.into(),
110    }
111}
112
113fn manager_error(error: kcode_kweb_manager::Error) -> Error {
114    Error {
115        message: manager_error_message(error.kind(), error.to_string()),
116    }
117}
118
119fn manager_error_message(kind: ErrorKind, message: String) -> String {
120    match kind {
121        ErrorKind::InvalidInput | ErrorKind::NotFound | ErrorKind::Conflict => message,
122        _ => UNEXPECTED_KMAP_ERROR.into(),
123    }
124}
125
126fn local_error(error: impl std::fmt::Display) -> Error {
127    Error {
128        message: error.to_string(),
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn canonical_id_errors_preserve_dependency_messages() {
138        let node_text = "not-a-node";
139        let expected_node = node_text.parse::<NodeId>().unwrap_err().to_string();
140        assert_eq!(
141            parse_node_id(node_text).unwrap_err().to_string(),
142            expected_node
143        );
144
145        let object_text = "not-an-object";
146        let expected_object = object_text.parse::<ObjectId>().unwrap_err().to_string();
147        assert_eq!(
148            parse_object_id(object_text).unwrap_err().to_string(),
149            expected_object
150        );
151    }
152
153    #[test]
154    fn historical_raw_files_remain_decodable_but_reserved_corruption_does_not() {
155        let object_id = ObjectId::from_bytes([0x80, 0, 0, 0, 0, 0]).unwrap();
156        assert!(decode_stored_file(object_id, b"historical payload".to_vec()).is_ok());
157        assert!(decode_stored_file(object_id, b"KFILE".to_vec()).is_err());
158    }
159
160    #[test]
161    fn generated_image_envelope_keeps_existing_metadata() {
162        let payload = vec![1, 2, 3, 4];
163        let actual = generated_image_envelope(payload.clone(), "../result.png", "image/png")
164            .expect("generated image envelope");
165        let expected = encode_file(
166            "generated-image",
167            Some("../result.png"),
168            "image/png",
169            Some("image"),
170            payload,
171        )
172        .expect("expected envelope");
173        assert_eq!(actual, expected);
174    }
175
176    #[test]
177    fn generated_image_provenance_keeps_existing_fields() {
178        let timestamp = DateTime::from_timestamp(1_700_000_000, 123_456_789).unwrap();
179        let provenance = generated_image_provenance("image-model", timestamp);
180        assert_eq!(provenance.author, "image-model");
181        assert_eq!(provenance.source, "kennedy-generated-image");
182        assert_eq!(provenance.source_created_at, timestamp);
183        assert_eq!(
184            provenance.data,
185            "Image generated or modified through Kennedy intelligence."
186        );
187    }
188
189    #[test]
190    fn expected_manager_failures_remain_visible() {
191        assert_eq!(
192            manager_error_message(ErrorKind::InvalidInput, "invalid".into()),
193            "invalid"
194        );
195        assert_eq!(
196            manager_error_message(ErrorKind::NotFound, "missing".into()),
197            "missing"
198        );
199        assert_eq!(
200            manager_error_message(ErrorKind::Conflict, "conflict".into()),
201            "conflict"
202        );
203    }
204}