Skip to main content

vn_core/http/request/
mod.rs

1pub mod get;
2pub mod post;
3
4use super::{Endpoint, UrlQueryParams};
5use crate::error::{Error, Result};
6use crate::vndb::Token;
7use http::Method;
8use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
9use reqwest::{Client, Response as RawResponse};
10use serde::Serialize;
11use serde::de::DeserializeOwned;
12use std::sync::{LazyLock, Weak};
13use tokio::sync::{OwnedSemaphorePermit, Semaphore};
14use tokio::task::spawn;
15use tokio::time::{Duration, sleep};
16
17pub const API_BASE_URL: &str = "https://api.vndb.org/kana";
18const DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
19
20static HTTP: LazyLock<Client> = LazyLock::new(|| {
21  Client::builder()
22    .use_rustls_tls()
23    .https_only(true)
24    .build()
25    .expect("failed to create http client")
26});
27
28#[bon::builder]
29pub(super) async fn request<Body>(
30  #[builder(start_fn)] endpoint: Endpoint,
31  method: Method,
32  semaphore: Weak<Semaphore>,
33  query: Option<UrlQueryParams>,
34  body: Option<&Body>,
35  token: Option<&Token>,
36  delay: Option<Duration>,
37  timeout: Option<Duration>,
38  user_agent: Option<&str>,
39) -> Result<RawResponse>
40where
41  Body: Serialize + ?Sized,
42{
43  let permit = semaphore
44    .upgrade()
45    .unwrap()
46    .acquire_owned()
47    .await
48    .map_err(|_| Error::Disconnected)?;
49
50  let mut url = endpoint.url();
51  if let Some(query) = query {
52    url.query_pairs_mut().extend_pairs(query.0);
53  }
54
55  let mut request = HTTP.request(method, url);
56
57  if let Some(body) = body {
58    request = request.header(CONTENT_TYPE, "application/json");
59    request = request.json(body);
60  }
61
62  if let Some(token) = token {
63    request = request.header(AUTHORIZATION, token.to_header());
64  }
65
66  if let Some(timeout) = timeout {
67    request = request.timeout(timeout);
68  }
69
70  if let Some(user_agent) = user_agent {
71    request = request.header(USER_AGENT, user_agent);
72  } else {
73    request = request.header(USER_AGENT, DEFAULT_USER_AGENT);
74  }
75
76  let response = request.send().await?.error_for_status()?;
77
78  if let Some(delay) = delay {
79    delay_drop(permit, delay);
80  } else {
81    drop(permit);
82  }
83
84  Ok(response)
85}
86
87#[bon::builder]
88pub(super) async fn request_json<Body, Json>(
89  #[builder(start_fn)] endpoint: Endpoint,
90  semaphore: Weak<Semaphore>,
91  method: Method,
92  query: Option<UrlQueryParams>,
93  body: Option<&Body>,
94  token: Option<&Token>,
95  delay: Option<Duration>,
96  timeout: Option<Duration>,
97  user_agent: Option<&str>,
98) -> Result<Json>
99where
100  Body: Serialize + ?Sized,
101  Json: DeserializeOwned,
102{
103  request(endpoint)
104    .method(method)
105    .semaphore(semaphore)
106    .maybe_query(query)
107    .maybe_body(body)
108    .maybe_token(token)
109    .maybe_delay(delay)
110    .maybe_timeout(timeout)
111    .maybe_user_agent(user_agent)
112    .call()
113    .await?
114    .json()
115    .await
116    .map_err(Into::into)
117}
118
119fn delay_drop(permit: OwnedSemaphorePermit, duration: Duration) {
120  spawn(async move {
121    sleep(duration).await;
122    drop(permit);
123  });
124}