rosu_render/request/
render.rs

1use std::future::IntoFuture;
2
3use crate::{
4    model::{RenderAdded, RenderOptions, RenderSkinOption},
5    routing::Route,
6    util::multipart::Form,
7    ClientError, OrdrClient,
8};
9
10use super::{OrdrFuture, Request};
11
12enum ReplaySource<'a> {
13    File(&'a [u8]),
14    Url(&'a str),
15}
16
17/// Commission a render job to o!rdr.
18///
19/// If successful, progress of the rendering can be tracking through the [`OrdrWebsocket`](crate::OrdrWebsocket).
20#[must_use]
21pub struct CommissionRender<'a> {
22    ordr: &'a OrdrClient,
23    replay_source: ReplaySource<'a>,
24    username: &'a str,
25    skin: &'a RenderSkinOption<'a>,
26    options: Option<&'a RenderOptions>,
27}
28
29impl<'a> CommissionRender<'a> {
30    pub(crate) const fn with_file(
31        ordr: &'a OrdrClient,
32        replay_file: &'a [u8],
33        username: &'a str,
34        skin: &'a RenderSkinOption<'a>,
35    ) -> Self {
36        Self {
37            ordr,
38            replay_source: ReplaySource::File(replay_file),
39            username,
40            skin,
41            options: None,
42        }
43    }
44
45    pub(crate) const fn with_url(
46        ordr: &'a OrdrClient,
47        replay_url: &'a str,
48        username: &'a str,
49        skin: &'a RenderSkinOption<'a>,
50    ) -> Self {
51        Self {
52            ordr,
53            replay_source: ReplaySource::Url(replay_url),
54            username,
55            skin,
56            options: None,
57        }
58    }
59
60    /// Specify rendering options.
61    pub fn options(mut self, options: &'a RenderOptions) -> Self {
62        self.options = Some(options);
63
64        self
65    }
66}
67
68impl IntoFuture for &mut CommissionRender<'_> {
69    type Output = Result<RenderAdded, ClientError>;
70    type IntoFuture = OrdrFuture<RenderAdded>;
71
72    fn into_future(self) -> Self::IntoFuture {
73        let missing_resolution = self.options.is_none();
74
75        let mut form = self.options.map_or_else(Form::new, Form::serialize);
76
77        if missing_resolution {
78            form.push_text("resolution", RenderOptions::DEFAULT_RESOLUTION.as_str());
79        }
80
81        match self.replay_source {
82            ReplaySource::File(bytes) => form.push_replay("replayFile", bytes),
83            ReplaySource::Url(url) => form.push_text("replayURL", url),
84        };
85
86        form.push_text("username", self.username);
87
88        match self.skin {
89            RenderSkinOption::Official { name } => {
90                form.push_text("skin", name.as_ref())
91                    .push_text("customSkin", "false");
92            }
93            RenderSkinOption::Custom { id } => {
94                form.push_text("skin", id.to_string())
95                    .push_text("customSkin", "true");
96            }
97        }
98
99        if let Some(verification) = self.ordr.verification() {
100            form.push_text("verificationKey", verification.as_str());
101        }
102
103        self.ordr
104            .request(Request::builder(Route::Render).form(form).build())
105    }
106}
107
108impl IntoFuture for CommissionRender<'_> {
109    type Output = Result<RenderAdded, ClientError>;
110    type IntoFuture = OrdrFuture<RenderAdded>;
111
112    fn into_future(mut self) -> Self::IntoFuture {
113        (&mut self).into_future()
114    }
115}