plexor-codec-serde-postcard 0.1.0-alpha.2

Postcard codec implementation for the Plexo distributed system architecture using Serde.
Documentation
// Copyright 2025 Alecks Gates
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use plexor_codec_serde_postcard::SerdePostcardCodec;
use plexor_core::namespace::NamespaceImpl;
use plexor_core::neuron::{Neuron, NeuronImpl};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct SerdeStruct {
    num: i32,
    text: String,
}

// New response type structs beyond just DebugStruct
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct ApiResponse {
    status: String,
    code: u16,
    data: Option<String>,
    timestamp: u64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct TaskResult {
    task_id: String,
    success: bool,
    result: Option<HashMap<String, String>>,
    error: Option<String>,
    execution_time_ms: u64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct MetricsReport {
    service_name: String,
    cpu_usage: f64,
    memory_mb: u64,
    request_count: u64,
    error_rate: f32,
    uptime_seconds: u64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct UserEvent {
    user_id: String,
    event_type: String,
    properties: HashMap<String, String>,
    session_id: Option<String>,
    timestamp: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct NotificationPayload {
    recipient_id: String,
    message: String,
    priority: NotificationPriority,
    channels: Vec<String>,
    metadata: Option<HashMap<String, String>>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum NotificationPriority {
    Low,
    Normal,
    High,
    Critical,
}
#[test]
fn test_postcard_neuron() {
    let ms = SerdeStruct {
        num: 42,
        text: "Hello, plexor!".to_string(),
    };

    let ns = NamespaceImpl {
        delimiter: ".",
        parts: vec!["dev", "plexo"],
    };

    let neuron: NeuronImpl<SerdeStruct, SerdePostcardCodec> =
        NeuronImpl::new(Arc::new(ns.clone()));
    assert_eq!(neuron.name(), "dev.plexo.SerdeStruct.postcard");
    assert_eq!(neuron.name_without_codec(), "dev.plexo.SerdeStruct");

    let encoded = neuron.encode(&ms).expect("Encoding should succeed");
    // Postcard is binary, so we don't check string representation
    let decoded = neuron
        .decode(&encoded)
        .expect("Decoding should succeed");
    assert_eq!(decoded.num, ms.num);
    assert_eq!(decoded.text, ms.text);
}

#[test]
fn test_api_response_postcard_codec() {
    let response = ApiResponse {
        status: "success".to_string(),
        code: 200,
        data: Some("Operation completed".to_string()),
        timestamp: 1609459200,
    };

    let ns = NamespaceImpl {
        delimiter: ".",
        parts: vec!["api", "v1"],
    };

    let neuron: NeuronImpl<ApiResponse, SerdePostcardCodec> = NeuronImpl::new(Arc::new(ns));
    assert_eq!(neuron.name(), "api.v1.ApiResponse.postcard");

    let encoded = neuron.encode(&response).expect("Encoding should succeed");
    let decoded = neuron.decode(&encoded).expect("Decoding should succeed");
    assert_eq!(decoded, response);
}

#[test]
fn test_task_result_postcard_codec() {
    let mut result_data = HashMap::new();
    result_data.insert("output".to_string(), "processed 100 items".to_string());
    result_data.insert("duration".to_string(), "5.2s".to_string());

    let task = TaskResult {
        task_id: "task_12345".to_string(),
        success: true,
        result: Some(result_data),
        error: None,
        execution_time_ms: 5200,
    };

    let ns = NamespaceImpl {
        delimiter: ".",
        parts: vec!["worker", "tasks"],
    };

    let neuron: NeuronImpl<TaskResult, SerdePostcardCodec> = NeuronImpl::new(Arc::new(ns));
    assert_eq!(neuron.name(), "worker.tasks.TaskResult.postcard");

    let encoded = neuron.encode(&task).expect("Encoding should succeed");
    let decoded = neuron.decode(&encoded).expect("Decoding should succeed");
    assert_eq!(decoded, task);
}

#[test]
fn test_metrics_report_postcard_codec() {
    let metrics = MetricsReport {
        service_name: "api-gateway".to_string(),
        cpu_usage: 75.5,
        memory_mb: 512,
        request_count: 10000,
        error_rate: 0.02,
        uptime_seconds: 3600,
    };

    let ns = NamespaceImpl {
        delimiter: ".",
        parts: vec!["monitoring"],
    };

    let neuron: NeuronImpl<MetricsReport, SerdePostcardCodec> = NeuronImpl::new(Arc::new(ns));
    assert_eq!(neuron.name(), "monitoring.MetricsReport.postcard");

    let encoded = neuron.encode(&metrics).expect("Encoding should succeed");
    let decoded = neuron.decode(&encoded).expect("Decoding should succeed");
    assert_eq!(decoded, metrics);
}

#[test]
fn test_user_event_postcard_codec() {
    let mut properties = HashMap::new();
    properties.insert(
        "page".to_string(),
        "homepage".to_string(),
    );
    properties.insert(
        "duration".to_string(),
        "120".to_string(),
    );

    let event = UserEvent {
        user_id: "user_789".to_string(),
        event_type: "page_view".to_string(),
        properties,
        session_id: Some("session_abc123".to_string()),
        timestamp: chrono::Utc::now(),
    };

    let ns = NamespaceImpl {
        delimiter: ".",
        parts: vec!["analytics"],
    };

    let neuron: NeuronImpl<UserEvent, SerdePostcardCodec> = NeuronImpl::new(Arc::new(ns));
    assert_eq!(neuron.name(), "analytics.UserEvent.postcard");

    let encoded = neuron.encode(&event).expect("Encoding should succeed");
    let decoded = neuron.decode(&encoded).expect("Decoding should succeed");
    assert_eq!(decoded.user_id, event.user_id);
    assert_eq!(decoded.event_type, event.event_type);
    assert_eq!(decoded.properties, event.properties);
    assert_eq!(decoded.session_id, event.session_id);
}

#[test]
fn test_notification_payload_postcard_codec() {
    let mut metadata = HashMap::new();
    metadata.insert("campaign_id".to_string(), "promo_2024".to_string());

    let notification = NotificationPayload {
        recipient_id: "user_456".to_string(),
        message: "Your order has been shipped!".to_string(),
        priority: NotificationPriority::High,
        channels: vec!["email".to_string(), "push".to_string()],
        metadata: Some(metadata),
    };

    let ns = NamespaceImpl {
        delimiter: ".",
        parts: vec!["notifications"],
    };

    let neuron: NeuronImpl<NotificationPayload, SerdePostcardCodec> = NeuronImpl::new(Arc::new(ns));
    assert_eq!(neuron.name(), "notifications.NotificationPayload.postcard");

    let encoded = neuron
        .encode(&notification)
        .expect("Encoding should succeed");
    let decoded = neuron.decode(&encoded).expect("Decoding should succeed");
    assert_eq!(decoded, notification);
}

#[test]
fn test_notification_priority_enum_roundtrip() {
    let original = NotificationPriority::Critical;
    let encoded = postcard::to_allocvec(&original).expect("Should encode");
    let decoded: NotificationPriority = postcard::from_bytes(&encoded).expect("Should decode");
    assert_eq!(original, decoded);
}