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 rustolio_utils::http;

use crate::ServerFnError;

pub trait Extract<B, C>: Sized {
    fn extract(
        req: &http::Request<B>,
        ctx: C,
    ) -> impl std::future::Future<Output = Result<Self, ServerFnError>>;
}

#[cfg(test)]
mod tests {
    use rustolio_utils::http::StatusCode;
    use rustolio_utils::prelude::*;

    use super::*;

    #[derive(Debug)]
    struct MyCustomError;

    struct User {
        username: String,
    }

    #[derive(Debug)]
    struct Context {}

    impl<B, C> Extract<B, C> for User {
        async fn extract(req: &http::Request<B>, _ctx: C) -> Result<Self, ServerFnError> {
            let username = req
                .header("x-user-name")
                .ok_or(ServerFnError::http_error(
                    StatusCode::FORBIDDEN,
                    "Not logged in",
                ))?
                .to_str()
                .map_err(|_| {
                    ServerFnError::http_error(StatusCode::BAD_REQUEST, "Could not read HeaderValue")
                })?
                .to_string();
            Ok(Self { username })
        }
    }

    async fn rpc_fn<B>(
        req: http::Request<B>,
        ctx: &'static Context,
    ) -> Result<String, ServerFnError<MyCustomError>> {
        // Extractor call (is generated in #[rpc] macro)
        let user: User = match User::extract(&req, ctx).await {
            Ok(v) => v,
            Err(e) => return Err(e.into()),
        };

        // rpc fn code
        Ok(user.username)
    }

    #[tokio::test]
    async fn test_rpc_extractor() {
        let ctx = Context {};
        service!(ctx as Context);

        let req = http::Request::get("/").build().unwrap();
        let e = rpc_fn(req, ctx)
            .await
            .expect_err("Extractor should fail as there is no header set");
        assert!(matches!(
            e,
            ServerFnError::HttpError(StatusCode::FORBIDDEN, _)
        ));

        let req = http::Request::get("/")
            .header("x-user-name", "SomeUser")
            .build()
            .unwrap();
        let username = rpc_fn(req, ctx)
            .await
            .expect("User should be extracter as there is a username in the header");
        assert_eq!(username, "SomeUser");
    }
}