vn_core/http/request/
mod.rs1pub 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}