1use std::{fmt, str::FromStr};
16
17use crate::cbor;
18
19#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
20#[repr(u8)]
21pub enum EraName {
22 Byron = 1,
23 Shelley = 2,
24 Allegra = 3,
25 Mary = 4,
26 Alonzo = 5,
27 Babbage = 6,
28 Conway = 7,
29 Dijkstra = 8,
30}
31
32pub const ERA_NAMES: [EraName; 8] = [
33 EraName::Byron,
34 EraName::Shelley,
35 EraName::Allegra,
36 EraName::Mary,
37 EraName::Alonzo,
38 EraName::Babbage,
39 EraName::Conway,
40 EraName::Dijkstra,
41];
42
43const ERA_STRINGS: [&str; 8] = ["Byron", "Shelley", "Allegra", "Mary", "Alonzo", "Babbage", "Conway", "Dijkstra"];
44
45impl EraName {
46 pub const fn is_tagged_on_network(self) -> bool {
47 !matches!(self, EraName::Byron)
48 }
49
50 pub const fn as_str(self) -> &'static str {
51 ERA_STRINGS[self as usize - 1]
52 }
53
54 pub const fn header_variant(self) -> u8 {
55 (self as u8) - 1
56 }
57
58 pub const fn from_header_variant(variant: u8) -> Result<EraName, EraNameError> {
59 match variant {
63 0 => Ok(EraName::Byron),
64 1 => Ok(EraName::Shelley),
65 2 => Ok(EraName::Allegra),
66 3 => Ok(EraName::Mary),
67 4 => Ok(EraName::Alonzo),
68 5 => Ok(EraName::Babbage),
69 6 => Ok(EraName::Conway),
70 7 => Ok(EraName::Dijkstra),
71 _ => Err(EraNameError::InvalidEraTag(variant)),
72 }
73 }
74}
75
76impl fmt::Display for EraName {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{:?}", self)
79 }
80}
81
82impl FromStr for EraName {
83 type Err = EraNameError;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 let (_, era_name) = ERA_STRINGS
87 .into_iter()
88 .zip(ERA_NAMES)
89 .find(|(era_str, _)| era_str == &s)
90 .ok_or(EraNameError::InvalidEraName(s.to_string()))?;
91 Ok(era_name)
92 }
93}
94
95impl From<EraName> for u8 {
96 fn from(value: EraName) -> Self {
97 value as u8
98 }
99}
100
101#[derive(Debug, thiserror::Error, PartialEq, Eq)]
102pub enum EraNameError {
103 #[error("Invalid era name: {0}")]
104 InvalidEraName(String),
105 #[error("Invalid era tag: {0}")]
106 InvalidEraTag(u8),
107}
108
109impl TryFrom<u8> for EraName {
110 type Error = EraNameError;
111
112 fn try_from(value: u8) -> Result<Self, Self::Error> {
113 let ix = value as usize;
114 if ix == 0 || ix > ERA_NAMES.len() {
115 return Err(EraNameError::InvalidEraTag(value));
116 }
117 Ok(ERA_NAMES[ix - 1])
118 }
119}
120
121impl<'de> serde::Deserialize<'de> for EraName {
122 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123 where
124 D: serde::Deserializer<'de>,
125 {
126 if deserializer.is_human_readable() {
127 let s = String::deserialize(deserializer)?;
128 Self::from_str(&s).map_err(serde::de::Error::custom)
129 } else {
130 let value = u8::deserialize(deserializer)?;
131 Self::try_from(value).map_err(serde::de::Error::custom)
132 }
133 }
134}
135
136impl serde::Serialize for EraName {
137 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138 where
139 S: serde::Serializer,
140 {
141 if serializer.is_human_readable() {
142 serializer.serialize_str(self.as_str())
143 } else {
144 serializer.serialize_u8(*self as u8)
145 }
146 }
147}
148
149impl<C> cbor::Encode<C> for EraName {
150 fn encode<W: cbor::encode::Write>(
151 &self,
152 e: &mut cbor::Encoder<W>,
153 _ctx: &mut C,
154 ) -> Result<(), cbor::encode::Error<W::Error>> {
155 e.u8(*self as u8)?;
156 Ok(())
157 }
158}
159
160impl<'b, C> cbor::Decode<'b, C> for EraName {
161 fn decode(d: &mut cbor::Decoder<'b>, _ctx: &mut C) -> Result<Self, cbor::decode::Error> {
162 let value = d.u8()?;
163 EraName::try_from(value).map_err(cbor::decode::Error::message)
164 }
165}
166
167#[cfg(any(test, feature = "test-utils"))]
168pub fn any_era_name() -> impl proptest::prelude::Strategy<Value = EraName> {
169 proptest::sample::select(&ERA_NAMES)
170}
171
172#[cfg(test)]
173mod tests {
174 use std::str::FromStr;
175
176 use proptest::prelude::*;
177
178 use super::*;
179 use crate::to_cbor;
180
181 proptest! {
182 #[test]
183 fn era_name_string_roundtrip(era_name in any_era_name()) {
184 let string = format!("{}", era_name);
185 assert_eq!(EraName::from_str(&string), Ok(era_name));
186 }
187
188 #[test]
189 fn era_name_debug_roundtrip(era_name in any_era_name()) {
190 let string = format!("{:?}", era_name);
191 assert_eq!(EraName::from_str(&string), Ok(era_name));
192 }
193
194 #[test]
195 fn era_name_encode_decode_roundtrip(era_name in any_era_name()) {
196 let buffer = cbor::to_vec(era_name).unwrap();
197 let decoded: EraName = cbor::decode(&buffer).unwrap();
198 assert_eq!(era_name, decoded);
199 }
200
201 #[test]
202 fn era_name_serde_string_roundtrip(era_name in any_era_name()) {
203 let string = serde_json::to_string(&era_name).unwrap();
204 let decoded: EraName = serde_json::from_str(&string).unwrap();
205 assert_eq!(era_name, decoded);
206 }
207
208 #[test]
209 fn era_name_serde_binary_roundtrip(era_name in any_era_name()) {
210 let bytes = cbor4ii::serde::to_vec(Vec::new(), &era_name).unwrap();
211 let decoded: EraName = cbor4ii::serde::from_slice(&bytes).unwrap();
212 assert_eq!(era_name, decoded);
213 }
214 }
215
216 #[test]
217 fn cbor_encoding() {
218 let cbor = to_cbor(&ERA_NAMES);
219 assert_eq!(cbor, &[0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
220 }
221
222 #[test]
223 fn serde_string_encoding() {
224 let string = serde_json::to_string(&ERA_NAMES).unwrap();
225 assert_eq!(string, r#"["Byron","Shelley","Allegra","Mary","Alonzo","Babbage","Conway","Dijkstra"]"#);
226 }
227
228 #[test]
229 fn serde_binary_encoding() {
230 let bytes = cbor4ii::serde::to_vec(Vec::new(), &ERA_NAMES).unwrap();
231 assert_eq!(bytes, &[0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
232 }
233}