1use crate::{Arc, Error, ObjectIdentifier, Result, encoder::Encoder};
4
5#[derive(Debug)]
9pub(crate) struct Parser {
10 current_arc: Option<Arc>,
12
13 encoder: Encoder<{ ObjectIdentifier::MAX_SIZE }>,
15}
16
17impl Parser {
18 pub(crate) const fn parse(s: &str) -> Result<Self> {
20 let bytes = s.as_bytes();
21
22 if bytes.is_empty() {
23 return Err(Error::Empty);
24 }
25
26 match bytes[0] {
27 b'0'..=b'9' => Self {
28 current_arc: None,
29 encoder: Encoder::new(),
30 }
31 .parse_bytes(bytes),
32 actual => Err(Error::DigitExpected { actual }),
33 }
34 }
35
36 pub(crate) const fn finish(self) -> Result<ObjectIdentifier> {
38 self.encoder.finish()
39 }
40
41 const fn parse_bytes(mut self, bytes: &[u8]) -> Result<Self> {
43 match bytes {
44 [] => match self.current_arc {
46 Some(arc) => match self.encoder.arc(arc) {
47 Ok(encoder) => {
48 self.encoder = encoder;
49 Ok(self)
50 }
51 Err(err) => Err(err),
52 },
53 None => Err(Error::TrailingDot),
54 },
55 [byte @ b'0'..=b'9', remaining @ ..] => {
56 let digit = byte.saturating_sub(b'0');
57 let arc = match self.current_arc {
58 Some(arc) => arc,
59 None => 0,
60 };
61
62 self.current_arc = match arc.checked_mul(10) {
64 Some(arc) => match arc.checked_add(digit as Arc) {
65 None => return Err(Error::ArcTooBig),
66 Some(arc) => Some(arc),
67 },
68 None => return Err(Error::ArcTooBig),
69 };
70 self.parse_bytes(remaining)
71 }
72 [b'.', remaining @ ..] => {
73 match self.current_arc {
74 Some(arc) => {
75 if remaining.is_empty() {
76 return Err(Error::TrailingDot);
77 }
78
79 match self.encoder.arc(arc) {
81 Ok(encoder) => {
82 self.encoder = encoder;
83 self.current_arc = None;
84 self.parse_bytes(remaining)
85 }
86 Err(err) => Err(err),
87 }
88 }
89 None => Err(Error::RepeatedDot),
90 }
91 }
92 [byte, ..] => Err(Error::DigitExpected { actual: *byte }),
93 }
94 }
95}
96
97#[cfg(test)]
98#[allow(clippy::unwrap_used)]
99mod tests {
100 use super::Parser;
101 use crate::Error;
102
103 #[test]
104 fn parse() {
105 let oid = Parser::parse("1.23.456").unwrap().finish().unwrap();
106 assert_eq!(oid, "1.23.456".parse().unwrap());
107 }
108
109 #[test]
110 fn reject_empty_string() {
111 assert_eq!(Parser::parse("").err().unwrap(), Error::Empty);
112 }
113
114 #[test]
115 fn reject_non_digits() {
116 assert_eq!(
117 Parser::parse("X").err().unwrap(),
118 Error::DigitExpected { actual: b'X' }
119 );
120
121 assert_eq!(
122 Parser::parse("1.2.X").err().unwrap(),
123 Error::DigitExpected { actual: b'X' }
124 );
125 }
126
127 #[test]
128 fn reject_trailing_dot() {
129 assert_eq!(Parser::parse("1.23.").err().unwrap(), Error::TrailingDot);
130 }
131}