115 lines
3.2 KiB
Rust
115 lines
3.2 KiB
Rust
use std::sync::Arc;
|
|
|
|
use axum::{
|
|
extract::{Query, State},
|
|
http::{StatusCode, header},
|
|
response::{IntoResponse, Response},
|
|
};
|
|
use serde::Deserialize;
|
|
use snafu::{ResultExt, Snafu};
|
|
|
|
use super::books::{RenderError, render_books};
|
|
use crate::{
|
|
APP_NAME,
|
|
api::{
|
|
OPDS_TAG,
|
|
error::{ErrorResponse, HttpStatus},
|
|
search::{self, SearchQueryError},
|
|
},
|
|
app_state::AppState,
|
|
http_error,
|
|
opds::{
|
|
error::AsXmlError,
|
|
search::{OpenSearchDescription, Url},
|
|
},
|
|
};
|
|
|
|
#[derive(Debug, Snafu)]
|
|
/// Errors that can occur when searching for books.
|
|
pub enum SearchError {
|
|
/// A failure to query for books.
|
|
#[snafu(display("Failed to query books."))]
|
|
Query { source: SearchQueryError },
|
|
/// A failure to render the feed.
|
|
#[snafu(display("Failed to render feed."))]
|
|
Render { source: RenderError },
|
|
}
|
|
impl HttpStatus for SearchError {
|
|
fn status_code(&self) -> StatusCode {
|
|
match self {
|
|
SearchError::Query { source: _ } => StatusCode::INTERNAL_SERVER_ERROR,
|
|
SearchError::Render { source: _ } => StatusCode::INTERNAL_SERVER_ERROR,
|
|
}
|
|
}
|
|
}
|
|
http_error!(SearchError);
|
|
|
|
#[derive(Deserialize)]
|
|
/// Parameters for a search request.
|
|
pub struct Params {
|
|
/// Query for a search request.
|
|
query: String,
|
|
}
|
|
/// Search for books and return results as an OPDS feed.
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/search",
|
|
tag = OPDS_TAG,
|
|
responses(
|
|
(status = 200, content_type = "application/atom+xml"),
|
|
(status = 500, description = "Error retrieving books from database", body = ErrorResponse)
|
|
)
|
|
)]
|
|
pub async fn handler(
|
|
Query(params): Query<Params>,
|
|
State(state): State<Arc<AppState>>,
|
|
) -> Result<Response, SearchError> {
|
|
let books = search::query(¶ms.query, &state.calibre, &state.config.library_path)
|
|
.await
|
|
.context(QuerySnafu)?;
|
|
|
|
render_books(books).await.context(RenderSnafu)
|
|
}
|
|
|
|
#[derive(Debug, Snafu)]
|
|
/// Errors that can occur when retrieving search information.
|
|
pub enum InfoError {
|
|
/// A failure to render the feed.
|
|
#[snafu(display("Failed to render feed."))]
|
|
FeedRender { source: AsXmlError },
|
|
}
|
|
impl HttpStatus for InfoError {
|
|
fn status_code(&self) -> StatusCode {
|
|
match self {
|
|
InfoError::FeedRender { source: _ } => StatusCode::INTERNAL_SERVER_ERROR,
|
|
}
|
|
}
|
|
}
|
|
|
|
http_error!(InfoError);
|
|
|
|
/// Render search information as an OPDS feed.
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/search/info",
|
|
tag = OPDS_TAG,
|
|
responses(
|
|
(status = 200, content_type = "application/atom+xml"),
|
|
(status = 500, description = "Internal error", body = ErrorResponse)
|
|
)
|
|
)]
|
|
pub async fn info() -> Result<Response, InfoError> {
|
|
let search = OpenSearchDescription {
|
|
short_name: APP_NAME.to_string(),
|
|
description: "Search for ebooks".to_string(),
|
|
input_encoding: "UTF-8".to_string(),
|
|
output_encoding: "UTF-8".to_string(),
|
|
url: Url {
|
|
type_name: "application/atom+xml".to_string(),
|
|
template: "/opds/search?query={searchTerms}".to_string(),
|
|
},
|
|
};
|
|
let xml = search.as_xml().context(FeedRenderSnafu)?;
|
|
|
|
Ok(([(header::CONTENT_TYPE, "application/atom+xml")], xml).into_response())
|
|
}
|