Skip to main content

rustolio_rpc/
error.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11use std::fmt::{Debug, Display};
12
13use rustolio_utils::{http::StatusCode, prelude::*};
14
15#[derive(Debug, Encode, Decode)]
16pub enum NoCustomError {}
17
18impl std::fmt::Display for NoCustomError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "self:?")
21    }
22}
23impl std::error::Error for NoCustomError {}
24
25#[derive(Debug, Encode, Decode)]
26pub enum ServerFnError<E = NoCustomError> {
27    /// Custom RPC Function Error
28    RpcError(E),
29
30    /// Client Network Error
31    NetworkError(String),
32
33    /// Server Error Response
34    /// This will not be serialized, instead an HttpRespone is build
35    #[encode(skip)]
36    HttpError(StatusCode, String),
37
38    /// Serialization Error on Client
39    SerializationError(String),
40
41    /// Deserialization Error on Client
42    DeserializationError(String),
43}
44
45impl<E> ServerFnError<E> {
46    pub fn http_error(
47        status_code: impl TryInto<StatusCode, Error: Debug>,
48        msg: impl Display,
49    ) -> Self {
50        ServerFnError::HttpError(
51            status_code.try_into().expect("Could not parse StatusCode"),
52            msg.to_string(),
53        )
54    }
55}
56
57// Cannot use the `From` trait here because this leads to conflicting implementations from `core`
58impl<E> ServerFnError<E> {
59    pub fn from(value: ServerFnError<NoCustomError>) -> Self {
60        match value {
61            ServerFnError::RpcError(_) => {
62                unreachable!("NoCustomError cannot be constructed")
63            }
64            ServerFnError::NetworkError(msg) => ServerFnError::NetworkError(msg),
65            ServerFnError::HttpError(status, msg) => ServerFnError::HttpError(status, msg),
66            ServerFnError::SerializationError(msg) => ServerFnError::SerializationError(msg),
67            ServerFnError::DeserializationError(msg) => ServerFnError::DeserializationError(msg),
68        }
69    }
70}
71
72impl ServerFnError<NoCustomError> {
73    pub fn into<E>(self) -> ServerFnError<E> {
74        ServerFnError::from(self)
75    }
76}
77
78impl<E> From<E> for ServerFnError<E> {
79    fn from(value: E) -> Self {
80        ServerFnError::RpcError(value)
81    }
82}
83
84pub trait IntoServerResult<T, E> {
85    fn server_result(self) -> std::result::Result<T, ServerFnError<E>>;
86}
87
88impl<T, E, C> IntoServerResult<T, C> for std::result::Result<T, E>
89where
90    E: Into<C>,
91{
92    fn server_result(self) -> std::result::Result<T, ServerFnError<C>> {
93        match self {
94            Ok(v) => Ok(v),
95            Err(e) => Err(ServerFnError::RpcError(e.into())),
96        }
97    }
98}
99
100impl<E> std::fmt::Display for ServerFnError<E> {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        write!(f, "self:?")
103    }
104}
105impl<E> std::error::Error for ServerFnError<E> where E: std::error::Error {}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[allow(unused)]
112    struct MyCustomError;
113
114    impl Into<ServerFnError<String>> for MyCustomError {
115        fn into(self) -> ServerFnError<String> {
116            ServerFnError::HttpError(StatusCode::BAD_REQUEST, "MyCustomError".to_string())
117        }
118    }
119
120    // This test does not need to be run - only checks for compilation failure
121    fn _test_server_fn_error_from_conversion() -> std::result::Result<(), ServerFnError<String>> {
122        // This should compile - uses From::from(String) -> ServerFnError<String>
123        Err("Custom error".to_string())?;
124        Err(ServerFnError::NetworkError("Network issue".to_string()))?;
125
126        // Conversion from any type that impls `Into<ServerFnError<E>>`
127        if let Err(e) = Err::<(), _>(MyCustomError) {
128            return Err(e.into());
129        }
130
131        // Conversion from E to ServerFnError<E>
132        if let Err(e) = Err::<(), _>(String::from("Custom error")) {
133            return Err(e.into());
134        }
135
136        // Conversion from ServerFnError<NoCustomError> to ServerFnError<E>
137        if let Err(e) =
138            Err::<(), ServerFnError>(ServerFnError::NetworkError("Network issue".to_string()))
139        {
140            return Err(e.into());
141        }
142
143        Ok(())
144    }
145
146    fn _rpc_endpoint() -> std::result::Result<String, ServerFnError<String>> {
147        let _ = Ok::<_, String>(String::from("Ok"))?;
148        let _ = Err(String::from("Err"))?;
149        let _ = Err("Err").server_result()?;
150        unreachable!()
151    }
152}