Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ida98135cf868db0f5a46a64b8ac562366a6a6964
239 lines
6.5 KiB
Rust
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"));
|
|
}
|
|
}
|