Skip to main content

polyoxide_binance/shared/
symbol.rs

1//! The symbol a Binance request names, whichever product it is sent to.
2
3use std::{fmt, str::FromStr};
4
5/// A Binance symbol such as `BTCUSDT`, `BTCUSDT_261225` or `币安人生USDT`.
6///
7/// [`Symbol::new`] accepts 1 to 32 characters, each a Unicode letter, a digit
8/// or `_`. Binance lists Chinese-character symbols, and its quarterly futures
9/// carry their delivery date after an underscore; on 2026-10-07 the 924
10/// USDⓈ-M symbols used no other character and the longest had 17. ASCII
11/// letters are uppercased: no listed symbol has a lowercase one, stream names
12/// spell every symbol in lowercase, and uppercasing is what lets a stream name
13/// be parsed back to the symbol that built it.
14///
15/// Response rows carry symbols as `String`, taken as sent, so a symbol this
16/// type would refuse never fails a whole response.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct Symbol(String);
19
20/// A string [`Symbol::new`] refused.
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22#[error("{0:?} is not a Binance symbol: 1 to 32 letters, digits or underscores")]
23pub struct InvalidSymbol(pub String);
24
25/// An `InvalidRequest`: the caller named a symbol no request can carry.
26impl polyoxide_venue::Classify for InvalidSymbol {
27    fn class(&self) -> polyoxide_venue::Class {
28        polyoxide_venue::Class::InvalidRequest
29    }
30}
31
32impl Symbol {
33    /// The longest symbol [`Symbol::new`] accepts, in characters.
34    pub const MAX_LEN: usize = 32;
35
36    /// Checks a symbol and uppercases its ASCII letters.
37    pub fn new(symbol: impl Into<String>) -> Result<Self, InvalidSymbol> {
38        let symbol = symbol.into();
39        let chars = symbol.chars().count();
40        let valid = (1..=Self::MAX_LEN).contains(&chars)
41            && symbol.chars().all(|c| c.is_alphanumeric() || c == '_');
42        if valid {
43            Ok(Self(symbol.to_ascii_uppercase()))
44        } else {
45            Err(InvalidSymbol(symbol))
46        }
47    }
48
49    /// The symbol, as REST spells it.
50    pub fn as_str(&self) -> &str {
51        &self.0
52    }
53}
54
55impl fmt::Display for Symbol {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.write_str(&self.0)
58    }
59}
60
61impl FromStr for Symbol {
62    type Err = InvalidSymbol;
63
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        Self::new(s)
66    }
67}
68
69impl AsRef<str> for Symbol {
70    fn as_ref(&self) -> &str {
71        &self.0
72    }
73}