perspective_client/utils/
mod.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13//! Utility functions that are common to the Perspective crates.
14
15mod clone;
16mod logging;
17
18#[cfg(test)]
19mod tests;
20
21use std::sync::Arc;
22
23use rand_unique::{RandomSequence, RandomSequenceBuilder};
24use thiserror::*;
25
26use crate::proto;
27
28#[derive(Clone, Error, Debug)]
29pub enum ClientError {
30    #[error("View not found")]
31    ViewNotFound,
32
33    #[error("Abort(): {0}")]
34    Internal(String),
35
36    #[error("Transport error: {0}")]
37    TransportError(String),
38
39    #[error("Client not yet initialized")]
40    NotInitialized,
41
42    #[error("Unknown error: {0}")]
43    Unknown(String),
44
45    #[error("Unwrapped option")]
46    Option,
47
48    #[error("Bad string")]
49    Utf8(#[from] std::str::Utf8Error),
50
51    #[error("Undecipherable server message {0:?}")]
52    DecodeError(#[from] prost::DecodeError),
53
54    #[error("Response aborted")]
55    ResponseAborted,
56
57    #[error("Unexpected response {0:?}")]
58    ResponseFailed(Box<proto::response::ClientResp>),
59
60    #[error("Not yet implemented {0:?}")]
61    NotImplemented(&'static str),
62
63    #[error("Can't use both `limit` and `index` arguments")]
64    BadTableOptions,
65
66    #[error("External error: {0}")]
67    ExternalError(Arc<Box<dyn std::error::Error + Send + Sync>>),
68
69    #[error("Undecipherable proto message")]
70    ProtoError(#[from] prost::EncodeError),
71
72    #[error("Duplicate name {0}")]
73    DuplicateNameError(String),
74}
75
76pub type ClientResult<T> = Result<T, ClientError>;
77
78impl From<Box<dyn std::error::Error + Send + Sync>> for ClientError {
79    fn from(value: Box<dyn std::error::Error + Send + Sync>) -> Self {
80        ClientError::ExternalError(Arc::new(value))
81    }
82}
83
84impl<'a, A> From<std::sync::PoisonError<std::sync::MutexGuard<'a, A>>> for ClientError {
85    fn from(_: std::sync::PoisonError<std::sync::MutexGuard<'a, A>>) -> Self {
86        ClientError::Internal("Lock Error".to_owned())
87    }
88}
89
90impl From<Option<proto::response::ClientResp>> for ClientError {
91    fn from(value: Option<proto::response::ClientResp>) -> Self {
92        match value {
93            Some(proto::response::ClientResp::ServerError(x)) => match x.status_code() {
94                proto::StatusCode::ServerError => ClientError::Internal(x.message),
95                proto::StatusCode::ViewNotFound => ClientError::ViewNotFound,
96                proto::StatusCode::TransportError => ClientError::TransportError(x.message),
97            },
98            Some(x) => ClientError::ResponseFailed(Box::new(x)),
99            None => ClientError::ResponseAborted,
100        }
101    }
102}
103
104impl From<proto::response::ClientResp> for ClientError {
105    fn from(value: proto::response::ClientResp) -> Self {
106        match value {
107            proto::response::ClientResp::ServerError(x) => match x.status_code() {
108                proto::StatusCode::ServerError => ClientError::Internal(x.message),
109                proto::StatusCode::ViewNotFound => ClientError::ViewNotFound,
110                proto::StatusCode::TransportError => ClientError::TransportError(x.message),
111            },
112            x => ClientError::ResponseFailed(Box::new(x)),
113        }
114    }
115}
116
117pub trait PerspectiveResultExt {
118    fn unwrap_or_log(&self);
119}
120
121impl<T, E> PerspectiveResultExt for Result<T, E>
122where
123    E: std::error::Error,
124{
125    fn unwrap_or_log(&self) {
126        if let Err(e) = self {
127            tracing::warn!("{}", e);
128        }
129    }
130}
131
132/// Generate a sequence of IDs
133#[derive(Clone)]
134pub struct IDGen(Arc<std::sync::Mutex<RandomSequence<u32>>>);
135
136impl Default for IDGen {
137    fn default() -> Self {
138        Self(Arc::new(std::sync::Mutex::new(Self::new_seq())))
139    }
140}
141
142impl IDGen {
143    fn new_seq() -> RandomSequence<u32> {
144        let mut rng = rand::rngs::ThreadRng::default();
145        let config = RandomSequenceBuilder::<u32>::rand(&mut rng);
146        config.into_iter()
147    }
148
149    pub fn next(&self) -> u32 {
150        let mut idgen = self.0.lock().unwrap();
151        if let Some(x) = idgen.next() {
152            x
153        } else {
154            *idgen = Self::new_seq();
155            idgen.next().unwrap()
156        }
157    }
158}