1use crate::error::{Error, Result};
2use crate::http::request::get::Get;
3use crate::http::request::post::prelude::*;
4use crate::model::character::CharacterId;
5use crate::model::producer::ProducerId;
6use crate::model::release::ReleaseId;
7use crate::model::staff::StaffId;
8use crate::model::tag::TagId;
9use crate::model::r#trait::TraitId;
10use crate::model::user::{User, UserField, UserId};
11use crate::model::visual_novel::VisualNovelId;
12use std::num::NonZeroU8;
13use std::ops::Deref;
14use std::sync::{Arc, Weak};
15use std::time::Duration;
16use tokio::sync::Semaphore;
17
18const CONCURRENCY: NonZeroU8 = NonZeroU8::new(10).unwrap();
19
20#[derive(Debug)]
21pub struct Vndb {
22 pub(crate) semaphore: Arc<Semaphore>,
23 pub(crate) token: Option<Token>,
24 pub(crate) delay: Option<Duration>,
25 pub(crate) timeout: Option<Duration>,
26 pub(crate) user_agent: Option<String>,
27}
28
29impl Vndb {
30 pub fn new() -> Arc<Self> {
31 let concurrent_requests = usize::from(CONCURRENCY.get());
32 let semaphore = Semaphore::new(concurrent_requests);
33 Arc::new(Self {
34 semaphore: Arc::new(semaphore),
35 token: None,
36 delay: None,
37 timeout: None,
38 user_agent: None,
39 })
40 }
41
42 pub fn builder() -> VndbBuilder {
43 VndbBuilder::new()
44 }
45
46 pub fn with_token(token: impl Into<Token>) -> Arc<Self> {
47 Self::builder().token(token).build()
48 }
49
50 pub fn get(self: &Arc<Self>) -> Get {
51 Get::new(Arc::downgrade(self))
52 }
53
54 pub fn post(self: &Arc<Self>) -> Post {
55 Post::new(Arc::downgrade(self))
56 }
57
58 pub(crate) fn upgrade(weak: &Weak<Self>) -> Result<Arc<Self>> {
59 weak.upgrade().ok_or(Error::Disconnected)
60 }
61}
62
63macro_rules! find {
64 ($vndb:expr, $id:expr, $post_fn:ident, $field:ident) => {{
65 let filters = serde_json::json!(["id", "=", $id]);
66 $vndb
67 .post()
68 .$post_fn()
69 .filters(filters.into())
70 }};
71}
72
73macro_rules! search {
74 ($vndb:expr, $query:expr, $post_fn:ident, $field:ident) => {{
75 let query = $query.as_ref();
76 let filters = serde_json::json!(["search", "=", query]);
77 $vndb
78 .post()
79 .$post_fn()
80 .filters(filters.into())
81 }};
82}
83
84impl Vndb {
85 pub fn find_character(self: &Arc<Self>, id: &CharacterId) -> CharacterQuery {
86 find!(self, id, character, CharacterField)
87 }
88
89 pub fn find_producer(self: &Arc<Self>, id: &ProducerId) -> ProducerQuery {
90 find!(self, id, producer, ProducerField)
91 }
92
93 pub fn find_release(self: &Arc<Self>, id: &ReleaseId) -> ReleaseQuery {
94 find!(self, id, release, ReleaseField)
95 }
96
97 pub fn find_staff(self: &Arc<Self>, id: &StaffId) -> StaffQuery {
98 find!(self, id, staff, StaffField)
99 }
100
101 pub fn find_tag(self: &Arc<Self>, id: &TagId) -> TagQuery {
102 find!(self, id, tag, TagField)
103 }
104
105 pub fn find_trait(self: &Arc<Self>, id: &TraitId) -> TraitQuery {
106 find!(self, id, r#trait, TraitField)
107 }
108
109 pub async fn find_user(self: &Arc<Self>, id: &UserId) -> Result<Option<User>> {
110 let user = self
111 .get()
112 .user(id, UserField::all())
113 .await?
114 .into_inner()
115 .into_values()
116 .next();
117
118 Ok(user)
119 }
120
121 pub fn find_visual_novel(self: &Arc<Self>, id: &VisualNovelId) -> VisualNovelQuery {
122 find!(self, id, visual_novel, VisualNovelField)
123 }
124
125 pub fn search_character(self: &Arc<Self>, query: impl AsRef<str>) -> CharacterQuery {
126 search!(self, query, character, CharacterField)
127 }
128
129 pub fn search_producer(self: &Arc<Self>, query: impl AsRef<str>) -> ProducerQuery {
130 search!(self, query, producer, ProducerField)
131 }
132
133 pub fn search_release(self: &Arc<Self>, query: impl AsRef<str>) -> ReleaseQuery {
134 search!(self, query, release, ReleaseField)
135 }
136
137 pub fn search_staff(self: &Arc<Self>, query: impl AsRef<str>) -> StaffQuery {
138 search!(self, query, staff, StaffField)
139 }
140
141 pub fn search_tag(self: &Arc<Self>, query: impl AsRef<str>) -> TagQuery {
142 search!(self, query, tag, TagField)
143 }
144
145 pub fn search_trait(self: &Arc<Self>, query: impl AsRef<str>) -> TraitQuery {
146 search!(self, query, r#trait, TraitField)
147 }
148
149 pub fn search_visual_novel(self: &Arc<Self>, query: impl AsRef<str>) -> VisualNovelQuery {
150 search!(self, query, visual_novel, VisualNovelField)
151 }
152}
153
154#[derive(Debug)]
155pub struct VndbBuilder {
156 max_concurrent_requests: NonZeroU8,
157 token: Option<Token>,
158 delay: Option<Duration>,
159 timeout: Option<Duration>,
160 user_agent: Option<String>,
161}
162
163impl VndbBuilder {
164 pub fn new() -> Self {
165 Self::default()
166 }
167
168 #[must_use]
169 pub fn max_concurrent_requests(mut self, amount: u8) -> Self {
170 self.max_concurrent_requests = NonZeroU8::new(amount).unwrap_or(CONCURRENCY);
171 self
172 }
173
174 #[must_use]
175 pub fn token(mut self, token: impl Into<Token>) -> Self {
176 self.token = Some(token.into());
177 self
178 }
179
180 #[must_use]
181 pub fn delay(mut self, delay: Duration) -> Self {
182 self.delay = Some(delay);
183 self
184 }
185
186 #[must_use]
187 pub fn timeout(mut self, timeout: Duration) -> Self {
188 self.timeout = Some(timeout);
189 self
190 }
191
192 #[must_use]
193 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
194 self.user_agent = Some(user_agent.into());
195 self
196 }
197
198 pub fn build(self) -> Arc<Vndb> {
199 let max_concurrent_requests = self.max_concurrent_requests.get();
200 let semaphore = Semaphore::new(usize::from(max_concurrent_requests));
201 let vndb = Vndb {
202 semaphore: Arc::new(semaphore),
203 token: self.token,
204 delay: self.delay,
205 timeout: self.timeout,
206 user_agent: self.user_agent,
207 };
208
209 Arc::new(vndb)
210 }
211}
212
213impl Default for VndbBuilder {
214 fn default() -> Self {
215 Self {
216 max_concurrent_requests: CONCURRENCY,
217 token: None,
218 delay: None,
219 timeout: None,
220 user_agent: None,
221 }
222 }
223}
224
225#[derive(Clone, Debug)]
227pub struct Token(Box<str>);
228
229impl Token {
230 pub(crate) fn to_header(&self) -> String {
231 format!("Token {}", self.0)
232 }
233}
234
235impl<T: AsRef<str>> From<T> for Token {
236 fn from(token: T) -> Self {
237 Self(Box::from(token.as_ref()))
238 }
239}
240
241impl Deref for Token {
242 type Target = str;
243
244 fn deref(&self) -> &Self::Target {
245 &self.0
246 }
247}