treewide: replace std hashers with rustc_hash alternatives; fix clippy

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I766c36cb53d3d7f9e85b91a67c4131a66a6a6964
This commit is contained in:
raf 2026-03-19 22:34:30 +03:00
commit f831e58723
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
53 changed files with 343 additions and 394 deletions

View file

@ -19,6 +19,7 @@ toml = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }
mime_guess = { workspace = true }
rustc-hash = { workspace = true }
# WASM bridge types
wit-bindgen = { workspace = true, optional = true }

View file

@ -4,12 +4,10 @@
//! Plugins can extend Pinakes by implementing one or more of the provided
//! traits.
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use std::path::{Path, PathBuf};
use async_trait::async_trait;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
@ -74,7 +72,7 @@ pub struct PluginContext {
pub cache_dir: PathBuf,
/// Plugin configuration from manifest
pub config: HashMap<String, serde_json::Value>,
pub config: FxHashMap<String, serde_json::Value>,
/// Capabilities granted to the plugin
pub capabilities: Capabilities,
@ -160,7 +158,7 @@ pub struct PluginMetadata {
pub struct HealthStatus {
pub healthy: bool,
pub message: Option<String>,
pub metrics: HashMap<String, f64>,
pub metrics: FxHashMap<String, f64>,
}
/// Trait for plugins that provide custom media type support
@ -227,7 +225,7 @@ pub struct ExtractedMetadata {
pub bitrate_kbps: Option<u32>,
/// Custom metadata fields specific to this file type
pub custom_fields: HashMap<String, serde_json::Value>,
pub custom_fields: FxHashMap<String, serde_json::Value>,
/// Tags extracted from the file
pub tags: Vec<String>,
@ -301,14 +299,14 @@ pub struct SearchIndexItem {
pub content: Option<String>,
pub tags: Vec<String>,
pub media_type: String,
pub metadata: HashMap<String, serde_json::Value>,
pub metadata: FxHashMap<String, serde_json::Value>,
}
/// Search query
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchQuery {
pub query_text: String,
pub filters: HashMap<String, serde_json::Value>,
pub filters: FxHashMap<String, serde_json::Value>,
pub limit: usize,
pub offset: usize,
}
@ -360,7 +358,7 @@ pub enum EventType {
pub struct Event {
pub event_type: EventType,
pub timestamp: String,
pub data: HashMap<String, serde_json::Value>,
pub data: FxHashMap<String, serde_json::Value>,
}
/// Trait for plugins that provide UI themes
@ -387,7 +385,7 @@ pub struct ThemeDefinition {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Theme {
pub id: String,
pub colors: HashMap<String, String>,
pub fonts: HashMap<String, String>,
pub colors: FxHashMap<String, String>,
pub fonts: FxHashMap<String, String>,
pub custom_css: Option<String>,
}

View file

@ -1,7 +1,8 @@
//! Plugin manifest parsing and validation
use std::{collections::HashMap, path::Path};
use std::path::Path;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
@ -23,7 +24,7 @@ pub struct PluginManifest {
pub capabilities: ManifestCapabilities,
#[serde(default)]
pub config: HashMap<String, toml::Value>,
pub config: FxHashMap<String, toml::Value>,
/// UI pages provided by this plugin
#[serde(default)]
@ -49,8 +50,8 @@ pub struct UiSection {
/// CSS custom property overrides provided by this plugin.
/// Keys are property names (e.g. `--accent-color`), values are CSS values.
/// The host applies these to `document.documentElement` on startup.
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub theme_extensions: HashMap<String, String>,
#[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
pub theme_extensions: FxHashMap<String, String>,
}
impl UiSection {
@ -709,7 +710,7 @@ gap = 16
"/api/v1/media".to_string(),
"/api/plugins/my-plugin/data".to_string(),
],
theme_extensions: HashMap::new(),
theme_extensions: FxHashMap::default(),
};
assert!(section.validate().is_ok());
}
@ -720,7 +721,7 @@ gap = 16
pages: vec![],
widgets: vec![],
required_endpoints: vec!["/not-api/something".to_string()],
theme_extensions: HashMap::new(),
theme_extensions: FxHashMap::default(),
};
assert!(section.validate().is_err());
}
@ -731,7 +732,7 @@ gap = 16
pages: vec![],
widgets: vec![],
required_endpoints: vec!["/api/ok".to_string(), "no-slash".to_string()],
theme_extensions: HashMap::new(),
theme_extensions: FxHashMap::default(),
};
let err = section.validate().unwrap_err();
assert!(

View file

@ -49,8 +49,7 @@
//! Array indices use the same notation: `"items.0.title"`.
//! ```
use std::collections::HashMap;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
@ -133,12 +132,12 @@ pub struct UiPage {
pub root_element: UiElement,
/// Named data sources available to this page
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub data_sources: HashMap<String, DataSource>,
#[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
pub data_sources: FxHashMap<String, DataSource>,
/// Named actions available to this page (referenced by `ActionRef::Name`)
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub actions: HashMap<String, ActionDefinition>,
#[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
pub actions: FxHashMap<String, ActionDefinition>,
}
impl UiPage {
@ -204,8 +203,8 @@ impl UiPage {
/// Validates that there are no cycles in Transform data source dependencies
fn validate_no_cycles(&self) -> SchemaResult<()> {
let mut visited = std::collections::HashSet::new();
let mut stack = std::collections::HashSet::new();
let mut visited = rustc_hash::FxHashSet::default();
let mut stack = rustc_hash::FxHashSet::default();
for name in self.data_sources.keys() {
Self::dfs_check_cycles(self, name, &mut visited, &mut stack)?;
@ -218,8 +217,8 @@ impl UiPage {
fn dfs_check_cycles(
&self,
name: &str,
visited: &mut std::collections::HashSet<String>,
stack: &mut std::collections::HashSet<String>,
visited: &mut rustc_hash::FxHashSet<String>,
stack: &mut rustc_hash::FxHashSet<String>,
) -> SchemaResult<()> {
if stack.contains(name) {
return Err(SchemaError::ValidationError(format!(
@ -1451,8 +1450,8 @@ pub struct ActionDefinition {
pub path: String,
/// Action parameters (merged with form data on submit)
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub params: HashMap<String, serde_json::Value>,
#[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
pub params: FxHashMap<String, serde_json::Value>,
/// Success message
#[serde(skip_serializing_if = "Option::is_none")]
@ -1509,7 +1508,7 @@ impl Default for ActionDefinition {
Self {
method: default_http_method(),
path: String::new(),
params: HashMap::new(),
params: FxHashMap::default(),
success_message: None,
error_message: None,
navigate_to: None,
@ -1543,8 +1542,8 @@ pub enum DataSource {
path: String,
/// Query parameters
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
params: HashMap<String, Expression>,
#[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
params: FxHashMap<String, Expression>,
/// Polling interval in seconds (0 = no polling)
#[serde(default)]
@ -1839,7 +1838,7 @@ mod tests {
let valid = DataSource::Endpoint {
method: HttpMethod::Get,
path: "/api/test".to_string(),
params: HashMap::new(),
params: FxHashMap::default(),
poll_interval: 0,
transform: None,
};
@ -1848,7 +1847,7 @@ mod tests {
let invalid = DataSource::Endpoint {
method: HttpMethod::Get,
path: "api/test".to_string(),
params: HashMap::new(),
params: FxHashMap::default(),
poll_interval: 0,
transform: None,
};
@ -1898,8 +1897,8 @@ mod tests {
page_size: 0,
row_actions: vec![],
},
data_sources: HashMap::new(),
actions: HashMap::new(),
data_sources: FxHashMap::default(),
actions: FxHashMap::default(),
};
let refs = page.referenced_data_sources();
@ -1918,8 +1917,8 @@ mod tests {
columns: 13,
gap: 16,
},
data_sources: HashMap::new(),
actions: HashMap::new(),
data_sources: FxHashMap::default(),
actions: FxHashMap::default(),
};
assert!(page.validate().is_err());
@ -1937,8 +1936,8 @@ mod tests {
content: TextContent::Static("Title".to_string()),
id: None,
},
data_sources: HashMap::new(),
actions: HashMap::new(),
data_sources: FxHashMap::default(),
actions: FxHashMap::default(),
};
assert!(page.validate().is_err());
@ -2005,7 +2004,7 @@ mod tests {
let bad = DataSource::Endpoint {
method: HttpMethod::Get,
path: "/not-api/something".to_string(),
params: HashMap::new(),
params: FxHashMap::default(),
poll_interval: 0,
transform: None,
};
@ -2017,7 +2016,7 @@ mod tests {
let bad = DataSource::Endpoint {
method: HttpMethod::Get,
path: "/api/v1/../admin".to_string(),
params: HashMap::new(),
params: FxHashMap::default(),
poll_interval: 0,
transform: None,
};
@ -2078,7 +2077,7 @@ mod tests {
#[test]
fn test_link_validation_rejects_unsafe_href() {
use std::collections::HashMap as HM;
use rustc_hash::FxHashMap as HM;
let page = UiPage {
id: "p".to_string(),
title: "P".to_string(),
@ -2089,15 +2088,15 @@ mod tests {
href: "javascript:alert(1)".to_string(),
external: false,
},
data_sources: HM::new(),
actions: HM::new(),
data_sources: HM::default(),
actions: HM::default(),
};
assert!(page.validate().is_err());
}
#[test]
fn test_reserved_route_rejected() {
use std::collections::HashMap as HM;
use rustc_hash::FxHashMap as HM;
let page = UiPage {
id: "search-page".to_string(),
title: "Search".to_string(),
@ -2108,8 +2107,8 @@ mod tests {
gap: 0,
padding: None,
},
data_sources: HM::new(),
actions: HM::new(),
data_sources: HM::default(),
actions: HM::default(),
};
let err = page.validate().unwrap_err();
assert!(

View file

@ -343,7 +343,7 @@ impl SchemaValidator {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use rustc_hash::FxHashMap;
use super::*;
use crate::UiElement;
@ -359,8 +359,8 @@ mod tests {
gap: 0,
padding: None,
},
data_sources: HashMap::new(),
actions: HashMap::new(),
data_sources: FxHashMap::default(),
actions: FxHashMap::default(),
}
}

View file

@ -1,7 +1,6 @@
//! WASM bridge types and helpers for plugin communication
use std::collections::HashMap;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
/// Memory allocation info for passing data between host and plugin
@ -93,7 +92,7 @@ pub struct LogMessage {
pub level: LogLevel,
pub target: String,
pub message: String,
pub fields: HashMap<String, String>,
pub fields: FxHashMap<String, String>,
}
/// HTTP request parameters
@ -101,7 +100,7 @@ pub struct LogMessage {
pub struct HttpRequest {
pub method: String,
pub url: String,
pub headers: HashMap<String, String>,
pub headers: FxHashMap<String, String>,
pub body: Option<Vec<u8>>,
}
@ -109,7 +108,7 @@ pub struct HttpRequest {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpResponse {
pub status: u16,
pub headers: HashMap<String, String>,
pub headers: FxHashMap<String, String>,
pub body: Vec<u8>,
}

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, path::PathBuf};
use std::path::PathBuf;
use async_trait::async_trait;
use pinakes_plugin_api::{
@ -25,6 +25,7 @@ use pinakes_plugin_api::{
ThumbnailOptions,
wasm::{HttpRequest, HttpResponse, LogLevel, LogMessage},
};
use rustc_hash::FxHashMap;
struct TestPlugin {
initialized: bool,
@ -41,7 +42,7 @@ impl TestPlugin {
health_status: HealthStatus {
healthy: true,
message: Some("OK".to_string()),
metrics: HashMap::new(),
metrics: FxHashMap::default(),
},
metadata: PluginMetadata {
id: "test-plugin".to_string(),
@ -82,7 +83,7 @@ async fn test_plugin_context_creation() {
let context = PluginContext {
data_dir: PathBuf::from("/data/test-plugin"),
cache_dir: PathBuf::from("/cache/test-plugin"),
config: HashMap::from([
config: FxHashMap::from([
("enabled".to_string(), serde_json::json!(true)),
("max_items".to_string(), serde_json::json!(100)),
]),
@ -119,7 +120,7 @@ async fn test_plugin_context_fields() {
let context = PluginContext {
data_dir: PathBuf::from("/custom/data"),
cache_dir: PathBuf::from("/custom/cache"),
config: HashMap::new(),
config: FxHashMap::default(),
capabilities: Capabilities::default(),
};
@ -137,7 +138,7 @@ async fn test_plugin_lifecycle() {
let context = PluginContext {
data_dir: PathBuf::from("/data"),
cache_dir: PathBuf::from("/cache"),
config: HashMap::new(),
config: FxHashMap::default(),
capabilities: Capabilities::default(),
};
plugin.initialize(context).await.unwrap();
@ -164,7 +165,7 @@ async fn test_extracted_metadata_structure() {
file_size_bytes: Some(1_500_000),
codec: Some("h264".to_string()),
bitrate_kbps: Some(5000),
custom_fields: HashMap::from([
custom_fields: FxHashMap::from([
("color_space".to_string(), serde_json::json!("sRGB")),
("orientation".to_string(), serde_json::json!(90)),
]),
@ -182,7 +183,7 @@ async fn test_extracted_metadata_structure() {
async fn test_search_query_serialization() {
let query = SearchQuery {
query_text: "nature landscape".to_string(),
filters: HashMap::from([
filters: FxHashMap::from([
("type".to_string(), serde_json::json!("image")),
("year".to_string(), serde_json::json!(2023)),
]),
@ -329,7 +330,7 @@ async fn test_event_serialization() {
let event = Event {
event_type: EventType::MediaImported,
timestamp: "2024-01-15T10:00:00Z".to_string(),
data: HashMap::from([
data: FxHashMap::from([
("path".to_string(), serde_json::json!("/media/test.jpg")),
("size".to_string(), serde_json::json!(1024)),
]),
@ -347,7 +348,7 @@ async fn test_http_request_serialization() {
let request = HttpRequest {
method: "GET".to_string(),
url: "https://api.example.com/data".to_string(),
headers: HashMap::from([
headers: FxHashMap::from([
("Authorization".to_string(), "Bearer token".to_string()),
("Content-Type".to_string(), "application/json".to_string()),
]),
@ -366,7 +367,7 @@ async fn test_http_request_serialization() {
async fn test_http_response_serialization() {
let response = HttpResponse {
status: 200,
headers: HashMap::from([(
headers: FxHashMap::from([(
"Content-Type".to_string(),
"application/json".to_string(),
)]),
@ -386,7 +387,7 @@ async fn test_log_message_serialization() {
level: LogLevel::Info,
target: "plugin::metadata".to_string(),
message: "Metadata extraction complete".to_string(),
fields: HashMap::from([
fields: FxHashMap::from([
("file_count".to_string(), "42".to_string()),
("duration_ms".to_string(), "150".to_string()),
]),
@ -453,7 +454,7 @@ async fn test_search_index_item_serialization() {
"photos".to_string(),
],
media_type: "image/jpeg".to_string(),
metadata: HashMap::from([
metadata: FxHashMap::from([
("camera".to_string(), serde_json::json!("Canon EOS R5")),
("location".to_string(), serde_json::json!("Beach")),
]),
@ -474,7 +475,7 @@ async fn test_health_status_variants() {
let healthy = HealthStatus {
healthy: true,
message: Some("All systems operational".to_string()),
metrics: HashMap::from([
metrics: FxHashMap::from([
("items_processed".to_string(), 1000.0),
("avg_process_time_ms".to_string(), 45.5),
]),
@ -484,7 +485,7 @@ async fn test_health_status_variants() {
let unhealthy = HealthStatus {
healthy: false,
message: Some("Database connection failed".to_string()),
metrics: HashMap::new(),
metrics: FxHashMap::default(),
};
assert!(!unhealthy.healthy);
assert_eq!(
@ -571,7 +572,7 @@ async fn test_extracted_metadata_default() {
async fn test_search_query_structure() {
let query = SearchQuery {
query_text: "test query".to_string(),
filters: HashMap::new(),
filters: FxHashMap::default(),
limit: 10,
offset: 0,
};

View file

@ -3,8 +3,6 @@
//! Renderer-level behaviour (e.g., Dioxus components) is out of scope here;
//! that requires a Dioxus runtime and belongs in pinakes-ui tests.
use std::collections::HashMap;
use pinakes_plugin_api::{
DataSource,
HttpMethod,
@ -26,8 +24,8 @@ fn make_page(id: &str, route: &str) -> UiPage {
gap: 0,
padding: None,
},
data_sources: HashMap::new(),
actions: HashMap::new(),
data_sources: Default::default(),
actions: Default::default(),
}
}