1pub(crate) mod model;
2
3use std::{
4 collections::HashMap,
5 num::{ParseFloatError, ParseIntError},
6};
7
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)]
11#[serde(untagged)]
12pub enum Number {
13 Int(i64),
14 Float(f64),
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
18#[serde(untagged)]
19pub enum CodeValue {
20 Bool(bool),
21 String(String),
22 Number(Number),
23 CQCode(CQCode),
24}
25
26#[derive(Debug, thiserror::Error)]
27pub enum NumberError {
28 #[error(transparent)]
29 ParseInt(#[from] ParseIntError),
30 #[error(transparent)]
31 ParseFloat(#[from] ParseFloatError),
32}
33
34impl Number {
35 pub fn parse(s: impl AsRef<str>) -> Result<Self, NumberError> {
36 let s = s.as_ref();
37 if s.contains('.') {
38 s.parse::<f64>()
39 .map(Number::Float)
40 .map_err(NumberError::from)
41 } else {
42 s.parse::<i64>().map(Number::Int).map_err(NumberError::from)
43 }
44 }
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
56pub struct CQCode {
57 #[serde(rename = "$type")]
58 pub cq_type: String,
59 #[serde(flatten)]
60 pub data: HashMap<String, String>,
61}
62
63impl CQCode {
64 pub fn new(ty: impl Into<String>) -> Self {
65 CQCode {
66 cq_type: ty.into(),
67 data: HashMap::default(),
68 }
69 }
70
71 pub fn data_mut(&mut self) -> &mut HashMap<String, String> {
72 &mut self.data
73 }
74
75 pub fn data(&self) -> &HashMap<String, String> {
76 &self.data
77 }
78}
79
80impl<T, V, VS> From<(T, V)> for CQCode
81where
82 V: IntoIterator<Item = (VS, VS)>,
83 T: Into<String>,
84 VS: Into<String>,
85{
86 fn from(value: (T, V)) -> Self {
87 let (ty, data) = value;
88 let mut cq_code = CQCode::new(ty.into());
89 for (key, value) in data {
90 cq_code.data.insert(key.into(), value.into());
91 }
92 cq_code
93 }
94}
95
96pub fn parse_code(code: &str) -> Option<char> {
97 match code {
98 "amp" => Some('&'),
99 "#91" => Some('['),
100 "#93" => Some(']'),
101 "#44" => Some(','),
102 _ => None,
103 }
104}
105
106pub const fn escape_char(code: char) -> Option<&'static str> {
107 match code {
108 '&' => Some("amp"),
109 '[' => Some("#91"),
110 ']' => Some("#93"),
111 ',' => Some("#44"),
112 _ => None,
113 }
114}
115
116pub fn escape_str(s: &str) -> String {
117 let mut out = String::new();
118 for ele in s.chars() {
119 if let Some(escaped) = escape_char(ele) {
120 out.push_str(escaped);
121 } else {
122 out.push(ele);
123 }
124 }
125
126 out
127}