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, 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(crate) fn verification(&self) -> Option<&Verification> {
108 self.inner.verification.as_ref()
109 }
110
111 pub(crate) fn request<T>(&self, req: Request) -> OrdrFuture<T> {
112 self.try_request::<T>(req).unwrap_or_else(OrdrFuture::error)
113 }
114
115 fn try_request<T>(&self, req: Request) -> Result<OrdrFuture<T>, ClientError> {
116 let Request {
117 form,
118 method,
119 path,
120 ratelimiter,
121 } = req;
122
123 let inner = self.try_request_raw(form, method, &path)?;
124
125 Ok(OrdrFuture::new(
126 Box::pin(inner),
127 self.inner.ratelimiter.get(ratelimiter).acquire_owned(1),
128 ))
129 }
130
131 fn try_request_raw(
132 &self,
133 form: Option<Form>,
134 method: Method,
135 path: &str,
136 ) -> Result<ResponseFuture, ClientError> {
137 let mut url = String::with_capacity(BASE_URL.len() + path.len());
138 url.push_str(BASE_URL);
139 url.push_str(path);
140 debug!(?url);
141
142 debug_assert!(method != Method::POST || form.is_some());
143
144 let mut builder = HyperRequest::builder().method(method).uri(&url);
145
146 if let Some(headers) = builder.headers_mut() {
147 if let Some(ref form) = form {
148 headers.insert(CONTENT_LENGTH, HeaderValue::from(form.len()));
149
150 if let Ok(content_type) = HeaderValue::try_from(form.content_type()) {
151 headers.insert(CONTENT_TYPE, content_type);
152 }
153 }
154
155 headers.insert(USER_AGENT, HeaderValue::from_static(ROSU_RENDER_USER_AGENT));
156 }
157
158 let try_req = if let Some(form) = form {
159 builder.body(Full::from(form.build()))
160 } else {
161 builder.body(Full::default())
162 };
163
164 let req = try_req.map_err(|source| ClientError::BuildingRequest {
165 source: Box::new(source),
166 })?;
167
168 Ok(self.inner.http.request(req))
169 }
170}
171
172impl Default for OrdrClient {
173 fn default() -> Self {
174 Self::builder().build()
175 }
176}