kcode-kennedy-session-kmap-services 0.1.0

Kennedy logical-session Kmap capability facade
Documentation
//! Typed access to a Kennedy logical session's Kmap capability.

#![forbid(unsafe_code)]

use chrono::{DateTime, Utc};
use kcode_kweb_db::{Node, NodeId, ObjectId, Provenance};
use kcode_kweb_manager::{ErrorKind, KwebManager};
use kcode_server_object_envelopes::{StoredFile, decode_file, encode_file};

const GENERATED_IMAGE_ENVELOPE_KIND: &str = "generated-image";
const GENERATED_IMAGE_TRANSPORT_KIND: &str = "image";
const GENERATED_IMAGE_SOURCE: &str = "kennedy-generated-image";
const GENERATED_IMAGE_PROVENANCE_DATA: &str =
    "Image generated or modified through Kennedy intelligence.";
const UNEXPECTED_KMAP_ERROR: &str = "An unexpected Kmap database error occurred.";

#[derive(Debug, Clone)]
pub struct Error {
    message: String,
}

impl std::fmt::Display for Error {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(&self.message)
    }
}

impl std::error::Error for Error {}

#[derive(Clone)]
pub struct Service {
    manager: KwebManager,
}

impl Service {
    pub fn new(manager: KwebManager) -> Self {
        Self { manager }
    }

    pub fn manager(&self) -> &KwebManager {
        &self.manager
    }

    pub fn node(&self, node_id: &str) -> Result<Node, Error> {
        let node_id = parse_node_id(node_id)?;
        self.manager.get_node(node_id).map_err(manager_error)
    }

    pub fn file(&self, object_id: &str) -> Result<StoredFile, Error> {
        let object_id = parse_object_id(object_id)?;
        let bytes = self.manager.get_object(object_id).map_err(manager_error)?;
        decode_stored_file(object_id, bytes)
    }

    pub fn commit_session(
        &self,
        input: kcode_commit_session::CommitRequest,
    ) -> Result<kcode_commit_session::CommitReceipt, Error> {
        self.manager.commit_session(input).map_err(manager_error)
    }

    pub fn save_generated_image(
        &self,
        bytes: Vec<u8>,
        file_name: &str,
        media_type: &str,
        model: &str,
    ) -> Result<String, Error> {
        let bytes = generated_image_envelope(bytes, file_name, media_type)?;
        self.manager
            .store_object(generated_image_provenance(model, Utc::now()), bytes)
            .map(|id| id.to_string())
            .map_err(manager_error)
    }
}

fn parse_node_id(node_id: &str) -> Result<NodeId, Error> {
    node_id.parse().map_err(local_error)
}

fn parse_object_id(object_id: &str) -> Result<ObjectId, Error> {
    object_id.parse().map_err(local_error)
}

fn decode_stored_file(object_id: ObjectId, bytes: Vec<u8>) -> Result<StoredFile, Error> {
    decode_file(object_id, bytes).map_err(local_error)
}

fn generated_image_envelope(
    bytes: Vec<u8>,
    file_name: &str,
    media_type: &str,
) -> Result<Vec<u8>, Error> {
    encode_file(
        GENERATED_IMAGE_ENVELOPE_KIND,
        Some(file_name),
        media_type,
        Some(GENERATED_IMAGE_TRANSPORT_KIND),
        bytes,
    )
    .map_err(local_error)
}

fn generated_image_provenance(model: &str, source_created_at: DateTime<Utc>) -> Provenance {
    Provenance {
        author: model.into(),
        source: GENERATED_IMAGE_SOURCE.into(),
        source_created_at,
        data: GENERATED_IMAGE_PROVENANCE_DATA.into(),
    }
}

fn manager_error(error: kcode_kweb_manager::Error) -> Error {
    Error {
        message: manager_error_message(error.kind(), error.to_string()),
    }
}

fn manager_error_message(kind: ErrorKind, message: String) -> String {
    match kind {
        ErrorKind::InvalidInput | ErrorKind::NotFound | ErrorKind::Conflict => message,
        _ => UNEXPECTED_KMAP_ERROR.into(),
    }
}

fn local_error(error: impl std::fmt::Display) -> Error {
    Error {
        message: error.to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn canonical_id_errors_preserve_dependency_messages() {
        let node_text = "not-a-node";
        let expected_node = node_text.parse::<NodeId>().unwrap_err().to_string();
        assert_eq!(
            parse_node_id(node_text).unwrap_err().to_string(),
            expected_node
        );

        let object_text = "not-an-object";
        let expected_object = object_text.parse::<ObjectId>().unwrap_err().to_string();
        assert_eq!(
            parse_object_id(object_text).unwrap_err().to_string(),
            expected_object
        );
    }

    #[test]
    fn historical_raw_files_remain_decodable_but_reserved_corruption_does_not() {
        let object_id = ObjectId::from_bytes([0x80, 0, 0, 0, 0, 0]).unwrap();
        assert!(decode_stored_file(object_id, b"historical payload".to_vec()).is_ok());
        assert!(decode_stored_file(object_id, b"KFILE".to_vec()).is_err());
    }

    #[test]
    fn generated_image_envelope_keeps_existing_metadata() {
        let payload = vec![1, 2, 3, 4];
        let actual = generated_image_envelope(payload.clone(), "../result.png", "image/png")
            .expect("generated image envelope");
        let expected = encode_file(
            "generated-image",
            Some("../result.png"),
            "image/png",
            Some("image"),
            payload,
        )
        .expect("expected envelope");
        assert_eq!(actual, expected);
    }

    #[test]
    fn generated_image_provenance_keeps_existing_fields() {
        let timestamp = DateTime::from_timestamp(1_700_000_000, 123_456_789).unwrap();
        let provenance = generated_image_provenance("image-model", timestamp);
        assert_eq!(provenance.author, "image-model");
        assert_eq!(provenance.source, "kennedy-generated-image");
        assert_eq!(provenance.source_created_at, timestamp);
        assert_eq!(
            provenance.data,
            "Image generated or modified through Kennedy intelligence."
        );
    }

    #[test]
    fn expected_manager_failures_remain_visible() {
        assert_eq!(
            manager_error_message(ErrorKind::InvalidInput, "invalid".into()),
            "invalid"
        );
        assert_eq!(
            manager_error_message(ErrorKind::NotFound, "missing".into()),
            "missing"
        );
        assert_eq!(
            manager_error_message(ErrorKind::Conflict, "conflict".into()),
            "conflict"
        );
    }
}