use std::fmt;
#[cfg(feature = "chrono")]
use chrono::NaiveDate;
#[cfg(not(feature = "chrono"))]
use time::{Date, Month};
use super::date_range::DateRange;
#[cfg(feature = "chrono")]
pub type GieDate = NaiveDate;
#[cfg(not(feature = "chrono"))]
pub type GieDate = Date;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DatasetType {
Eu,
Ne,
Ai,
}
impl DatasetType {
pub const fn as_str(self) -> &'static str {
match self {
Self::Eu => "eu",
Self::Ne => "ne",
Self::Ai => "ai",
}
}
}
impl fmt::Display for DatasetType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DatasetName {
Storage,
Lng,
Unknown(String),
}
impl DatasetName {
pub fn as_str(&self) -> &str {
match self {
Self::Storage => "storage",
Self::Lng => "lng",
Self::Unknown(value) => value.as_str(),
}
}
}
impl fmt::Display for DatasetName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecordType {
Country,
Company,
Facility,
Unknown(String),
}
impl RecordType {
pub fn as_str(&self) -> &str {
match self {
Self::Country => "country",
Self::Company => "company",
Self::Facility => "facility",
Self::Unknown(value) => value.as_str(),
}
}
}
impl fmt::Display for RecordType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DateFilter {
Day(GieDate),
Range(DateRange),
}
#[derive(Debug, Clone)]
pub struct GiePage<T> {
pub last_page: u32,
pub total: u32,
pub dataset: Option<DatasetName>,
pub gas_day: Option<GieDate>,
pub data: Vec<T>,
}
pub(crate) fn format_date(date: GieDate) -> String {
#[cfg(feature = "chrono")]
{
date.format("%Y-%m-%d").to_string()
}
#[cfg(not(feature = "chrono"))]
{
YmdDate(date).to_string()
}
}
pub(crate) fn parse_date(value: &str) -> Result<GieDate, String> {
let trimmed = value.trim();
if !trimmed.is_ascii() {
return Err("invalid date format, expected ASCII YYYY-MM-DD".to_string());
}
let bytes = trimmed.as_bytes();
if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
return Err("invalid date format, expected YYYY-MM-DD".to_string());
}
#[cfg(feature = "chrono")]
{
NaiveDate::parse_from_str(trimmed, "%Y-%m-%d")
.map_err(|error| format!("invalid calendar date: {error}"))
}
#[cfg(not(feature = "chrono"))]
{
let year = parse_year_component(&trimmed[0..4])?;
let month = parse_month_component(&trimmed[5..7])?;
let day = parse_day_component(&trimmed[8..10])?;
Date::from_calendar_date(year, month, day)
.map_err(|error| format!("invalid calendar date: {error}"))
}
}
pub(crate) fn parse_dataset_type(value: &str) -> Result<DatasetType, String> {
let trimmed = value.trim();
if trimmed.eq_ignore_ascii_case("eu") {
Ok(DatasetType::Eu)
} else if trimmed.eq_ignore_ascii_case("ne") {
Ok(DatasetType::Ne)
} else if trimmed.eq_ignore_ascii_case("ai") {
Ok(DatasetType::Ai)
} else {
Err(format!(
"invalid dataset type, expected one of: eu, ne, ai (got {trimmed:?})"
))
}
}
pub(crate) fn parse_dataset_name(value: &str) -> DatasetName {
let trimmed = value.trim();
if trimmed.eq_ignore_ascii_case("storage") {
DatasetName::Storage
} else if trimmed.eq_ignore_ascii_case("lng") {
DatasetName::Lng
} else {
DatasetName::Unknown(trimmed.to_string())
}
}
pub(crate) fn parse_record_type(value: &str) -> RecordType {
let trimmed = value.trim();
if trimmed.eq_ignore_ascii_case("country") {
RecordType::Country
} else if trimmed.eq_ignore_ascii_case("company") {
RecordType::Company
} else if trimmed.eq_ignore_ascii_case("facility") {
RecordType::Facility
} else {
RecordType::Unknown(trimmed.to_string())
}
}
#[cfg(not(feature = "chrono"))]
fn parse_year_component(value: &str) -> Result<i32, String> {
value
.parse::<i32>()
.map_err(|error| format!("invalid year component: {error}"))
}
#[cfg(not(feature = "chrono"))]
fn parse_month_component(value: &str) -> Result<Month, String> {
let month = value
.parse::<u8>()
.map_err(|error| format!("invalid month component: {error}"))?;
Month::try_from(month).map_err(|_| format!("month component is out of range: {month}"))
}
#[cfg(not(feature = "chrono"))]
fn parse_day_component(value: &str) -> Result<u8, String> {
value
.parse::<u8>()
.map_err(|error| format!("invalid day component: {error}"))
}
#[cfg(not(feature = "chrono"))]
struct YmdDate(Date);
#[cfg(not(feature = "chrono"))]
impl fmt::Display for YmdDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:04}-{:02}-{:02}",
self.0.year(),
u8::from(self.0.month()),
self.0.day()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_type_parser_is_case_insensitive_and_keeps_unknown_values() {
assert_eq!(parse_record_type(" country "), RecordType::Country);
assert_eq!(parse_record_type("CoMpAnY"), RecordType::Company);
assert_eq!(parse_record_type("FACILITY"), RecordType::Facility);
assert_eq!(
parse_record_type("pipeline"),
RecordType::Unknown("pipeline".to_string())
);
}
#[test]
fn dataset_name_parser_is_case_insensitive_and_keeps_unknown_values() {
assert_eq!(parse_dataset_name(" storage "), DatasetName::Storage);
assert_eq!(parse_dataset_name("LNG"), DatasetName::Lng);
assert_eq!(
parse_dataset_name("storage ERROR"),
DatasetName::Unknown("storage ERROR".to_string())
);
}
}