kcode_kennedy_session_kmap_services/
lib.rs1#![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}