pinakes-core: add plugin pipeline; impl signature verification & dependency resolution
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ida98135cf868db0f5a46a64b8ac562366a6a6964
This commit is contained in:
parent
8347a714d2
commit
4edda201e6
12 changed files with 2784 additions and 36 deletions
239
crates/pinakes-core/src/plugin/rpc.rs
Normal file
239
crates/pinakes-core/src/plugin/rpc.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
//! 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"));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue