fix(api): resolve all OpenAPI schema reference errors
- Add #[schema(value_type = Option<String>)] 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 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ pub struct LibraryResponse {
|
|||||||
pub book_count: i64,
|
pub book_count: i64,
|
||||||
pub monitor_enabled: bool,
|
pub monitor_enabled: bool,
|
||||||
pub scan_mode: String,
|
pub scan_mode: String,
|
||||||
|
#[schema(value_type = Option<String>)]
|
||||||
pub next_scan_at: Option<chrono::DateTime<chrono::Utc>>,
|
pub next_scan_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
pub watcher_enabled: bool,
|
pub watcher_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ use utoipa::OpenApi;
|
|||||||
crate::books::BooksPage,
|
crate::books::BooksPage,
|
||||||
crate::books::BookDetails,
|
crate::books::BookDetails,
|
||||||
crate::books::SeriesItem,
|
crate::books::SeriesItem,
|
||||||
|
crate::books::SeriesPage,
|
||||||
crate::pages::PageQuery,
|
crate::pages::PageQuery,
|
||||||
crate::search::SearchQuery,
|
crate::search::SearchQuery,
|
||||||
crate::search::SearchResponse,
|
crate::search::SearchResponse,
|
||||||
@@ -118,15 +119,24 @@ mod tests {
|
|||||||
.to_pretty_json()
|
.to_pretty_json()
|
||||||
.expect("Failed to serialize OpenAPI");
|
.expect("Failed to serialize OpenAPI");
|
||||||
|
|
||||||
// Check that there are no references to non-existent schemas
|
// Check that all $ref targets exist in components/schemas
|
||||||
assert!(
|
let doc: serde_json::Value =
|
||||||
!json.contains("\"/components/schemas/Uuid\""),
|
serde_json::from_str(&json).expect("OpenAPI JSON should be valid");
|
||||||
"Uuid schema should not be referenced"
|
let empty = serde_json::Map::new();
|
||||||
);
|
let schemas = doc["components"]["schemas"]
|
||||||
assert!(
|
.as_object()
|
||||||
!json.contains("\"/components/schemas/DateTime\""),
|
.unwrap_or(&empty);
|
||||||
"DateTime schema should not be referenced"
|
let prefix = "#/components/schemas/";
|
||||||
);
|
let mut broken: Vec<String> = 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
|
// Save to file for inspection
|
||||||
std::fs::write("/tmp/openapi.json", &json).expect("Failed to write file");
|
std::fs::write("/tmp/openapi.json", &json).expect("Failed to write file");
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use serde::Deserialize;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::{error::ApiError, index_jobs, state::AppState};
|
use crate::{error::ApiError, index_jobs::{self, IndexJobResponse}, state::AppState};
|
||||||
|
|
||||||
#[derive(Deserialize, ToSchema)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct ThumbnailsRebuildRequest {
|
pub struct ThumbnailsRebuildRequest {
|
||||||
@@ -21,7 +21,7 @@ pub struct ThumbnailsRebuildRequest {
|
|||||||
tag = "indexing",
|
tag = "indexing",
|
||||||
request_body = Option<ThumbnailsRebuildRequest>,
|
request_body = Option<ThumbnailsRebuildRequest>,
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = index_jobs::IndexJobResponse),
|
(status = 200, body = IndexJobResponse),
|
||||||
(status = 401, description = "Unauthorized"),
|
(status = 401, description = "Unauthorized"),
|
||||||
(status = 403, description = "Forbidden - Admin scope required"),
|
(status = 403, description = "Forbidden - Admin scope required"),
|
||||||
),
|
),
|
||||||
@@ -55,7 +55,7 @@ pub async fn start_thumbnails_rebuild(
|
|||||||
tag = "indexing",
|
tag = "indexing",
|
||||||
request_body = Option<ThumbnailsRebuildRequest>,
|
request_body = Option<ThumbnailsRebuildRequest>,
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = index_jobs::IndexJobResponse),
|
(status = 200, body = IndexJobResponse),
|
||||||
(status = 401, description = "Unauthorized"),
|
(status = 401, description = "Unauthorized"),
|
||||||
(status = 403, description = "Forbidden - Admin scope required"),
|
(status = 403, description = "Forbidden - Admin scope required"),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user