rosu_render/client/
error.rs

1use std::{
2    error::Error as StdError,
3    fmt::{Debug, Display, Formatter, Result as FmtResult},
4    str::from_utf8 as str_from_utf8,
5};
6
7use hyper::{
8    body::{Bytes, Incoming},
9    Response,
10};
11use serde::{
12    de::{Deserializer, Error as DeError, Unexpected, Visitor},
13    Deserialize,
14};
15use serde_json::Error as JsonError;
16use serde_urlencoded::ser::Error as UrlError;
17use thiserror::Error as ThisError;
18
19use crate::model::SkinDeleted;
20
21#[derive(Debug, ThisError)]
22#[non_exhaustive]
23pub enum ClientError {
24    #[error("Failed to build the request")]
25    BuildingRequest {
26        #[source]
27        source: Box<dyn StdError + Send + Sync + 'static>,
28    },
29    #[error("Failed to chunk the response")]
30    ChunkingResponse {
31        #[source]
32        source: hyper::Error,
33    },
34    #[error("Failed to deserialize response body: {body}")]
35    Parsing {
36        body: StringOrBytes,
37        #[source]
38        source: JsonError,
39    },
40    #[error("Parsing or sending the response failed")]
41    RequestError {
42        #[source]
43        source: hyper_util::client::legacy::Error,
44    },
45    #[error("Response error: status code {status_code}, {error}")]
46    Response {
47        body: Bytes,
48        error: ApiError,
49        status_code: u16,
50    },
51    #[error("Failed to serialize the query")]
52    SerdeQuery {
53        #[from]
54        source: UrlError,
55    },
56    #[error("API may be temporarily unavailable (received a 503)")]
57    ServiceUnavailable { response: Response<Incoming> },
58    #[error("Skin was not found (received a 404)")]
59    SkinDeleted { error: SkinDeleted },
60}
61
62impl ClientError {
63    pub(crate) fn response_error(bytes: Bytes, status_code: u16) -> Self {
64        match serde_json::from_slice(&bytes) {
65            Ok(error) => Self::Response {
66                body: bytes,
67                error,
68                status_code,
69            },
70            Err(source) => Self::Parsing {
71                body: bytes.into(),
72                source,
73            },
74        }
75    }
76}
77
78#[derive(Clone, Debug)]
79pub struct StringOrBytes {
80    bytes: Bytes,
81}
82
83impl Display for StringOrBytes {
84    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
85        match str_from_utf8(&self.bytes) {
86            Ok(string) => f.write_str(string),
87            Err(_) => <[u8] as Debug>::fmt(&*self.bytes, f),
88        }
89    }
90}
91
92impl From<Bytes> for StringOrBytes {
93    fn from(bytes: Bytes) -> Self {
94        Self { bytes }
95    }
96}
97
98#[derive(Debug, Deserialize)]
99pub struct ApiError {
100    /// The response of the server.
101    pub message: Box<str>,
102    /// The reason of the ban (if provided by admins).
103    pub reason: Option<Box<str>>,
104    /// The error code of the creation of this render.
105    #[serde(rename = "errorCode")]
106    pub code: Option<ErrorCode>,
107}
108
109impl Display for ApiError {
110    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
111        if let Some(ref code) = self.code {
112            write!(f, "Error code {code}: ")?;
113        }
114
115        f.write_str(&self.message)?;
116
117        if let Some(ref reason) = self.reason {
118            write!(f, " (reason: {reason})")?;
119        }
120
121        Ok(())
122    }
123}
124
125/// Error codes as defined by o!rdr
126///
127/// See <https://ordr.issou.best/docs/#section/Error-codes>
128#[derive(Copy, Clone, Debug, ThisError, PartialEq, Eq, Hash)]
129#[non_exhaustive]
130#[repr(u8)]
131pub enum ErrorCode {
132    #[error("Emergency stop (triggered manually)")]
133    EmergencyStop,
134    #[error("Replay download error (bad upload from the sender)")]
135    ReplayParsingError,
136    #[error("Replay download error (bad download from the server), can happen because of invalid characters")]
137    ReplayDownloadError,
138    #[error("All beatmap mirrors are unavailable")]
139    MirrorsUnavailable,
140    #[error("Replay file corrupted")]
141    ReplayFileCorrupted,
142    #[error("Invalid osu! gamemode (not 0 = std)")]
143    InvalidGameMode,
144    #[error("The replay has no input data")]
145    ReplayWithoutInputData,
146    #[error("Beatmap does not exist on osu! (probably because of custom difficulty or non-submitted map)")]
147    BeatmapNotFound,
148    #[error("Audio for the map is unavailable (because of copyright claim)")]
149    BeatmapAudioUnavailable,
150    #[error("Cannot connect to osu! api")]
151    OsuApiConnection,
152    #[error("The replay has the autoplay mod")]
153    ReplayIsAutoplay,
154    #[error("The replay username has invalid characters")]
155    InvalidReplayUsername,
156    #[error("The beatmap is longer than 15 minutes")]
157    BeatmapTooLong,
158    #[error("This player is banned from o!rdr")]
159    PlayerBannedFromOrdr,
160    #[error("Beatmap not found on all the beatmap mirrors")]
161    MapNotFound,
162    #[error("This IP is banned from o!rdr")]
163    IpBannedFromOrdr,
164    #[error("This username is banned from o!rdr")]
165    UsernameBannedFromOrdr,
166    #[error("Unknown error from the renderer")]
167    UnknownRendererError,
168    #[error("The renderer cannot download the map")]
169    CannotDownloadMap,
170    #[error("Beatmap version on the mirror is not the same as the replay")]
171    InconsistentMapVersion,
172    #[error("The replay is corrupted (danser cannot process it)")]
173    ReplayFileCorrupted2,
174    #[error("Server-side problem while finalizing the generated video")]
175    FailedFinalizing,
176    #[error("Server-side problem while preparing the render")]
177    ServerFailedPreparation,
178    #[error("The beatmap has no name")]
179    BeatmapHasNoName,
180    #[error("The replay is missing input data")]
181    ReplayMissingInputData,
182    #[error("The replay has incompatible mods")]
183    ReplayIncompatibleMods,
184    #[error(
185        "Something with the renderer went wrong: it probably has an unstable internet connection \
186        (multiple renders at the same time)"
187    )]
188    RendererIssue,
189    #[error("The renderer cannot download the replay")]
190    CannotDownloadReplay,
191    #[error("The replay is already rendering or in queue")]
192    ReplayAlreadyInQueue,
193    #[error("The star rating is greater than 20")]
194    StarRatingTooHigh,
195    #[error("The mapper is blacklisted")]
196    MapperIsBlacklisted,
197    #[error("The beatmapset is blacklisted")]
198    BeatmapsetIsBlacklisted,
199    #[error("The replay has already errored less than an hour ago")]
200    ReplayErroredRecently,
201    #[error("invalid replay URL or can't download the replay (if replayURL is provided)")]
202    InvalidReplayUrl,
203    #[error("a required field is missing (the missing field is shown in the message)")]
204    MissingField,
205    #[error("your last replays have a too high error rate (cannot be triggered when you're a verified bot)")]
206    ErrorRateTooHigh,
207    #[error("the replay username is inappropriate")]
208    InappropriateUsername,
209    #[error("this skin does not exist")]
210    SkinDoesNotExist,
211    #[error("this custom skin does not exist or has been deleted")]
212    CustomSkinDoesNotExist,
213    #[error("o!rdr is not ready to take render jobs at the moment")]
214    RenderJobsPaused,
215    #[error("o!rdr is not ready to take render jobs from unauthenticated users at the moment (verified bots are not authenticated users)")]
216    UnauthenticatedRenderJobsPaused,
217    #[error("replay accuracy is too bad and you're not authenticated")]
218    AccuracyTooLow,
219    #[error("this score does not exist")]
220    ScoreDoesNotExist,
221    #[error("the replay for this score isn't available")]
222    ReplayUnavailable,
223    #[error("invalid osu! ruleset score ID")]
224    InvalidRulesetId,
225    #[error("Unknown error code {0}")]
226    Other(u8),
227}
228
229impl ErrorCode {
230    #[must_use]
231    pub fn to_u8(self) -> u8 {
232        match self {
233            Self::EmergencyStop => 1,
234            Self::ReplayParsingError => 2,
235            Self::ReplayDownloadError => 3,
236            Self::MirrorsUnavailable => 4,
237            Self::ReplayFileCorrupted => 5,
238            Self::InvalidGameMode => 6,
239            Self::ReplayWithoutInputData => 7,
240            Self::BeatmapNotFound => 8,
241            Self::BeatmapAudioUnavailable => 9,
242            Self::OsuApiConnection => 10,
243            Self::ReplayIsAutoplay => 11,
244            Self::InvalidReplayUsername => 12,
245            Self::BeatmapTooLong => 13,
246            Self::PlayerBannedFromOrdr => 14,
247            Self::MapNotFound => 15,
248            Self::IpBannedFromOrdr => 16,
249            Self::UsernameBannedFromOrdr => 17,
250            Self::UnknownRendererError => 18,
251            Self::CannotDownloadMap => 19,
252            Self::InconsistentMapVersion => 20,
253            Self::ReplayFileCorrupted2 => 21,
254            Self::FailedFinalizing => 22,
255            Self::ServerFailedPreparation => 23,
256            Self::BeatmapHasNoName => 24,
257            Self::ReplayMissingInputData => 25,
258            Self::ReplayIncompatibleMods => 26,
259            Self::RendererIssue => 27,
260            Self::CannotDownloadReplay => 28,
261            Self::ReplayAlreadyInQueue => 29,
262            Self::StarRatingTooHigh => 30,
263            Self::MapperIsBlacklisted => 31,
264            Self::BeatmapsetIsBlacklisted => 32,
265            Self::ReplayErroredRecently => 33,
266            Self::InvalidReplayUrl => 34,
267            Self::MissingField => 35,
268            Self::ErrorRateTooHigh => 36,
269            Self::InappropriateUsername => 37,
270            Self::SkinDoesNotExist => 38,
271            Self::CustomSkinDoesNotExist => 39,
272            Self::RenderJobsPaused => 40,
273            Self::UnauthenticatedRenderJobsPaused => 41,
274            Self::AccuracyTooLow => 42,
275            Self::ScoreDoesNotExist => 43,
276            Self::ReplayUnavailable => 44,
277            Self::InvalidRulesetId => 45,
278            Self::Other(code) => code,
279        }
280    }
281}
282
283impl From<u8> for ErrorCode {
284    fn from(code: u8) -> Self {
285        match code {
286            1 => Self::EmergencyStop,
287            2 => Self::ReplayParsingError,
288            5 => Self::ReplayFileCorrupted,
289            6 => Self::InvalidGameMode,
290            7 => Self::ReplayWithoutInputData,
291            8 => Self::BeatmapNotFound,
292            9 => Self::BeatmapAudioUnavailable,
293            10 => Self::OsuApiConnection,
294            11 => Self::ReplayIsAutoplay,
295            12 => Self::InvalidReplayUsername,
296            13 => Self::BeatmapTooLong,
297            14 => Self::PlayerBannedFromOrdr,
298            16 => Self::IpBannedFromOrdr,
299            17 => Self::UsernameBannedFromOrdr,
300            23 => Self::ServerFailedPreparation,
301            24 => Self::BeatmapHasNoName,
302            25 => Self::ReplayMissingInputData,
303            26 => Self::ReplayIncompatibleMods,
304            29 => Self::ReplayAlreadyInQueue,
305            30 => Self::StarRatingTooHigh,
306            31 => Self::MapperIsBlacklisted,
307            32 => Self::BeatmapsetIsBlacklisted,
308            33 => Self::ReplayErroredRecently,
309            34 => Self::InvalidReplayUrl,
310            35 => Self::MissingField,
311            36 => Self::ErrorRateTooHigh,
312            37 => Self::InappropriateUsername,
313            38 => Self::SkinDoesNotExist,
314            39 => Self::CustomSkinDoesNotExist,
315            40 => Self::RenderJobsPaused,
316            41 => Self::UnauthenticatedRenderJobsPaused,
317            42 => Self::AccuracyTooLow,
318            43 => Self::ScoreDoesNotExist,
319            44 => Self::ReplayUnavailable,
320            45 => Self::InvalidRulesetId,
321            other => Self::Other(other),
322        }
323    }
324}
325
326impl<'de> Deserialize<'de> for ErrorCode {
327    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
328        struct ErrorCodeVisitor;
329
330        impl Visitor<'_> for ErrorCodeVisitor {
331            type Value = ErrorCode;
332
333            fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
334                f.write_str("u8")
335            }
336
337            fn visit_u8<E: DeError>(self, v: u8) -> Result<Self::Value, E> {
338                Ok(ErrorCode::from(v))
339            }
340
341            fn visit_u64<E: DeError>(self, v: u64) -> Result<Self::Value, E> {
342                let code = u8::try_from(v).map_err(|_| {
343                    DeError::invalid_value(Unexpected::Unsigned(v), &"a valid error code")
344                })?;
345
346                self.visit_u8(code)
347            }
348        }
349
350        d.deserialize_u8(ErrorCodeVisitor)
351    }
352}