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(feature = "talc-allocator")]
19mod talc_allocator;
20
21#[cfg(test)]
22mod tests;
23
24use std::sync::Arc;
25use std::time::SystemTimeError;
26
27use rand::rngs::StdRng;
28use rand::{Rng, SeedableRng};
29use rand_unique::{RandomSequence, RandomSequenceBuilder};
30#[cfg(feature = "talc-allocator")]
31pub(crate) use talc_allocator::get_used;
32use thiserror::*;
33
34use crate::proto;
35
36#[derive(Clone, Error, Debug)]
37pub enum ClientError {
38    #[error("View not found")]
39    ViewNotFound,
40
41    #[error("Abort(): {0}")]
42    Internal(String),
43
44    #[error("Transport error: {0}")]
45    TransportError(String),
46
47    #[error("Client not yet initialized")]
48    NotInitialized,
49
50    #[error("Unknown error: {0}")]
51    Unknown(String),
52
53    #[error("Unwrapped option")]
54    Option,
55
56    #[error("Bad string")]
57    Utf8(#[from] std::str::Utf8Error),
58
59    #[error("Undecipherable server message {0:?}")]
60    DecodeError(#[from] prost::DecodeError),
61
62    #[error("Response aborted")]
63    ResponseAborted,
64
65    #[error("Unexpected response {0:?}")]
66    ResponseFailed(Box<proto::response::ClientResp>),
67
68    #[error("Not yet implemented {0:?}")]
69    NotImplemented(&'static str),
70
71    #[error("Can't use both `limit` and `index` arguments")]
72    BadTableOptions,
73
74    #[error("External error: {0}")]
75    ExternalError(Arc<Box<dyn std::error::Error + Send + Sync>>),
76
77    #[error("Undecipherable proto message")]
78    ProtoError(#[from] prost::EncodeError),
79
80    #[error("Duplicate name {0}")]
81    DuplicateNameError(String),
82
83    #[error("{0}")]
84    TimeError(#[from] SystemTimeError),
85}
86
87pub type ClientResult<T> = Result<T, ClientError>;
88
89impl From<Box<dyn std::error::Error + Send + Sync>> for ClientError {
90    fn from(value: Box<dyn std::error::Error + Send + Sync>) -> Self {
91        ClientError::ExternalError(Arc::new(value))
92    }
93}
94
95impl<'a, A> From<std::sync::PoisonError<std::sync::MutexGuard<'a, A>>> for ClientError {
96    fn from(_: std::sync::PoisonError<std::sync::MutexGuard<'a, A>>) -> Self {
97        ClientError::Internal("Lock Error".to_owned())
98    }
99}
100
101impl From<Option<proto::response::ClientResp>> for ClientError {
102    fn from(value: Option<proto::response::ClientResp>) -> Self {
103        match value {
104            Some(proto::response::ClientResp::ServerError(x)) => match x.status_code() {
105                proto::StatusCode::ServerError => ClientError::Internal(x.message),
106                proto::StatusCode::ViewNotFound => ClientError::ViewNotFound,
107                proto::StatusCode::TransportError => ClientError::TransportError(x.message),
108            },
109            Some(x) => ClientError::ResponseFailed(Box::new(x)),
110            None => ClientError::ResponseAborted,
111        }
112    }
113}
114
115impl From<proto::response::ClientResp> for ClientError {
116    fn from(value: proto::response::ClientResp) -> Self {
117        match value {
118            proto::response::ClientResp::ServerError(x) => match x.status_code() {
119                proto::StatusCode::ServerError => ClientError::Internal(x.message),
120                proto::StatusCode::ViewNotFound => ClientError::ViewNotFound,
121                proto::StatusCode::TransportError => ClientError::TransportError(x.message),
122            },
123            x => ClientError::ResponseFailed(Box::new(x)),
124        }
125    }
126}
127
128pub trait PerspectiveResultExt {
129    fn unwrap_or_log(&self);
130}
131
132impl<T, E> PerspectiveResultExt for Result<T, E>
133where
134    E: std::error::Error,
135{
136    fn unwrap_or_log(&self) {
137        if let Err(e) = self {
138            tracing::warn!("{}", e);
139        }
140    }
141}
142
143/// Generate a sequence of IDs
144#[derive(Clone)]
145pub struct IDGen(Arc<std::sync::Mutex<RandomSequence<u32>>>);
146
147impl Default for IDGen {
148    fn default() -> Self {
149        Self(Arc::new(std::sync::Mutex::new(Self::new_seq())))
150    }
151}
152
153impl IDGen {
154    fn new_seq() -> RandomSequence<u32> {
155        let mut rng = rand::rngs::ThreadRng::default();
156        let config = RandomSequenceBuilder::<u32>::rand(&mut rng);
157        config.into_iter()
158    }
159
160    pub fn next(&self) -> u32 {
161        let mut idgen = self.0.lock().unwrap();
162        if let Some(x) = idgen.next() {
163            x
164        } else {
165            *idgen = Self::new_seq();
166            idgen.next().unwrap()
167        }
168    }
169}
170
171const SIZE: usize = 21;
172
173const CHARACTERS: [char; 52] = [
174    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
175    't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
176    'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
177];
178
179/// Generate a random identifier `String`
180pub fn randid() -> String {
181    let mask = CHARACTERS.len().next_power_of_two() - 1;
182    let step: usize = 8 * SIZE / 5;
183    let mut id = String::with_capacity(SIZE);
184    loop {
185        let mut random = StdRng::from_os_rng();
186        let mut bytes: Vec<u8> = vec![0; step];
187        random.fill(&mut bytes[..]);
188        for &byte in &bytes {
189            let byte = byte as usize & mask;
190            if CHARACTERS.len() > byte {
191                id.push(CHARACTERS[byte]);
192                if id.len() == SIZE {
193                    return id;
194                }
195            }
196        }
197    }
198}