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
125pub(crate) fn format_date(date: GieDate) -> String {
126 #[cfg(feature = "chrono")]
127 {
128 date.format("%Y-%m-%d").to_string()
129 }
130 #[cfg(not(feature = "chrono"))]
131 {
132 YmdDate(date).to_string()
133 }
134}
135
136pub(crate) fn parse_date(value: &str) -> Result<GieDate, String> {
137 let trimmed = value.trim();
138 if !trimmed.is_ascii() {
139 return Err("invalid date format, expected ASCII YYYY-MM-DD".to_string());
140 }
141
142 let bytes = trimmed.as_bytes();
143 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
144 return Err("invalid date format, expected YYYY-MM-DD".to_string());
145 }
146
147 #[cfg(feature = "chrono")]
148 {
149 NaiveDate::parse_from_str(trimmed, "%Y-%m-%d")
150 .map_err(|error| format!("invalid calendar date: {error}"))
151 }
152 #[cfg(not(feature = "chrono"))]
153 {
154 let year = parse_year_component(&trimmed[0..4])?;
155 let month = parse_month_component(&trimmed[5..7])?;
156 let day = parse_day_component(&trimmed[8..10])?;
157
158 Date::from_calendar_date(year, month, day)
159 .map_err(|error| format!("invalid calendar date: {error}"))
160 }
161}
162
163pub(crate) fn parse_dataset_type(value: &str) -> Result<DatasetType, String> {
164 let trimmed = value.trim();
165
166 if trimmed.eq_ignore_ascii_case("eu") {
167 Ok(DatasetType::Eu)
168 } else if trimmed.eq_ignore_ascii_case("ne") {
169 Ok(DatasetType::Ne)
170 } else if trimmed.eq_ignore_ascii_case("ai") {
171 Ok(DatasetType::Ai)
172 } else {
173 Err(format!(
174 "invalid dataset type, expected one of: eu, ne, ai (got {trimmed:?})"
175 ))
176 }
177}
178
179pub(crate) fn parse_dataset_name(value: &str) -> DatasetName {
180 let trimmed = value.trim();
181
182 if trimmed.eq_ignore_ascii_case("storage") {
183 DatasetName::Storage
184 } else if trimmed.eq_ignore_ascii_case("lng") {
185 DatasetName::Lng
186 } else {
187 DatasetName::Unknown(trimmed.to_string())
188 }
189}
190
191pub(crate) fn parse_record_type(value: &str) -> RecordType {
192 let trimmed = value.trim();
193
194 if trimmed.eq_ignore_ascii_case("country") {
195 RecordType::Country
196 } else if trimmed.eq_ignore_ascii_case("company") {
197 RecordType::Company
198 } else if trimmed.eq_ignore_ascii_case("facility") {
199 RecordType::Facility
200 } else {
201 RecordType::Unknown(trimmed.to_string())
202 }
203}
204
205#[cfg(not(feature = "chrono"))]
206fn parse_year_component(value: &str) -> Result<i32, String> {
207 value
208 .parse::<i32>()
209 .map_err(|error| format!("invalid year component: {error}"))
210}
211
212#[cfg(not(feature = "chrono"))]
213fn parse_month_component(value: &str) -> Result<Month, String> {
214 let month = value
215 .parse::<u8>()
216 .map_err(|error| format!("invalid month component: {error}"))?;
217
218 Month::try_from(month).map_err(|_| format!("month component is out of range: {month}"))
219}
220
221#[cfg(not(feature = "chrono"))]
222fn parse_day_component(value: &str) -> Result<u8, String> {
223 value
224 .parse::<u8>()
225 .map_err(|error| format!("invalid day component: {error}"))
226}
227
228#[cfg(not(feature = "chrono"))]
229struct YmdDate(Date);
230
231#[cfg(not(feature = "chrono"))]
232impl fmt::Display for YmdDate {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(
235 f,
236 "{:04}-{:02}-{:02}",
237 self.0.year(),
238 u8::from(self.0.month()),
239 self.0.day()
240 )
241 }
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}