use std::collections::HashMap;
use diaryx_core::plugin::permissions::PluginPermissions;
use serde::{Deserialize, Serialize};
pub const CURRENT_PROTOCOL_VERSION: u32 = 1;
pub const MIN_SUPPORTED_PROTOCOL_VERSION: u32 = 1;
fn default_protocol_version() -> u32 {
1
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GuestRequestedPermissions {
#[serde(default)]
pub defaults: PluginPermissions,
#[serde(default)]
pub reasons: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuestManifest {
#[serde(default = "default_protocol_version")]
pub protocol_version: u32,
pub id: String,
pub name: String,
pub version: String,
pub description: String,
pub capabilities: Vec<String>,
#[serde(default)]
pub ui: Vec<serde_json::Value>,
#[serde(default)]
pub commands: Vec<String>,
#[serde(default)]
pub cli: Vec<serde_json::Value>,
#[serde(default)]
pub requested_permissions: Option<GuestRequestedPermissions>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuestEvent {
pub event_type: String,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandRequest {
pub command: String,
pub params: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandResponse {
pub success: bool,
#[serde(default)]
pub data: Option<serde_json::Value>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub error_code: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn guest_manifest_roundtrip() {
let manifest = GuestManifest {
protocol_version: 1,
id: "com.example.test".into(),
name: "Test Plugin".into(),
version: "0.1.0".into(),
description: "A test plugin".into(),
capabilities: vec!["file_events".into(), "custom_commands".into()],
ui: vec![],
commands: vec!["do-thing".into()],
cli: vec![],
requested_permissions: None,
};
let json = serde_json::to_string(&manifest).unwrap();
let parsed: GuestManifest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.protocol_version, 1);
assert_eq!(parsed.id, "com.example.test");
assert_eq!(parsed.commands, vec!["do-thing"]);
}
#[test]
fn guest_manifest_defaults_protocol_version_to_1() {
let json =
r#"{"id":"test","name":"T","version":"1.0","description":"d","capabilities":[]}"#;
let m: GuestManifest = serde_json::from_str(json).unwrap();
assert_eq!(m.protocol_version, 1);
}
#[test]
fn guest_manifest_explicit_protocol_version() {
let json = r#"{"protocol_version":2,"id":"test","name":"T","version":"1.0","description":"d","capabilities":[]}"#;
let m: GuestManifest = serde_json::from_str(json).unwrap();
assert_eq!(m.protocol_version, 2);
}
#[test]
fn command_response_roundtrip() {
let resp = CommandResponse {
success: true,
data: Some(serde_json::json!({"count": 42})),
error: None,
error_code: None,
};
let json = serde_json::to_string(&resp).unwrap();
let parsed: CommandResponse = serde_json::from_str(&json).unwrap();
assert!(parsed.success);
assert_eq!(parsed.data.unwrap()["count"], 42);
}
#[test]
fn command_response_without_error_code() {
let json = r#"{"success":false,"error":"oops"}"#;
let resp: CommandResponse = serde_json::from_str(json).unwrap();
assert!(!resp.success);
assert_eq!(resp.error.as_deref(), Some("oops"));
assert!(resp.error_code.is_none());
}
#[test]
fn command_response_with_error_code() {
let json = r#"{"success":false,"error":"denied","error_code":"permission_denied"}"#;
let resp: CommandResponse = serde_json::from_str(json).unwrap();
assert!(!resp.success);
assert_eq!(resp.error_code.as_deref(), Some("permission_denied"));
}
#[test]
fn guest_event_roundtrip() {
let event = GuestEvent {
event_type: "file_saved".into(),
payload: serde_json::json!({"path": "2024/01/entry.md"}),
};
let json = serde_json::to_string(&event).unwrap();
let parsed: GuestEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type, "file_saved");
}
}