From 4c75e0805645d719a1f96b3d537721f7778454c8 Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Mon, 9 Mar 2026 22:27:52 +0100 Subject: [PATCH] fix(api): resolve all OpenAPI schema reference errors - Add #[schema(value_type = Option)] on chrono::DateTime fields - Register SeriesPage in openapi.rs components - Fix module-prefixed ref (index_jobs::IndexJobResponse -> IndexJobResponse) - Strengthen test: assert all $ref targets exist in components/schemas Co-Authored-By: Claude Sonnet 4.6 --- apps/api/src/libraries.rs | 1 + apps/api/src/openapi.rs | 28 +++++++++++++++++++--------- apps/api/src/thumbnails.rs | 6 +++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/apps/api/src/libraries.rs b/apps/api/src/libraries.rs index b798d4d..c92f6c3 100644 --- a/apps/api/src/libraries.rs +++ b/apps/api/src/libraries.rs @@ -18,6 +18,7 @@ pub struct LibraryResponse { pub book_count: i64, pub monitor_enabled: bool, pub scan_mode: String, + #[schema(value_type = Option)] pub next_scan_at: Option>, pub watcher_enabled: bool, } diff --git a/apps/api/src/openapi.rs b/apps/api/src/openapi.rs index 6cde81e..28ba081 100644 --- a/apps/api/src/openapi.rs +++ b/apps/api/src/openapi.rs @@ -42,6 +42,7 @@ use utoipa::OpenApi; crate::books::BooksPage, crate::books::BookDetails, crate::books::SeriesItem, + crate::books::SeriesPage, crate::pages::PageQuery, crate::search::SearchQuery, crate::search::SearchResponse, @@ -118,15 +119,24 @@ mod tests { .to_pretty_json() .expect("Failed to serialize OpenAPI"); - // Check that there are no references to non-existent schemas - assert!( - !json.contains("\"/components/schemas/Uuid\""), - "Uuid schema should not be referenced" - ); - assert!( - !json.contains("\"/components/schemas/DateTime\""), - "DateTime schema should not be referenced" - ); + // Check that all $ref targets exist in components/schemas + let doc: serde_json::Value = + serde_json::from_str(&json).expect("OpenAPI JSON should be valid"); + let empty = serde_json::Map::new(); + let schemas = doc["components"]["schemas"] + .as_object() + .unwrap_or(&empty); + let prefix = "#/components/schemas/"; + let mut broken: Vec = Vec::new(); + for part in json.split(prefix).skip(1) { + if let Some(name) = part.split('"').next() { + if !schemas.contains_key(name) { + broken.push(name.to_string()); + } + } + } + broken.dedup(); + assert!(broken.is_empty(), "Unresolved schema refs: {:?}", broken); // Save to file for inspection std::fs::write("/tmp/openapi.json", &json).expect("Failed to write file"); diff --git a/apps/api/src/thumbnails.rs b/apps/api/src/thumbnails.rs index 1560e3a..04aaa14 100644 --- a/apps/api/src/thumbnails.rs +++ b/apps/api/src/thumbnails.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use uuid::Uuid; use utoipa::ToSchema; -use crate::{error::ApiError, index_jobs, state::AppState}; +use crate::{error::ApiError, index_jobs::{self, IndexJobResponse}, state::AppState}; #[derive(Deserialize, ToSchema)] pub struct ThumbnailsRebuildRequest { @@ -21,7 +21,7 @@ pub struct ThumbnailsRebuildRequest { tag = "indexing", request_body = Option, responses( - (status = 200, body = index_jobs::IndexJobResponse), + (status = 200, body = IndexJobResponse), (status = 401, description = "Unauthorized"), (status = 403, description = "Forbidden - Admin scope required"), ), @@ -55,7 +55,7 @@ pub async fn start_thumbnails_rebuild( tag = "indexing", request_body = Option, responses( - (status = 200, body = index_jobs::IndexJobResponse), + (status = 200, body = IndexJobResponse), (status = 401, description = "Unauthorized"), (status = 403, description = "Forbidden - Admin scope required"), ),