Skip to main content

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