serenity/utils/
token.rs

1//! Utilities to parse and validate Discord tokens.
2
3use std::{fmt, str};
4
5/// Validates that a token is likely in a valid format.
6///
7/// This performs the following checks on a given token:
8/// - Is not empty;
9/// - Contains 3 parts (split by the period char `'.'`);
10/// - The second part of the token is at least 6 characters long;
11///
12/// # Examples
13///
14/// Validate that a token is valid and that a number of malformed tokens are actually invalid:
15///
16/// ```
17/// use serenity::utils::token::validate;
18///
19/// // ensure a valid token is in fact a valid format:
20/// assert!(validate("Mjg4NzYwMjQxMzYzODc3ODg4.C_ikow.j3VupLBuE1QWZng3TMGH0z_UAwg").is_ok());
21///
22/// assert!(validate("Mjg4NzYwMjQxMzYzODc3ODg4").is_err());
23/// assert!(validate("").is_err());
24/// ```
25///
26/// # Errors
27///
28/// Returns a [`InvalidToken`] when one of the above checks fail. The type of failure is not
29/// specified.
30pub fn validate(token: impl AsRef<str>) -> Result<(), InvalidToken> {
31    // Tokens can be preceded by "Bot " (that's how the Discord API expects them)
32    let mut parts = token.as_ref().trim_start_matches("Bot ").split('.');
33
34    let is_valid = parts.next().is_some_and(|p| !p.is_empty())
35        && parts.next().is_some_and(|p| !p.is_empty())
36        && parts.next().is_some_and(|p| !p.is_empty())
37        && parts.next().is_none();
38
39    if is_valid {
40        Ok(())
41    } else {
42        Err(InvalidToken)
43    }
44}
45
46/// Error that can be return by [`validate`].
47#[derive(Debug)]
48pub struct InvalidToken;
49
50impl std::error::Error for InvalidToken {}
51
52impl fmt::Display for InvalidToken {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.write_str("The provided token was invalid")
55    }
56}