Skip to main content

open_library_api_rs/api/
search.rs

1// v0.0.1
2use crate::client::OpenLibraryClient;
3use crate::error::{Error, Result};
4use crate::models::search::{
5    AuthorDoc, AuthorSearchParams, BookDoc, InsideDoc, ListDoc, SearchParams, SearchResponse,
6    SubjectDoc,
7};
8use crate::validation::{validate_limit, validate_search_query};
9
10impl OpenLibraryClient {
11    /// Search for books and works across the Open Library catalogue.
12    ///
13    /// At least one of `q`, `title`, `author`, `isbn`, or `subject` must be set.
14    /// All other [`SearchParams`] fields are optional filters or pagination controls.
15    ///
16    /// Returns a [`SearchResponse<BookDoc>`] where `num_found` is the total match
17    /// count and `docs` is the current page.
18    pub async fn search(&self, params: SearchParams) -> Result<SearchResponse<BookDoc>> {
19        // At least one of q, title, author, isbn, or subject must be provided.
20        let q = params.q.as_deref().unwrap_or_default();
21        let title = params.title.as_deref().unwrap_or_default();
22        let author = params.author.as_deref().unwrap_or_default();
23        let isbn = params.isbn.as_deref().unwrap_or_default();
24        let subject = params.subject.as_deref().unwrap_or_default();
25
26        if q.is_empty() && title.is_empty() && author.is_empty() && isbn.is_empty() && subject.is_empty() {
27            return Err(Error::InvalidInput(
28                "at least one of q, title, author, isbn, or subject must be provided".into(),
29            ));
30        }
31        if !q.is_empty() {
32            validate_search_query(q)?;
33        }
34        if let Some(ref limit) = params.limit {
35            validate_limit(*limit)?;
36        }
37
38        let mut url = self.base_url.join("search.json")?;
39        {
40            let mut qp = url.query_pairs_mut();
41            if !q.is_empty() { qp.append_pair("q", q); }
42            if !title.is_empty() { qp.append_pair("title", title); }
43            if !author.is_empty() { qp.append_pair("author", author); }
44            if !isbn.is_empty() { qp.append_pair("isbn", isbn); }
45            if !subject.is_empty() { qp.append_pair("subject", subject); }
46            if let Some(v) = params.place.as_deref() { qp.append_pair("place", v); }
47            if let Some(v) = params.person.as_deref() { qp.append_pair("person", v); }
48            if let Some(v) = params.language.as_deref() { qp.append_pair("language", v); }
49            if let Some(v) = params.publisher.as_deref() { qp.append_pair("publisher", v); }
50            if let Some(v) = params.page { qp.append_pair("page", &v.to_string()); }
51            if let Some(v) = params.limit { qp.append_pair("limit", &v.to_string()); }
52            if let Some(v) = params.offset { qp.append_pair("offset", &v.to_string()); }
53            if let Some(v) = params.sort.as_deref() { qp.append_pair("sort", v); }
54            if let Some(v) = params.fields.as_deref() { qp.append_pair("fields", v); }
55            if let Some(v) = params.lang.as_deref() { qp.append_pair("lang", v); }
56        }
57        self.get_json(url).await
58    }
59
60    /// Search the Open Library author index.
61    ///
62    /// `params.q` is required and must be non-empty.
63    pub async fn search_authors(
64        &self,
65        params: AuthorSearchParams,
66    ) -> Result<SearchResponse<AuthorDoc>> {
67        let q = params.q.as_deref().unwrap_or_default();
68        if q.is_empty() {
69            return Err(Error::InvalidInput("author search query must not be empty".into()));
70        }
71        validate_search_query(q)?;
72        if let Some(ref limit) = params.limit {
73            validate_limit(*limit)?;
74        }
75
76        let mut url = self.base_url.join("search/authors.json")?;
77        {
78            let mut qp = url.query_pairs_mut();
79            qp.append_pair("q", q);
80            if let Some(v) = params.limit { qp.append_pair("limit", &v.to_string()); }
81            if let Some(v) = params.offset { qp.append_pair("offset", &v.to_string()); }
82        }
83        self.get_json(url).await
84    }
85
86    /// Search the Open Library subject index.
87    ///
88    /// `query` is a free-text search term (non-empty, ≤ 1000 chars).
89    pub async fn search_subjects(&self, query: &str) -> Result<SearchResponse<SubjectDoc>> {
90        validate_search_query(query)?;
91        let mut url = self.base_url.join("search/subjects.json")?;
92        url.query_pairs_mut().append_pair("q", query);
93        self.get_json(url).await
94    }
95
96    /// Search public user-created reading lists.
97    ///
98    /// `query` is a free-text search term. `limit` is clamped to 1–1000.
99    pub async fn search_lists(&self, query: &str, limit: Option<u32>) -> Result<SearchResponse<ListDoc>> {
100        validate_search_query(query)?;
101        if let Some(l) = limit { validate_limit(l)?; }
102        let mut url = self.base_url.join("search/lists.json")?;
103        {
104            let mut qp = url.query_pairs_mut();
105            qp.append_pair("q", query);
106            if let Some(l) = limit { qp.append_pair("limit", &l.to_string()); }
107        }
108        self.get_json(url).await
109    }
110
111    /// Full-text search inside the text of scanned books (Internet Archive).
112    ///
113    /// `query` is a phrase or keyword. `limit` is clamped to 1–1000.
114    pub async fn search_inside(
115        &self,
116        query: &str,
117        limit: Option<u32>,
118    ) -> Result<SearchResponse<InsideDoc>> {
119        validate_search_query(query)?;
120        if let Some(l) = limit { validate_limit(l)?; }
121        let mut url = self.base_url.join("search/inside.json")?;
122        {
123            let mut qp = url.query_pairs_mut();
124            qp.append_pair("q", query);
125            if let Some(l) = limit { qp.append_pair("limit", &l.to_string()); }
126        }
127        self.get_json(url).await
128    }
129}