rustolio-rpc 0.1.0

An RPC extention for the HTTP-Server
Documentation
//
// SPDX-License-Identifier: MPL-2.0
//
// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

use std::fmt::{Debug, Display};

use rustolio_utils::{http::StatusCode, prelude::*};

#[derive(Debug, Encode, Decode)]
pub enum NoCustomError {}

impl std::fmt::Display for NoCustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "self:?")
    }
}
impl std::error::Error for NoCustomError {}

#[derive(Debug, Encode, Decode)]
pub enum ServerFnError<E = NoCustomError> {
    /// Custom RPC Function Error
    RpcError(E),

    /// Client Network Error
    NetworkError(String),

    /// Server Error Response
    /// This will not be serialized, instead an HttpRespone is build
    #[encode(skip)]
    HttpError(StatusCode, String),

    /// Serialization Error on Client
    SerializationError(String),

    /// Deserialization Error on Client
    DeserializationError(String),
}

impl<E> ServerFnError<E> {
    pub fn http_error(
        status_code: impl TryInto<StatusCode, Error: Debug>,
        msg: impl Display,
    ) -> Self {
        ServerFnError::HttpError(
            status_code.try_into().expect("Could not parse StatusCode"),
            msg.to_string(),
        )
    }
}

// Cannot use the `From` trait here because this leads to conflicting implementations from `core`
impl<E> ServerFnError<E> {
    pub fn from(value: ServerFnError<NoCustomError>) -> Self {
        match value {
            ServerFnError::RpcError(_) => {
                unreachable!("NoCustomError cannot be constructed")
            }
            ServerFnError::NetworkError(msg) => ServerFnError::NetworkError(msg),
            ServerFnError::HttpError(status, msg) => ServerFnError::HttpError(status, msg),
            ServerFnError::SerializationError(msg) => ServerFnError::SerializationError(msg),
            ServerFnError::DeserializationError(msg) => ServerFnError::DeserializationError(msg),
        }
    }
}

impl ServerFnError<NoCustomError> {
    pub fn into<E>(self) -> ServerFnError<E> {
        ServerFnError::from(self)
    }
}

impl<E> From<E> for ServerFnError<E> {
    fn from(value: E) -> Self {
        ServerFnError::RpcError(value)
    }
}

pub trait IntoServerResult<T, E> {
    fn server_result(self) -> std::result::Result<T, ServerFnError<E>>;
}

impl<T, E, C> IntoServerResult<T, C> for std::result::Result<T, E>
where
    E: Into<C>,
{
    fn server_result(self) -> std::result::Result<T, ServerFnError<C>> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => Err(ServerFnError::RpcError(e.into())),
        }
    }
}

impl<E> std::fmt::Display for ServerFnError<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "self:?")
    }
}
impl<E> std::error::Error for ServerFnError<E> where E: std::error::Error {}

#[cfg(test)]
mod tests {
    use super::*;

    #[allow(unused)]
    struct MyCustomError;

    impl Into<ServerFnError<String>> for MyCustomError {
        fn into(self) -> ServerFnError<String> {
            ServerFnError::HttpError(StatusCode::BAD_REQUEST, "MyCustomError".to_string())
        }
    }

    // This test does not need to be run - only checks for compilation failure
    fn _test_server_fn_error_from_conversion() -> std::result::Result<(), ServerFnError<String>> {
        // This should compile - uses From::from(String) -> ServerFnError<String>
        Err("Custom error".to_string())?;
        Err(ServerFnError::NetworkError("Network issue".to_string()))?;

        // Conversion from any type that impls `Into<ServerFnError<E>>`
        if let Err(e) = Err::<(), _>(MyCustomError) {
            return Err(e.into());
        }

        // Conversion from E to ServerFnError<E>
        if let Err(e) = Err::<(), _>(String::from("Custom error")) {
            return Err(e.into());
        }

        // Conversion from ServerFnError<NoCustomError> to ServerFnError<E>
        if let Err(e) =
            Err::<(), ServerFnError>(ServerFnError::NetworkError("Network issue".to_string()))
        {
            return Err(e.into());
        }

        Ok(())
    }

    fn _rpc_endpoint() -> std::result::Result<String, ServerFnError<String>> {
        let _ = Ok::<_, String>(String::from("Ok"))?;
        let _ = Err(String::from("Err"))?;
        let _ = Err("Err").server_result()?;
        unreachable!()
    }
}