1use std::str::FromStr;
2
3use rand::{distributions::Alphanumeric, thread_rng, Rng};
4use serde::{Deserialize, Serialize};
5
6use crate::error::ApiError;
7
8const TOKEN_LENGTH: usize = 24;
9
10enum TokenType {
11 Service,
12}
13
14impl TokenType {
15 pub fn prefix(&self) -> &'static str {
16 match self {
17 TokenType::Service => "s",
18 }
19 }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Token(String);
24
25impl FromStr for Token {
26 type Err = ApiError;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 if s.starts_with(TokenType::Service.prefix()) {
30 Ok(Self(s.to_string()))
32 } else {
33 Err(ApiError::bad_request())
34 }
35 }
36}
37
38impl Token {
39 #[must_use]
40 pub fn new() -> Self {
41 let mut rng = thread_rng();
42 let chars: String = (0..TOKEN_LENGTH)
43 .map(|_| rng.sample(Alphanumeric) as char)
44 .collect();
45 let token = format!("{}.{chars}", TokenType::Service.prefix());
46 Self(token)
47 }
48
49 #[allow(clippy::inherent_to_string)]
51 #[must_use]
52 pub fn to_string(&self) -> String {
53 self.0.clone()
54 }
55}
56
57impl Default for Token {
58 fn default() -> Self {
59 Self::new()
60 }
61}