1use crate::clients::account::AccountClient;
2use crate::clients::author::AuthorClient;
3use crate::clients::works::WorksClient;
4use crate::models::account::Session;
5use clients::books::BooksClient;
6use reqwest::header::{HeaderMap, HeaderValue};
7use reqwest::{ClientBuilder, Error, StatusCode};
8use serde::{Deserialize, Serialize};
9use std::fmt::Debug;
10use thiserror::Error;
11use url::{ParseError, Url};
12
13mod clients;
14mod format;
15pub mod models;
16#[cfg(test)]
17mod tests;
18
19#[derive(Debug, Error)]
20pub enum OpenLibraryError {
21 #[error(
22 "Received an {:?} response from the Open Library API: {:?}",
23 status_code,
24 error
25 )]
26 ApiError {
27 status_code: StatusCode,
28 error: Option<OpenLibraryErrorResponse>,
29 },
30 #[error("Unable to build HTTP client: {}", source)]
31 ClientBuildingError { source: reqwest::Error },
32 #[error("An internal error occurred: {}", reason)]
33 InternalError { reason: String },
34 #[error("An error occurred while parsing json: {}", source)]
35 JsonParseError { source: reqwest::Error },
36 #[error("The operation ({}) requires authentication to be provided!", reason)]
37 NotAuthenticated { reason: String },
38 #[error("An error occurred while trying to parse a value: {}", reason)]
39 ParsingError { reason: String },
40 #[error("An error occurred while sending HTTP request: {}", source)]
41 RequestFailed { source: reqwest::Error },
42}
43
44impl From<reqwest::Error> for OpenLibraryError {
45 fn from(error: Error) -> Self {
46 OpenLibraryError::RequestFailed { source: error }
47 }
48}
49
50impl From<ParseError> for OpenLibraryError {
51 fn from(error: ParseError) -> Self {
52 OpenLibraryError::ParsingError {
53 reason: error.to_string(),
54 }
55 }
56}
57
58#[derive(Debug, Deserialize, Serialize)]
59pub struct OpenLibraryErrorResponse {
60 pub error: String,
61}
62
63pub struct OpenLibraryAuthClient {
64 account: AccountClient,
65}
66
67impl OpenLibraryAuthClient {
68 pub fn new(host: Option<Url>) -> Result<OpenLibraryAuthClient, OpenLibraryError> {
70 let client = ClientBuilder::new()
71 .build()
72 .map_err(|error| OpenLibraryError::ClientBuildingError { source: error })?;
73
74 let host_url = match host {
75 Some(value) => value,
76 None => Url::parse("https://openlibrary.org/").unwrap(),
77 };
78
79 Ok(Self {
80 account: AccountClient {
81 client,
82 host: host_url,
83 },
84 })
85 }
86
87 pub async fn login(
88 &self,
89 username: String,
90 password: String,
91 ) -> Result<Session, OpenLibraryError> {
92 self.account.login(username, password).await
93 }
94}
95
96#[derive(Clone)]
97pub struct OpenLibraryClient {
98 pub account: AccountClient,
99 pub author: AuthorClient,
100 pub books: BooksClient,
101 pub works: WorksClient,
102}
103
104impl OpenLibraryClient {
105 pub fn builder() -> OpenLibraryClientBuilder {
106 OpenLibraryClientBuilder::new()
107 }
108}
109
110pub struct OpenLibraryClientBuilder {
111 host: Url,
112 session: Option<Session>,
113}
114
115impl OpenLibraryClientBuilder {
116 fn new() -> OpenLibraryClientBuilder {
117 OpenLibraryClientBuilder {
118 host: Url::parse("https://openlibrary.org/").unwrap(),
119 session: None,
120 }
121 }
122
123 pub fn with_host(self, host: Url) -> OpenLibraryClientBuilder {
124 OpenLibraryClientBuilder {
125 host,
126 session: self.session,
127 }
128 }
129
130 pub fn with_session(self, session: &Session) -> OpenLibraryClientBuilder {
131 OpenLibraryClientBuilder {
132 host: self.host,
133 session: Some(session.clone()),
134 }
135 }
136
137 pub fn build(self) -> Result<OpenLibraryClient, OpenLibraryError> {
138 let default_headers = match self.session {
139 Some(session) => {
140 let mut headers = HeaderMap::new();
141 headers.insert(
142 "Cookie",
143 HeaderValue::from_str(session.cookie().as_str()).map_err(|_error| {
144 OpenLibraryError::ParsingError {
145 reason: "Unable to parse session cookie into header value".to_string(),
146 }
147 })?,
148 );
149 headers
150 }
151 None => HeaderMap::new(),
152 };
153
154 let client = ClientBuilder::new()
155 .default_headers(default_headers)
156 .build()
157 .map_err(|error| OpenLibraryError::ClientBuildingError { source: error })?;
158
159 Ok(OpenLibraryClient {
160 books: BooksClient::new(&client, &self.host),
161 account: AccountClient::new(&client, &self.host),
162 author: AuthorClient::new(&client, &self.host),
163 works: WorksClient::new(&client, &self.host),
164 })
165 }
166}