pinakes/crates/pinakes-core/src/plugin/rpc.rs
NotAShelf 4edda201e6
pinakes-core: add plugin pipeline; impl signature verification & dependency resolution
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ida98135cf868db0f5a46a64b8ac562366a6a6964
2026-03-08 15:16:58 +03:00

239 lines
6.5 KiB
Rust

//! JSON RPC types for structured plugin function calls.
//!
//! Each extension point maps to well-known exported function names.
//! Requests are serialized to JSON, passed to the plugin, and responses
//! are deserialized from JSON written by the plugin via `host_set_result`.
use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
/// Request to check if a plugin can handle a file
#[derive(Debug, Serialize)]
pub struct CanHandleRequest {
pub path: PathBuf,
pub mime_type: Option<String>,
}
/// Response from `can_handle`
#[derive(Debug, Deserialize)]
pub struct CanHandleResponse {
pub can_handle: bool,
}
/// Media type definition returned by `supported_media_types`
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMediaTypeDefinition {
pub id: String,
pub name: String,
pub category: Option<String>,
pub extensions: Vec<String>,
pub mime_types: Vec<String>,
}
/// Request to extract metadata from a file
#[derive(Debug, Serialize)]
pub struct ExtractMetadataRequest {
pub path: PathBuf,
}
/// Metadata response from a plugin (all fields optional for partial results)
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ExtractMetadataResponse {
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub artist: Option<String>,
#[serde(default)]
pub album: Option<String>,
#[serde(default)]
pub genre: Option<String>,
#[serde(default)]
pub year: Option<i32>,
#[serde(default)]
pub duration_secs: Option<f64>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub extra: HashMap<String, String>,
}
/// Request to generate a thumbnail
#[derive(Debug, Serialize)]
pub struct GenerateThumbnailRequest {
pub source_path: PathBuf,
pub output_path: PathBuf,
pub max_width: u32,
pub max_height: u32,
pub format: String,
}
/// Response from thumbnail generation
#[derive(Debug, Deserialize)]
pub struct GenerateThumbnailResponse {
pub path: PathBuf,
pub width: u32,
pub height: u32,
pub format: String,
}
/// Event sent to event handler plugins
#[derive(Debug, Serialize)]
pub struct HandleEventRequest {
pub event_type: String,
pub payload: serde_json::Value,
}
/// Search request for search backend plugins
#[derive(Debug, Serialize)]
pub struct SearchRequest {
pub query: String,
pub limit: usize,
pub offset: usize,
}
/// Search response
#[derive(Debug, Clone, Deserialize)]
pub struct SearchResponse {
pub results: Vec<SearchResultItem>,
#[serde(default)]
pub total_count: Option<usize>,
}
/// Individual search result
#[derive(Debug, Clone, Deserialize)]
pub struct SearchResultItem {
pub id: String,
pub score: f64,
pub snippet: Option<String>,
}
/// Request to index a media item in a search backend
#[derive(Debug, Serialize)]
pub struct IndexItemRequest {
pub id: String,
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub description: Option<String>,
pub tags: Vec<String>,
pub media_type: String,
pub path: PathBuf,
}
/// Request to remove a media item from a search backend
#[derive(Debug, Serialize)]
pub struct RemoveItemRequest {
pub id: String,
}
/// A theme definition returned by a theme provider plugin
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginThemeDefinition {
pub id: String,
pub name: String,
pub description: Option<String>,
pub dark: bool,
}
/// Response from `load_theme`
#[derive(Debug, Clone, Deserialize)]
pub struct LoadThemeResponse {
pub css: Option<String>,
pub colors: HashMap<String, String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_metadata_request_serialization() {
let req = ExtractMetadataRequest {
path: "/tmp/test.mp3".into(),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("/tmp/test.mp3"));
}
#[test]
fn test_extract_metadata_response_partial() {
let json = r#"{"title":"My Song","extra":{"bpm":"120"}}"#;
let resp: ExtractMetadataResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.title.as_deref(), Some("My Song"));
assert_eq!(resp.artist, None);
assert_eq!(resp.extra.get("bpm").map(String::as_str), Some("120"));
}
#[test]
fn test_extract_metadata_response_empty() {
let json = "{}";
let resp: ExtractMetadataResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.title, None);
assert!(resp.extra.is_empty());
}
#[test]
fn test_can_handle_response() {
let json = r#"{"can_handle":true}"#;
let resp: CanHandleResponse = serde_json::from_str(json).unwrap();
assert!(resp.can_handle);
}
#[test]
fn test_can_handle_response_false() {
let json = r#"{"can_handle":false}"#;
let resp: CanHandleResponse = serde_json::from_str(json).unwrap();
assert!(!resp.can_handle);
}
#[test]
fn test_plugin_media_type_definition_round_trip() {
let def = PluginMediaTypeDefinition {
id: "heif".to_string(),
name: "HEIF Image".to_string(),
category: Some("image".to_string()),
extensions: vec!["heif".to_string(), "heic".to_string()],
mime_types: vec!["image/heif".to_string()],
};
let json = serde_json::to_string(&def).unwrap();
let parsed: PluginMediaTypeDefinition =
serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, "heif");
assert_eq!(parsed.extensions.len(), 2);
}
#[test]
fn test_search_response() {
let json =
r#"{"results":[{"id":"abc","score":0.95,"snippet":"match here"}]}"#;
let resp: SearchResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.results.len(), 1);
assert_eq!(resp.results[0].id, "abc");
}
#[test]
fn test_generate_thumbnail_request_serialization() {
let req = GenerateThumbnailRequest {
source_path: "/media/photo.heif".into(),
output_path: "/tmp/thumb.jpg".into(),
max_width: 256,
max_height: 256,
format: "jpeg".to_string(),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("photo.heif"));
assert!(json.contains("256"));
}
#[test]
fn test_handle_event_request_serialization() {
let req = HandleEventRequest {
event_type: "MediaImported".to_string(),
payload: serde_json::json!({"id": "abc-123"}),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("MediaImported"));
assert!(json.contains("abc-123"));
}
}