1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Wrapper for [Openlibrary's Web API](https://openlibrary.org/developers/api)
//!
//! # Overview
//!
//! **PLEASE NOTE**: this crate is currently in an experimental stage.
//! Meaning expect frequent, large breaking changes from version to version until we are in a stable state.
//!
//! There are currently 8 sub APIs offered by Openlibrary's Web API.
//! You can check the table below to see the status of each.
//!
//! - [X] [Books](https://openlibrary.org/dev/docs/api/books) [^books_completeness]
//! - [ ] [Authors](https://openlibrary.org/dev/docs/api/authors)
//! - [ ] [Subjects](https://openlibrary.org/dev/docs/api/subjects)
//! - [X] [Search](https://openlibrary.org/dev/docs/api/search)
//! - [ ] [Search inside](https://openlibrary.org/dev/docs/api/search_inside)
//! - [ ] [Partner](https://openlibrary.org/dev/docs/api/read)
//! - [ ] [Covers](https://openlibrary.org/dev/docs/api/covers)
//! - [ ] [Recent Changes](https://openlibrary.org/dev/docs/api/recentchanges)
//!
//! # Books
//!
//! You can view information about books by Works, Editions, and ISBN ids by using the [`books::Books`] struct
//!
//! ``` rust
//! use openlibrary_rs::books::{BookType, BooksBuilder};
//!
//! // execute request to Works API and pretty print debug of result
//! let books = BooksBuilder::default()
//!     .book_type(BookType::Works)
//!     .id("OL45883W")
//!     .build()
//!     .unwrap();
//!
//!     println!("{:#?}", books.execute());
//! ```
//!
//! # Search
//!
//! You can search for books, authors, and more using the [`search::Search`] struct
//!
//! ``` rust
//! use openlibrary_rs::search::SearchBuilder;
//!
//! // execute search and pretty print debug of first result
//! let search = SearchBuilder::default()
//!     .query("the lord of the rings")
//!     .build()
//!     .unwrap();
//!
//! println!("{:#?}", search.execute().docs[0]);
//! ```
//!
//! [^books_completeness]: Everything excluding the generic Books API is complete(i.e. Works, Editions, and ISBN APIs are done).
//!

#[allow(dead_code)]
const OPENLIBRARY_URL: &str = "https://openlibrary.org";

pub mod books;
pub mod search;

use reqwest::blocking::Response as ReqwestResponse;
use reqwest::Result as ReqwestResult;

use crate::books::Books;
use crate::search::Search;

struct OpenlibraryRequest {
    url: String,
}

impl OpenlibraryRequest {
    fn root_url() -> String {
        #[cfg(not(test))]
        return OPENLIBRARY_URL.to_string();
        #[cfg(test)]
        return mockito::server_url().to_string();
    }

    pub fn search_request(search: &Search) -> OpenlibraryRequest {
        let mut url = Self::root_url();

        url.push_str("/search");
        url.push_str(search.search_type.to_string().as_str());

        url.push_str(format!(".json?page={}&limit={}", search.page, search.limit,).as_str());

        if let Some(query) = search.query.as_deref() {
            url.push_str(format!("&q={}", query).as_str())
        }

        if !search.fields.is_empty() {
            url.push_str(format!("&fields={}", search.fields.join(",")).as_str())
        }

        OpenlibraryRequest { url }
    }

    pub fn books_request(books: &Books) -> OpenlibraryRequest {
        let mut url = Self::root_url();

        url.push_str(format!("{}/{}.json", books.book_type, books.id).as_str());

        OpenlibraryRequest { url }
    }

    pub fn execute(&self) -> ReqwestResult<ReqwestResponse> {
        reqwest::blocking::get(self.url.as_str())
    }
}

#[cfg(test)]
mod tests {
    use crate::books::BooksBuilder;
    use crate::search::SearchBuilder;
    use crate::OpenlibraryRequest;

    #[test]
    pub fn test_search_request() {
        let search = SearchBuilder::default().build().unwrap();
        let search_request = OpenlibraryRequest::search_request(&search);

        assert_eq!(
            search_request.url,
            format!("{}/search.json?page=1&limit=10", mockito::server_url())
        )
    }

    #[test]
    pub fn test_books_request() {
        let books = BooksBuilder::default().id("1234").build().unwrap();
        let books_request = OpenlibraryRequest::books_request(&books);

        assert_eq!(
            books_request.url,
            format!("{}/works/1234.json", mockito::server_url())
        )
    }
}