gie_client/common/
types.rs1use std::fmt;
2
3#[cfg(feature = "chrono")]
4use chrono::NaiveDate;
5#[cfg(not(feature = "chrono"))]
6use time::{Date, Month};
7
8use super::date_range::DateRange;
9
10#[cfg(feature = "chrono")]
12pub type GieDate = NaiveDate;
13#[cfg(not(feature = "chrono"))]
15pub type GieDate = Date;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum DatasetType {
20 Eu,
22 Ne,
24 Ai,
26}
27
28impl DatasetType {
29 pub const fn as_str(self) -> &'static str {
30 match self {
31 Self::Eu => "eu",
32 Self::Ne => "ne",
33 Self::Ai => "ai",
34 }
35 }
36}
37
38impl fmt::Display for DatasetType {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.write_str(self.as_str())
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum DatasetName {
47 Storage,
49 Lng,
51 Unknown(String),
53}
54
55impl DatasetName {
56 pub fn as_str(&self) -> &str {
57 match self {
58 Self::Storage => "storage",
59 Self::Lng => "lng",
60 Self::Unknown(value) => value.as_str(),
61 }
62 }
63}
64
65impl fmt::Display for DatasetName {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.write_str(self.as_str())
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum RecordType {
74 Country,
76 Company,
78 Facility,
80 Unknown(String),
82}
83
84impl RecordType {
85 pub fn as_str(&self) -> &str {
86 match self {
87 Self::Country => "country",
88 Self::Company => "company",
89 Self::Facility => "facility",
90 Self::Unknown(value) => value.as_str(),
91 }
92 }
93}
94
95impl fmt::Display for RecordType {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(self.as_str())
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum DateFilter {
104 Day(GieDate),
106 Range(DateRange),
108}
109
110#[derive(Debug, Clone)]
112pub struct GiePage<T> {
113 pub last_page: u32,
115 pub total: u32,
117 pub dataset: Option<DatasetName>,
119 pub gas_day: Option<GieDate>,
121 pub data: Vec<T>,
123}
124
125#[cfg(not(feature = "chrono"))]
126struct YmdDate(Date);
127
128#[cfg(not(feature = "chrono"))]
129impl fmt::Display for YmdDate {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(
132 f,
133 "{:04}-{:02}-{:02}",
134 self.0.year(),
135 u8::from(self.0.month()),
136 self.0.day()
137 )
138 }
139}
140
141pub(crate) fn format_date(date: GieDate) -> String {
142 #[cfg(feature = "chrono")]
143 {
144 date.format("%Y-%m-%d").to_string()
145 }
146 #[cfg(not(feature = "chrono"))]
147 {
148 YmdDate(date).to_string()
149 }
150}
151
152pub(crate) fn parse_date(value: &str) -> Result<GieDate, String> {
153 let trimmed = value.trim();
154 if !trimmed.is_ascii() {
155 return Err("invalid date format, expected ASCII YYYY-MM-DD".to_string());
156 }
157
158 let bytes = trimmed.as_bytes();
159 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
160 return Err("invalid date format, expected YYYY-MM-DD".to_string());
161 }
162
163 #[cfg(feature = "chrono")]
164 {
165 NaiveDate::parse_from_str(trimmed, "%Y-%m-%d")
166 .map_err(|error| format!("invalid calendar date: {error}"))
167 }
168 #[cfg(not(feature = "chrono"))]
169 {
170 let year = parse_year_component(&trimmed[0..4])?;
171 let month = parse_month_component(&trimmed[5..7])?;
172 let day = parse_day_component(&trimmed[8..10])?;
173
174 Date::from_calendar_date(year, month, day)
175 .map_err(|error| format!("invalid calendar date: {error}"))
176 }
177}
178
179pub(crate) fn parse_dataset_type(value: &str) -> Result<DatasetType, String> {
180 let trimmed = value.trim();
181
182 if trimmed.eq_ignore_ascii_case("eu") {
183 Ok(DatasetType::Eu)
184 } else if trimmed.eq_ignore_ascii_case("ne") {
185 Ok(DatasetType::Ne)
186 } else if trimmed.eq_ignore_ascii_case("ai") {
187 Ok(DatasetType::Ai)
188 } else {
189 Err(format!(
190 "invalid dataset type, expected one of: eu, ne, ai (got {trimmed:?})"
191 ))
192 }
193}
194
195pub(crate) fn parse_dataset_name(value: &str) -> DatasetName {
196 let trimmed = value.trim();
197
198 if trimmed.eq_ignore_ascii_case("storage") {
199 DatasetName::Storage
200 } else if trimmed.eq_ignore_ascii_case("lng") {
201 DatasetName::Lng
202 } else {
203 DatasetName::Unknown(trimmed.to_string())
204 }
205}
206
207pub(crate) fn parse_record_type(value: &str) -> RecordType {
208 let trimmed = value.trim();
209
210 if trimmed.eq_ignore_ascii_case("country") {
211 RecordType::Country
212 } else if trimmed.eq_ignore_ascii_case("company") {
213 RecordType::Company
214 } else if trimmed.eq_ignore_ascii_case("facility") {
215 RecordType::Facility
216 } else {
217 RecordType::Unknown(trimmed.to_string())
218 }
219}
220
221#[cfg(not(feature = "chrono"))]
222fn parse_year_component(value: &str) -> Result<i32, String> {
223 value
224 .parse::<i32>()
225 .map_err(|error| format!("invalid year component: {error}"))
226}
227
228#[cfg(not(feature = "chrono"))]
229fn parse_month_component(value: &str) -> Result<Month, String> {
230 let month = value
231 .parse::<u8>()
232 .map_err(|error| format!("invalid month component: {error}"))?;
233
234 Month::try_from(month).map_err(|_| format!("month component is out of range: {month}"))
235}
236
237#[cfg(not(feature = "chrono"))]
238fn parse_day_component(value: &str) -> Result<u8, String> {
239 value
240 .parse::<u8>()
241 .map_err(|error| format!("invalid day component: {error}"))
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn record_type_parser_is_case_insensitive_and_keeps_unknown_values() {
250 assert_eq!(parse_record_type(" country "), RecordType::Country);
251 assert_eq!(parse_record_type("CoMpAnY"), RecordType::Company);
252 assert_eq!(parse_record_type("FACILITY"), RecordType::Facility);
253 assert_eq!(
254 parse_record_type("pipeline"),
255 RecordType::Unknown("pipeline".to_string())
256 );
257 }
258
259 #[test]
260 fn dataset_name_parser_is_case_insensitive_and_keeps_unknown_values() {
261 assert_eq!(parse_dataset_name(" storage "), DatasetName::Storage);
262 assert_eq!(parse_dataset_name("LNG"), DatasetName::Lng);
263 assert_eq!(
264 parse_dataset_name("storage ERROR"),
265 DatasetName::Unknown("storage ERROR".to_string())
266 );
267 }
268}