Skip to main content

vn_core/
vndb.rs

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/// See: <https://api.vndb.org/kana#user-authentication>
226#[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}