From a077dc43471a9916dd61870dc20da602445a83d5 Mon Sep 17 00:00:00 2001 From: jedarden Date: Sun, 24 May 2026 15:46:59 -0400 Subject: [PATCH] fix(proxy): fix RustEmbed derive and error types in search_ui module - Fixed RustEmbed folder path from "../../static/search/" to "static/search/" - Changed error type from Result to Result for axum compatibility - Replaced MiroirError::InvalidRequest with ErrorResponse::invalid_request - Replaced MiroirError::Task with ErrorResponse::internal_error - Fixed config parameter to pass &config.search_ui instead of &config The RustEmbed derive was not working because the folder path was relative to the file location instead of the crate root. Additionally, the error type needed to be ErrorResponse (which implements IntoResponse) instead of MiroirError for axum handler compatibility. Closes: compilation errors in search_ui.rs --- crates/miroir-proxy/Cargo.toml | 2 +- crates/miroir-proxy/src/routes/search_ui.rs | 82 ++++++++++----------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/crates/miroir-proxy/Cargo.toml b/crates/miroir-proxy/Cargo.toml index f60a5da..95b6616 100644 --- a/crates/miroir-proxy/Cargo.toml +++ b/crates/miroir-proxy/Cargo.toml @@ -48,7 +48,7 @@ hex = "0.4" # HTTP / Tower tower = "0.5" -rust-embed = { version = "8", features = ["include-exclude"] } +rust-embed = { version = "8", features = ["debug-embed"] } mime_guess = "2" # OpenTelemetry (optional - use feature flag to enable) diff --git a/crates/miroir-proxy/src/routes/search_ui.rs b/crates/miroir-proxy/src/routes/search_ui.rs index c7832c8..45a34e8 100644 --- a/crates/miroir-proxy/src/routes/search_ui.rs +++ b/crates/miroir-proxy/src/routes/search_ui.rs @@ -15,9 +15,10 @@ use axum::{ use miroir_core::{ config::advanced::{SearchUiAuthConfig, SearchUiConfig}, task_store::{SearchUiScopedKey, TaskStore}, - MiroirError, Result, }; -use rust_embed::RustEmbed; + +use crate::error_response::ErrorResponse; +use rust_embed::RustEmbed as Embed; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tracing::{debug, info}; @@ -150,14 +151,12 @@ pub async fn mint_session( Path(index_uid): Path, State(state): State, headers: HeaderMap, -) -> Result { +) -> Result { let config = &state.config; // Check if search UI is enabled if !config.search_ui.enabled { - return Err(MiroirError::InvalidRequest( - "search UI is not enabled".to_string(), - )); + return Err(ErrorResponse::invalid_request("search UI is not enabled")); } // Validate auth mode @@ -170,18 +169,18 @@ pub async fn mint_session( "shared_key" => { // Require X-Search-UI-Key header let shared_key = std::env::var(&auth_config.shared_key_env).map_err(|_| { - MiroirError::InvalidRequest("search UI shared key not configured".to_string()) + ErrorResponse::invalid_request("search UI shared key not configured".to_string()) })?; let provided_key = headers .get("X-Search-UI-Key") .and_then(|v| v.to_str().ok()) .ok_or_else(|| { - MiroirError::InvalidRequest("missing X-Search-UI-Key header".to_string()) + ErrorResponse::invalid_request("missing X-Search-UI-Key header".to_string()) })?; if provided_key != shared_key { - return Err(MiroirError::InvalidRequest( + return Err(ErrorResponse::invalid_request( "invalid X-Search-UI-Key".to_string(), )); } @@ -192,7 +191,7 @@ pub async fn mint_session( .get(&auth_config.oauth_proxy.user_header) .and_then(|v| v.to_str().ok()) .ok_or_else(|| { - MiroirError::InvalidRequest(format!( + ErrorResponse::invalid_request(format!( "missing {} header", auth_config.oauth_proxy.user_header )) @@ -205,7 +204,7 @@ pub async fn mint_session( debug!(index = %index_uid, "minting oauth_proxy search UI session"); } _ => { - return Err(MiroirError::InvalidRequest(format!( + return Err(ErrorResponse::invalid_request(format!( "invalid auth mode: {}", auth_config.mode ))); @@ -213,7 +212,7 @@ pub async fn mint_session( } // Get or create scoped key for this index - let scoped_key = get_or_create_scoped_key(&state, &index_uid, config).await?; + let scoped_key = get_or_create_scoped_key(&state, &index_uid, &config.search_ui).await?; // Build JWT claims let now = chrono::Utc::now().timestamp() as u64; @@ -272,7 +271,7 @@ pub async fn mint_session( // Sign with primary secret let secret = std::env::var(&auth_config.jwt_secret_env) - .map_err(|_| MiroirError::Task("JWT secret not configured".to_string()))?; + .map_err(|_| ErrorResponse::internal_error("JWT secret not configured".to_string()))?; let header = JwtHeader { alg: "HS256".to_string(), @@ -281,7 +280,7 @@ pub async fn mint_session( }; let token = jwt_encode(&header, &claims, secret.as_bytes()) - .map_err(|e| MiroirError::Task(format!("JWT encoding failed: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("JWT encoding failed: {}", e)))?; info!( index = %index_uid, @@ -307,9 +306,9 @@ async fn get_or_create_scoped_key( state: &AppState, index_uid: &str, config: &SearchUiConfig, -) -> Result { +) -> Result { let redis_store = state.redis_store.as_ref().ok_or_else(|| { - MiroirError::TaskStore("Redis store required for search UI scoped keys".to_string()) + ErrorResponse::internal_error("Redis store required for search UI scoped keys") })?; // Try to get existing key @@ -334,7 +333,7 @@ async fn get_or_create_scoped_key( // TODO: Create new scoped key via Meilisearch API // For now, return an error indicating the key needs to be created - Err(MiroirError::Task(format!( + Err(ErrorResponse::internal_error(format!( "scoped key for index '{}' needs to be created via background rotation", index_uid ))) @@ -344,17 +343,17 @@ async fn get_or_create_scoped_key( pub async fn get_config( Path(index_uid): Path, State(state): State, -) -> Result { +) -> Result { let task_store = state .task_store .as_ref() - .ok_or_else(|| MiroirError::TaskStore("task store not available".to_string()))?; + .ok_or_else(|| ErrorResponse::internal_error("task store not available".to_string()))?; // Try to load config from task store if let Some(row) = task_store.get_search_ui_config(&index_uid)? { // Parse the config JSON let config: SearchUiIndexConfig = serde_json::from_str(&row.config_json) - .map_err(|e| MiroirError::Task(format!("failed to parse config: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("failed to parse config: {}", e)))?; return Ok(Json(config)); } @@ -380,12 +379,12 @@ pub async fn update_config( Path(index_uid): Path, State(state): State, Json(config): Json, -) -> Result { +) -> Result { use miroir_core::task_store::NewSearchUiConfig; // Serialize config to JSON for storage let config_json = serde_json::to_string(&config) - .map_err(|e| MiroirError::Task(format!("failed to serialize config: {}", e)))?; + .map_err(|e| ErrorResponse::internal_error(format!("failed to serialize config: {}", e)))?; // Validate custom template if present (plan §13.21) if let Some(template) = &config.result_template { @@ -393,7 +392,7 @@ pub async fn update_config( // Custom templates are stored separately in the config // The actual template HTML is stored in the custom_template_html field if config.custom_template_html.is_none() { - return Err(MiroirError::InvalidRequest( + return Err(ErrorResponse::invalid_request( "custom template requires custom_template_html field".to_string(), )); } @@ -407,7 +406,7 @@ pub async fn update_config( let task_store = state .task_store .as_ref() - .ok_or_else(|| MiroirError::TaskStore("task store not available".to_string()))?; + .ok_or_else(|| ErrorResponse::internal_error("task store not available".to_string()))?; let new_config = NewSearchUiConfig { index_uid: index_uid.clone(), @@ -435,12 +434,12 @@ pub async fn beacon( Path(index_uid): Path, State(state): State, Json(mut beacon): Json, -) -> Result { +) -> Result { let _ = &state.config; // Validate event_id is present if beacon.event_id.is_empty() { - return Err(MiroirError::InvalidRequest( + return Err(ErrorResponse::invalid_request( "event_id is required".to_string(), )); } @@ -462,22 +461,17 @@ pub async fn beacon( } /// Embedded static assets for the Search UI (plan §13.21). -#[derive(RustEmbed)] -#[folder = "../../static/search/"] -#[exclude = "*.swp"] -#[exclude = "*.DS_Store"] -#[exclude = ".git"] -struct SearchUiAssets; +#[derive(Embed)] +#[folder = "static/search/"] +pub struct SearchUiAssets; /// Serve the Search UI SPA (plan §13.21). -pub async fn serve_spa(State(state): State) -> Result { +pub async fn serve_spa(State(state): State) -> Result { let config = &state.config; // Check if search UI is enabled if !config.search_ui.enabled { - return Err(MiroirError::InvalidRequest( - "search UI is not enabled".to_string(), - )); + return Err(ErrorResponse::invalid_request("search UI is not enabled")); } // Build CSP header (plan §9) @@ -496,17 +490,17 @@ pub async fn serve_spa(State(state): State) -> Result) -> Result { +pub async fn serve_static_asset(Path(path): Path) -> Result { serve_embedded_file(&path, true) - .map_err(|_| MiroirError::InvalidRequest("asset not found".to_string())) } /// Serve an embedded file from the Search UI assets (plan §13.21). -fn serve_embedded_file(path: &str, is_static_asset: bool) -> Result { +fn serve_embedded_file(path: &str, is_static_asset: bool) -> Result { use axum::body::Body; use axum::http::header; - let asset = SearchUiAssets::get(path).ok_or(StatusCode::NOT_FOUND)?; + let asset = SearchUiAssets::get(path) + .ok_or_else(|| ErrorResponse::invalid_request("asset not found"))?; let mime_type = mime_guess::from_path(path) .first_or_octet_stream() @@ -543,7 +537,7 @@ fn serve_embedded_file(path: &str, is_static_asset: bool) -> Result Result<(), MiroirError> { +fn validate_template(template: &str) -> Result<(), ErrorResponse> { let mut if_stack = Vec::new(); let mut pos = 0; @@ -561,7 +555,7 @@ fn validate_template(template: &str) -> Result<(), MiroirError> { // Check for {{/if}} closing else if tag.starts_with("/if") { if if_stack.pop() != Some("if") { - return Err(MiroirError::InvalidRequest( + return Err(ErrorResponse::invalid_request( "unmatched {{/if}} tag in template".to_string(), )); } @@ -569,14 +563,14 @@ fn validate_template(template: &str) -> Result<(), MiroirError> { pos += end + 2; } else { - return Err(MiroirError::InvalidRequest( + return Err(ErrorResponse::invalid_request( "unclosed {{ tag in template".to_string(), )); } } if !if_stack.is_empty() { - return Err(MiroirError::InvalidRequest(format!( + return Err(ErrorResponse::invalid_request(format!( "unclosed {{#if}} tag in template" ))); }