1use std::{fmt, str::FromStr, time::Duration};
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: u64 = 1;
18
19pub const DEFAULT: u64 = 30;
21
22#[derive(Debug, Error, Diagnostic)]
26#[error("expected period to be at least `{MIN}`, got `{value}`")]
27#[diagnostic(
28 code(otp_std::period),
29 help("make sure the period is at least `{MIN}`")
30)]
31pub struct Error {
32 pub value: u64,
34}
35
36impl Error {
37 pub const fn new(value: u64) -> Self {
39 Self { value }
40 }
41}
42
43#[derive(Debug, Error, Diagnostic)]
45#[error(transparent)]
46#[diagnostic(transparent)]
47pub enum ParseErrorSource {
48 Period(#[from] Error),
50 Int(#[from] int::ParseError),
52}
53
54#[derive(Debug, Error, Diagnostic)]
56#[error("failed to parse `{string}` to digits")]
57#[diagnostic(
58 code(otp_std::period::parse),
59 help("see the report for more information")
60)]
61pub struct ParseError {
62 #[source]
64 #[diagnostic_source]
65 pub source: ParseErrorSource,
66 pub string: String,
68}
69
70impl ParseError {
71 pub const fn new(source: ParseErrorSource, string: String) -> Self {
73 Self { source, string }
74 }
75
76 pub fn period(error: Error, string: String) -> Self {
78 Self::new(error.into(), string)
79 }
80
81 pub fn int(error: int::ParseError, string: String) -> Self {
83 Self::new(error.into(), string)
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89pub struct Period {
90 value: u64,
91}
92
93#[cfg(feature = "serde")]
94impl Serialize for Period {
95 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
96 self.get().serialize(serializer)
97 }
98}
99
100#[cfg(feature = "serde")]
101impl<'de> Deserialize<'de> for Period {
102 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
103 let value = u64::deserialize(deserializer)?;
104
105 Self::new(value).map_err(de::Error::custom)
106 }
107}
108
109errors! {
110 Type = ParseError,
111 Hack = $,
112 int_error => int(error, string => to_owned),
113 period_error => period(error, string => to_owned),
114}
115
116impl FromStr for Period {
117 type Err = ParseError;
118
119 fn from_str(string: &str) -> Result<Self, Self::Err> {
120 let value = string
121 .parse()
122 .map_err(|error| int_error!(int::wrap(error), string))?;
123
124 Self::new(value).map_err(|error| period_error!(error, string))
125 }
126}
127
128impl fmt::Display for Period {
129 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130 self.get().fmt(formatter)
131 }
132}
133
134impl TryFrom<u64> for Period {
135 type Error = Error;
136
137 fn try_from(value: u64) -> Result<Self, Self::Error> {
138 Self::new(value)
139 }
140}
141
142impl From<Period> for u64 {
143 fn from(period: Period) -> Self {
144 period.get()
145 }
146}
147
148impl Default for Period {
149 fn default() -> Self {
150 Self::DEFAULT
151 }
152}
153
154errors! {
155 Type = Error,
156 Hack = $,
157 error => new(value),
158}
159
160impl Period {
161 pub const fn new(value: u64) -> Result<Self, Error> {
169 const_try!(Self::check(value));
170
171 Ok(unsafe { Self::new_unchecked(value) })
173 }
174
175 pub const fn new_ok(value: u64) -> Option<Self> {
179 const_ok!(Self::new(value))
180 }
181
182 pub const fn check(value: u64) -> Result<(), Error> {
188 const_early!(value < MIN => error!(value));
189
190 Ok(())
191 }
192
193 pub const unsafe fn new_unchecked(value: u64) -> Self {
203 Self { value }
204 }
205
206 pub const fn get(self) -> u64 {
208 self.value
209 }
210
211 pub const fn as_duration(self) -> Duration {
213 Duration::from_secs(self.get())
214 }
215
216 pub const MIN: Self = Self::new_ok(MIN).unwrap();
218
219 pub const DEFAULT: Self = Self::new_ok(DEFAULT).unwrap();
221}