rosu_render/client/
mod.rs1mod builder;
2mod connector;
3mod ratelimiter;
4
5pub mod error;
6
7use std::sync::Arc;
8
9use bytes::Bytes;
10use http_body_util::Full;
11use hyper::{
12 header::{CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT},
13 http::HeaderValue,
14 Method, Request as HyperRequest,
15};
16use hyper_util::client::legacy::{Client as HyperClient, ResponseFuture};
17
18pub use self::builder::OrdrClientBuilder;
19pub(crate) use self::ratelimiter::RatelimiterKind;
20use self::{connector::Connector, error::ClientError, ratelimiter::Ratelimiter};
21
22use crate::{
23 model::{RenderSkinOption, Verification},
24 request::{
25 CommissionRender, GetRenderList, GetServerList, GetServerOnlineCount, GetSkinCustom,
26 GetSkinList, GetUserPreset, OrdrFuture, Request,
27 },
28 util::multipart::Form,
29};
30
31const BASE_URL: &str = "https://apis.issou.best/ordr/";
32const ROSU_RENDER_USER_AGENT: &str = concat!("rosu-render (", env!("CARGO_PKG_VERSION"), ")");
33
34#[derive(Clone)]
38pub struct OrdrClient {
39 inner: Arc<OrdrRef>,
40}
41
42struct OrdrRef {
43 pub(super) http: HyperClient<Connector, Full<Bytes>>,
44 pub(super) ratelimiter: Ratelimiter,
45 pub(super) verification: Option<Verification>,
46}
47
48impl OrdrClient {
49 #[must_use]
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn builder() -> OrdrClientBuilder {
57 OrdrClientBuilder::new()
58 }
59
60 pub const fn custom_skin_info(&self, id: u32) -> GetSkinCustom<'_> {
64 GetSkinCustom::new(self, id)
65 }
66
67 pub const fn render_with_replay_file<'a>(
69 &'a self,
70 replay_file: &'a [u8],
71 username: &'a str,
72 skin: &'a RenderSkinOption<'a>,
73 ) -> CommissionRender<'a> {
74 CommissionRender::with_file(self, replay_file, username, skin)
75 }
76
77 pub const fn render_with_replay_url<'a>(
79 &'a self,
80 url: &'a str,
81 username: &'a str,
82 skin: &'a RenderSkinOption<'a>,
83 ) -> CommissionRender<'a> {
84 CommissionRender::with_url(self, url, username, skin)
85 }
86
87 pub const fn render_list(&self) -> GetRenderList<'_> {
89 GetRenderList::new(self)
90 }
91
92 pub const fn server_list(&self) -> GetServerList<'_> {
94 GetServerList::new(self)
95 }
96
97 pub const fn server_online_count(&self) -> GetServerOnlineCount<'_> {
99 GetServerOnlineCount::new(self)
100 }
101
102 pub const fn skin_list(&self) -> GetSkinList<'_> {
104 GetSkinList::new(self)
105 }
106
107 pub const fn user_preset<'a>(&'a self, key: &'a str, discord_id: u64) -> GetUserPreset<'a> {
112 GetUserPreset::new(self, key, discord_id)
113 }
114
115 pub(crate) fn verification(&self) -> Option<&Verification> {
116 self.inner.verification.as_ref()
117 }
118
119 pub(crate) fn request<T>(&self, req: Request) -> OrdrFuture<T> {
120 self.try_request::<T>(req).unwrap_or_else(OrdrFuture::error)
121 }
122
123 fn try_request<T>(&self, req: Request) -> Result<OrdrFuture<T>, ClientError> {
124 let Request {
125 form,
126 method,
127 path,
128 ratelimiter,
129 } = req;
130
131 let inner = self.try_request_raw(form, method, &path)?;
132
133 Ok(OrdrFuture::new(
134 Box::pin(inner),
135 self.inner.ratelimiter.get(ratelimiter).acquire_owned(1),
136 ))
137 }
138
139 fn try_request_raw(
140 &self,
141 form: Option<Form>,
142 method: Method,
143 path: &str,
144 ) -> Result<ResponseFuture, ClientError> {
145 let mut url = String::with_capacity(BASE_URL.len() + path.len());
146 url.push_str(BASE_URL);
147 url.push_str(path);
148 debug!(?url);
149
150 debug_assert!(method != Method::POST || form.is_some());
151
152 let mut builder = HyperRequest::builder().method(method).uri(&url);
153
154 if let Some(headers) = builder.headers_mut() {
155 if let Some(ref form) = form {
156 headers.insert(CONTENT_LENGTH, HeaderValue::from(form.len()));
157
158 if let Ok(content_type) = HeaderValue::try_from(form.content_type()) {
159 headers.insert(CONTENT_TYPE, content_type);
160 }
161 }
162
163 headers.insert(USER_AGENT, HeaderValue::from_static(ROSU_RENDER_USER_AGENT));
164 }
165
166 let try_req = if let Some(form) = form {
167 builder.body(Full::from(form.build()))
168 } else {
169 builder.body(Full::default())
170 };
171
172 let req = try_req.map_err(|source| ClientError::BuildingRequest {
173 source: Box::new(source),
174 })?;
175
176 Ok(self.inner.http.request(req))
177 }
178}
179
180impl Default for OrdrClient {
181 fn default() -> Self {
182 Self::builder().build()
183 }
184}