1use std::{fmt, str::FromStr};
4
5use const_macros::{const_early, const_ok, const_try};
6
7use miette::Diagnostic;
8
9#[cfg(feature = "serde")]
10use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
11
12use thiserror::Error;
13
14use crate::{int, macros::errors};
15
16pub const MIN: u8 = 6;
18
19pub const MAX: u8 = 8;
21
22pub const DEFAULT: u8 = MIN;
24
25#[derive(Debug, Error, Diagnostic)]
29#[error("expected digits in `[{MIN}, {MAX}]` range, got `{value}`")]
30#[diagnostic(
31 code(otp_std::digits),
32 help("make sure the digits are at least `{MIN}` and at most `{MAX}`")
33)]
34pub struct Error {
35 pub value: u8,
37}
38
39impl Error {
40 pub const fn new(value: u8) -> Self {
42 Self { value }
43 }
44}
45
46#[derive(Debug, Error, Diagnostic)]
48#[error(transparent)]
49#[diagnostic(transparent)]
50pub enum ParseErrorSource {
51 Digits(#[from] Error),
53 Int(#[from] int::ParseError),
55}
56
57#[derive(Debug, Error, Diagnostic)]
59#[error("failed to parse `{string}` to digits")]
60#[diagnostic(
61 code(otp_std::digits::parse),
62 help("see the report for more information")
63)]
64pub struct ParseError {
65 #[source]
67 #[diagnostic_source]
68 pub source: ParseErrorSource,
69 pub string: String,
71}
72
73impl ParseError {
74 pub const fn new(source: ParseErrorSource, string: String) -> Self {
76 Self { source, string }
77 }
78
79 pub fn digits(error: Error, string: String) -> Self {
81 Self::new(error.into(), string)
82 }
83
84 pub fn int(error: int::ParseError, string: String) -> Self {
86 Self::new(error.into(), string)
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub struct Digits {
93 value: u8,
94}
95
96#[cfg(feature = "serde")]
97impl Serialize for Digits {
98 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
99 self.get().serialize(serializer)
100 }
101}
102
103#[cfg(feature = "serde")]
104impl<'de> Deserialize<'de> for Digits {
105 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
106 let value = u8::deserialize(deserializer)?;
107
108 Self::new(value).map_err(de::Error::custom)
109 }
110}
111
112errors! {
113 Type = ParseError,
114 Hack = $,
115 digits_error => digits(error, string => to_owned),
116 int_error => int(error, string => to_owned),
117}
118
119impl FromStr for Digits {
120 type Err = ParseError;
121
122 fn from_str(string: &str) -> Result<Self, Self::Err> {
123 let value = string
124 .parse()
125 .map_err(|error| int_error!(int::wrap(error), string))?;
126
127 Self::new(value).map_err(|error| digits_error!(error, string))
128 }
129}
130
131impl fmt::Display for Digits {
132 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133 self.get().fmt(formatter)
134 }
135}
136
137impl TryFrom<u8> for Digits {
138 type Error = Error;
139
140 fn try_from(value: u8) -> Result<Self, Self::Error> {
141 Self::new(value)
142 }
143}
144
145impl From<Digits> for u8 {
146 fn from(digits: Digits) -> Self {
147 digits.get()
148 }
149}
150
151impl Default for Digits {
152 fn default() -> Self {
153 Self::DEFAULT
154 }
155}
156
157errors! {
158 Type = Error,
159 Hack = $,
160 error => new(value),
161}
162
163impl Digits {
164 pub const fn new(value: u8) -> Result<Self, Error> {
172 const_try!(Self::check(value));
173
174 Ok(unsafe { Self::new_unchecked(value) })
176 }
177
178 pub const fn new_ok(value: u8) -> Option<Self> {
182 const_ok!(Self::new(value))
183 }
184
185 pub const fn check(value: u8) -> Result<(), Error> {
191 const_early!(value < MIN || value > MAX => error!(value));
192
193 Ok(())
194 }
195
196 pub const unsafe fn new_unchecked(value: u8) -> Self {
206 Self { value }
207 }
208
209 pub const MIN: Self = Self::new_ok(MIN).unwrap();
211
212 pub const MAX: Self = Self::new_ok(MAX).unwrap();
214
215 pub const DEFAULT: Self = Self::new_ok(DEFAULT).unwrap();
217
218 pub const fn count(self) -> usize {
220 self.get() as usize
221 }
222
223 pub const fn get(self) -> u8 {
225 self.value
226 }
227
228 pub const fn power(self) -> u32 {
230 10u32.pow(self.get() as u32)
231 }
232
233 pub fn string(self, code: u32) -> String {
237 format!("{code:0count$}", count = self.count())
238 }
239}