nihility-rpc 0.1.1

nihility project rpc module
Documentation
use std::fs;
use nihility_rpc::client::ExecuteClient;
use nihility_rpc::common::{AudioData, ExecuteData, ExecuteRequest, ExecuteResponse};
use nihility_rpc::server::ExecuteServer;
use std::pin::Pin;
use time::format_description::well_known::Iso8601;
use tokio::spawn;
use tokio::sync::{mpsc, oneshot};
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::{Stream, StreamExt};
use tonic::transport::Server;
use tonic::{Request, Response, Status, Streaming};
use tracing::{error, info};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt::time::LocalTime;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{fmt, Layer};

const TEST_PORT: usize = 8000;

type StreamResp = Pin<Box<dyn Stream<Item = Result<ExecuteResponse, Status>> + Send>>;

#[derive(Default)]
pub struct TestExecuteServer;

#[tonic::async_trait]
impl nihility_rpc::server::Execute for TestExecuteServer {
    async fn execute(
        &self,
        request: Request<ExecuteRequest>,
    ) -> Result<Response<ExecuteResponse>, Status> {
        let req: ExecuteData = request
            .into_inner()
            .try_into()
            .expect("execute request data format error");
        info!("Executing request: {:?}", req);
        Ok(Response::new(req.into()))
    }

    type ExecuteStreamOutStream = StreamResp;

    async fn execute_stream_out(
        &self,
        request: Request<ExecuteRequest>,
    ) -> Result<Response<Self::ExecuteStreamOutStream>, Status> {
        let req: ExecuteData = request
            .into_inner()
            .try_into()
            .expect("execute request data format error");
        info!("Executing request: {:?}", req);
        let (tx, rx) = mpsc::channel(11);
        for i in 0..10 {
            info!("stream out index: {}", i);
            tx.send(Ok(ExecuteData::String(format!("test, index: {}", i)).into()))
                .await
                .expect("stream out failed");
        }
        tx.send(Ok(ExecuteData::Audio(AudioData {
            data: vec![1f32, 1f32, 0.1],
            ..Default::default()
        })
        .into()))
            .await
            .expect("stream out failed");
        Ok(Response::new(
            Box::pin(ReceiverStream::new(rx)) as Self::ExecuteStreamOutStream
        ))
    }

    type ExecuteStreamStream = StreamResp;

    async fn execute_stream(
        &self,
        request: Request<Streaming<ExecuteRequest>>,
    ) -> Result<Response<Self::ExecuteStreamStream>, Status> {
        let mut req_stream = request.into_inner();
        let (tx, rx) = mpsc::channel(10);
        spawn(async move {
            while let Some(req) = req_stream.next().await {
                match req {
                    Ok(ok_req) => {
                        let req_data: ExecuteData = ok_req
                            .try_into()
                            .expect("execute request data format error");
                        info!("execute_stream Executing request: {:?}", req_data);
                        tx.send(Ok(req_data.into()))
                            .await
                            .expect("stream out failed");
                    }
                    Err(err_req) => {
                        error!("execute_stream Executing request error: {:?}", err_req);
                    }
                }
            }
        });
        Ok(Response::new(
            Box::pin(ReceiverStream::new(rx)) as Self::ExecuteStreamOutStream
        ))
    }
}

#[tokio::test]
async fn test_execute() {
    tracing_subscriber::registry()
        .with(
            fmt::layer()
                .with_ansi(false)
                .with_thread_ids(true)
                .with_target(true)
                .with_timer(LocalTime::new(Iso8601::DATE_TIME_OFFSET))
                .with_filter(LevelFilter::DEBUG),
        )
        .init();
    let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
    let server_handle = spawn(async move {
        test_execute_server(shutdown_rx).await;
    });
    test_execute_client(shutdown_tx).await;
    server_handle.await.expect("server failed");
}

async fn test_execute_server(shutdown_rx: oneshot::Receiver<()>) {
    let addr = format!("[::1]:{TEST_PORT}")
        .parse()
        .expect("parse addr error");
    info!("start server");
    Server::builder()
        .add_service(ExecuteServer::new(TestExecuteServer::default()))
        .serve_with_shutdown(addr, async move {
            let _ = shutdown_rx.await;
        })
        .await
        .expect("server error");
}

async fn test_execute_client(shutdown_tx: oneshot::Sender<()>) {
    let addr = format!("http://[::1]:{TEST_PORT}");
    let mut client = ExecuteClient::connect(addr).await.expect("connect error");
    info!("client connected");
    info!("test service execute function");
    let execute_resp: ExecuteData = client
        .execute(Request::new(
            ExecuteData::Audio(AudioData {
                data: vec![0.2, 0.2, 0.2],
                ..Default::default()
            })
            .into(),
        ))
        .await
        .expect("execute response error")
        .into_inner()
        .try_into()
        .expect("execute response format error");
    info!("execute response: {:?}", execute_resp);
    info!("test service execute_stream_out function");
    let mut execute_stream_out_resp = client
        .execute_stream_out(Request::new(ExecuteData::String("test".to_string()).into()))
        .await
        .expect("execute_stream_out response error")
        .into_inner();
    while let Some(Ok(resp_chunk)) = execute_stream_out_resp.next().await {
        info!(
            "execute_stream_out_resp_chunk: {:?}",
            ExecuteData::try_from(resp_chunk).expect("execute_stream_out_resp_chunk format error")
        );
    }
    info!("test service execute_stream function");
    let req_stream = tokio_stream::iter(1..10)
        .map(|i| ExecuteRequest::from(ExecuteData::String(format!("test {}", i))));
    let mut execute_stream_resp = client
        .execute_stream(req_stream)
        .await
        .expect("execute_stream response error")
        .into_inner();
    while let Some(Ok(resp_chunk)) = execute_stream_resp.next().await {
        info!(
            "execute_stream_resp_chunk: {:?}",
            ExecuteData::try_from(resp_chunk).expect("execute_stream_resp_chunk format error")
        );
    }
    info!("test audio data type");
    let audio_file = fs::File::open("data/test.wav").expect("open audio file error");
    let (header, audio_samples) = wav_io::read_from_file(audio_file).expect("read audio file error");
    let audio_resp: ExecuteData = client.execute(Request::new(ExecuteData::Audio(AudioData {
        data: audio_samples,
        ..Default::default()
    }).into())).await.expect("execute audio error").into_inner().try_into().expect("execute audio error");
    let mut out_file = fs::File::create("data/out.wav").expect("open audio file error");
    match audio_resp {
        ExecuteData::Audio(audio_data) => {
            wav_io::write_to_file(&mut out_file, &header, &audio_data.data).expect("write audio file error");
            info!("write audio file");
        }
        _ => {
            error!("wrong audio format");
        }
    }
    info!("test success");
    let _ = shutdown_tx.send(());
}