Skip to main content

mecomp_prost/
lib.rs

1mod mecomp {
2    // we can't really control the code-gen, so we have to allow some lints here
3    #![allow(
4        clippy::derive_partial_eq_without_eq,
5        clippy::missing_const_for_fn,
6        clippy::too_many_lines,
7        clippy::default_trait_access,
8        clippy::doc_markdown,
9        clippy::missing_errors_doc,
10        clippy::must_use_candidate
11    )]
12
13    #[cfg(not(tarpaulin_include))]
14    tonic::include_proto!("mecomp");
15}
16#[doc(hidden)]
17mod conversions;
18pub mod helpers;
19
20use std::time::Duration;
21
22use tonic::service::Interceptor;
23use tonic::service::interceptor::InterceptedService;
24use tonic::transport::Channel;
25
26pub use conversions::convert_std_duration;
27pub use mecomp::music_player_client as client;
28pub use mecomp::music_player_server as server;
29pub use mecomp::*;
30
31pub type LibraryBrief = mecomp::LibraryBriefResponse;
32pub type LibraryFull = mecomp::LibraryFullResponse;
33pub type LibraryHealth = mecomp::LibraryHealthResponse;
34
35pub type MusicPlayerClient =
36    client::MusicPlayerClient<InterceptedService<Channel, TraceInterceptor>>;
37
38#[derive(thiserror::Error, Debug)]
39pub enum ConnectionError {
40    #[error("{0}")]
41    Transport(#[from] tonic::transport::Error),
42    #[error("failed to connect to Music Player Daemon on port {port} after {retries} retries")]
43    MaxRetriesExceeded { port: u16, retries: u64 },
44}
45
46#[derive(Clone, Debug)]
47pub struct TraceInterceptor {}
48impl Interceptor for TraceInterceptor {
49    fn call(&mut self, req: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
50        tracing::trace!("Received request with extensions: {:?}", req.extensions());
51        Ok(req)
52    }
53}
54
55/// Initialize the music player client, without verifying the connection.
56///
57/// # Note
58///
59/// Does not check that the daemon is actually running, to get a verified connection use either `init_client` or `init_client_with_retry`
60///
61/// # Panics
62///
63/// Panics if <https://localhost:{rpc_port}> is not a valid URL.
64#[must_use]
65pub fn lazy_init_client(rpc_port: u16) -> MusicPlayerClient {
66    let endpoint = format!("http://localhost:{rpc_port}");
67
68    let endpoint = Channel::from_shared(endpoint)
69        .expect("Invalid endpoint URL")
70        .connect_lazy();
71
72    let interceptor = TraceInterceptor {};
73
74    music_player_client::MusicPlayerClient::with_interceptor(endpoint, interceptor)
75}
76
77/// Initialize the music player client
78///
79/// # Errors
80///
81/// If the client cannot be initialized, an error is returned.
82///
83/// # Panics
84///
85/// Panics if <https://localhost:{rpc_port}> is not a valid URL.
86pub async fn init_client(rpc_port: u16) -> Result<MusicPlayerClient, ConnectionError> {
87    let endpoint = format!("http://localhost:{rpc_port}");
88
89    let endpoint = Channel::from_shared(endpoint)
90        .expect("Invalid endpoint URL")
91        .connect()
92        .await?;
93
94    let interceptor = TraceInterceptor {};
95
96    let client = music_player_client::MusicPlayerClient::with_interceptor(endpoint, interceptor);
97
98    Ok(client)
99}
100
101/// Initialize a client to the Music Player Daemon, with `MAX_RETRIES` retries spaced `DELAY` seconds apart
102///
103/// Will log intermediate failures as warnings.
104///
105/// # Errors
106///
107/// Fails if the maximum number of retries was exceeded
108#[allow(clippy::missing_inline_in_public_items)]
109pub async fn init_client_with_retry<const MAX_RETRIES: u64, const DELAY: u64>(
110    rpc_port: u16,
111) -> Result<MusicPlayerClient, ConnectionError> {
112    let mut retries = 0u64;
113
114    while retries < MAX_RETRIES {
115        match init_client(rpc_port).await {
116            Ok(client) => return Ok(client),
117            Err(e) => {
118                retries += 1;
119                log::warn!("Failed to connect to daemon: {e}");
120                tokio::time::sleep(Duration::from_secs(DELAY * retries)).await;
121            }
122        }
123    }
124
125    log::error!("{MAX_RETRIES} retries exceeded when attempting to connect to the daemon");
126
127    Err(ConnectionError::MaxRetriesExceeded {
128        port: rpc_port,
129        retries: MAX_RETRIES,
130    })
131}