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