feat: proper error handling & error propagation

This commit is contained in:
Erica Marigold 2023-08-28 15:58:11 +05:30
parent 6b4000d253
commit e4a65e301b
No known key found for this signature in database
GPG key ID: 23CD97ABBBCC5ED2
3 changed files with 104 additions and 89 deletions

View file

@ -1,7 +1,6 @@
use crate::lune::builtins::datetime::date_time::Timezone; use crate::lune::builtins::datetime::date_time::Timezone;
use chrono::prelude::*; use chrono::prelude::*;
use chrono_locale::LocaleDate; use chrono_locale::LocaleDate;
use mlua::prelude::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -37,31 +36,6 @@ impl Default for DateTimeBuilder {
} }
} }
impl<'lua> FromLua<'lua> for Timezone {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
fn num_to_enum(num: i32) -> LuaResult<Timezone> {
match num {
1 => Ok(Timezone::Utc),
2 => Ok(Timezone::Local),
_ => Err(LuaError::external("Invalid enum member!")),
}
}
match value {
LuaValue::Integer(num) => num_to_enum(num),
LuaValue::Number(num) => num_to_enum(num as i32),
LuaValue::String(str) => match str.to_str()?.to_lowercase().as_str() {
"utc" => Ok(Timezone::Utc),
"local" => Ok(Timezone::Local),
&_ => Err(LuaError::external("Invalid enum member!")),
},
_ => Err(LuaError::external(
"Invalid enum type, number or string expected",
)),
}
}
}
impl DateTimeBuilder { impl DateTimeBuilder {
/// Builder method to set the `Year`. /// Builder method to set the `Year`.
pub fn with_year(&mut self, year: i32) -> &mut Self { pub fn with_year(&mut self, year: i32) -> &mut Self {
@ -114,7 +88,12 @@ impl DateTimeBuilder {
} }
/// Converts the `DateTimeBuilder` to a string with a specified format and locale. /// Converts the `DateTimeBuilder` to a string with a specified format and locale.
pub fn to_string<T>(self, timezone: Timezone, format: Option<T>, locale: Option<T>) -> String pub fn to_string<T>(
self,
timezone: Timezone,
format: Option<T>,
locale: Option<T>,
) -> Result<String, ()>
where where
T: ToString, T: ToString,
{ {
@ -134,7 +113,7 @@ impl DateTimeBuilder {
} }
}); });
match timezone { Ok(match timezone {
Timezone::Utc => Utc Timezone::Utc => Utc
.with_ymd_and_hms( .with_ymd_and_hms(
self.year, self.year,
@ -144,7 +123,8 @@ impl DateTimeBuilder {
self.minute, self.minute,
self.second, self.second,
) )
.unwrap() .single()
.ok_or(())?
.formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) .formatl((*format_lazy).as_str(), (*locale_lazy).as_str())
.to_string(), .to_string(),
Timezone::Local => Local Timezone::Local => Local
@ -156,50 +136,14 @@ impl DateTimeBuilder {
self.minute, self.minute,
self.second, self.second,
) )
.unwrap() .single()
.ok_or(())?
.formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) .formatl((*format_lazy).as_str(), (*locale_lazy).as_str())
.to_string(), .to_string(),
} })
} }
fn build(self) -> Self { pub fn build(self) -> Self {
self self
} }
} }
impl LuaUserData for DateTimeBuilder {}
impl<'lua> FromLua<'lua> for DateTimeBuilder {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
match value {
LuaValue::Table(t) => Ok(Self::default()
.with_year(t.get("year")?)
.with_month(
(match t.get("month")? {
LuaValue::String(str) => Ok(str.to_str()?.parse::<Month>().or(Err(
LuaError::external("could not cast month string to Month"),
))?),
LuaValue::Nil => {
Err(LuaError::external("cannot find mandatory month argument"))
}
LuaValue::Number(num) => Ok(Month::try_from(num as u8).or(Err(
LuaError::external("could not cast month number to Month"),
))?),
LuaValue::Integer(int) => Ok(Month::try_from(int as u8).or(Err(
LuaError::external("could not cast month integer to Month"),
))?),
_ => Err(LuaError::external("unexpected month field type")),
})?,
)
.with_day(t.get("day")?)
.with_hour(t.get("hour")?)
.with_minute(t.get("minute")?)
.with_second(t.get("second")?)
// TODO: millisecond support
.build()),
_ => Err(LuaError::external(
"expected type table for DateTimeBuilder",
)),
}
}
}

View file

@ -211,10 +211,10 @@ impl DateTime {
Self::to_datetime_builder(Utc.timestamp_opt(self.unix_timestamp, 0).unwrap()) Self::to_datetime_builder(Utc.timestamp_opt(self.unix_timestamp, 0).unwrap())
} }
/// Formats a date as a ISO 8601 date-time string. The value returned by this /// Formats a date as a ISO 8601 date-time string, returns None if the DateTime object is invalid.
/// function could be passed to `from_local_time` to produce the original `DateTime` /// The value returned by this function could be passed to `from_local_time` to produce the
/// object. /// original `DateTime` object.
pub fn to_iso_date(&self) -> String { pub fn to_iso_date(&self) -> Result<String, ()> {
self.to_universal_time() self.to_universal_time()
.to_string::<&str>(Timezone::Utc, None, None) .to_string::<&str>(Timezone::Utc, None, None)
} }
@ -230,7 +230,7 @@ impl DateTime {
/// and a format string. The format string should contain tokens, which will /// and a format string. The format string should contain tokens, which will
/// replace to certain date/time values described by the `DateTime` object. /// replace to certain date/time values described by the `DateTime` object.
/// For more details, see the [accepted formatter tokens](https://docs.rs/chrono/latest/chrono/format/strftime/index.html). /// For more details, see the [accepted formatter tokens](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
pub fn format_time<T>(&self, timezone: Timezone, fmt_str: T, locale: T) -> String pub fn format_time<T>(&self, timezone: Timezone, fmt_str: T, locale: T) -> Result<String, ()>
where where
T: ToString, T: ToString,
{ {

View file

@ -1,3 +1,4 @@
use chrono::Month;
use mlua::prelude::*; use mlua::prelude::*;
pub(crate) mod builder; pub(crate) mod builder;
@ -10,7 +11,6 @@ use self::{
use crate::lune::util::TableBuilder; use crate::lune::util::TableBuilder;
// TODO: Proper error handling and stuff // TODO: Proper error handling and stuff
// FIX: fromUnixTimestamp calculation is broken
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)? TableBuilder::new(lua)?
@ -19,11 +19,9 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
let timestamp_cloned = timestamp.clone(); let timestamp_cloned = timestamp.clone();
let timestamp_kind = TimestampType::from_lua(timestamp, lua)?; let timestamp_kind = TimestampType::from_lua(timestamp, lua)?;
let timestamp = match timestamp_kind { let timestamp = match timestamp_kind {
TimestampType::Seconds => timestamp_cloned.as_i64().unwrap(), TimestampType::Seconds => timestamp_cloned.as_i64().ok_or(LuaError::external("invalid float integer timestamp supplied"))?,
TimestampType::Millis => { TimestampType::Millis => {
// FIXME: Remove the unwrap let timestamp = timestamp_cloned.as_f64().ok_or(LuaError::external("invalid float timestamp with millis component supplied"))?;
// If something breaks, blame this.
let timestamp = timestamp_cloned.as_f64().unwrap();
((((timestamp - timestamp.fract()) as u64) * 1000_u64) // converting the whole seconds part to millis ((((timestamp - timestamp.fract()) as u64) * 1000_u64) // converting the whole seconds part to millis
// the ..3 gets a &str of the first 3 chars of the digits after the decimals, ignoring // the ..3 gets a &str of the first 3 chars of the digits after the decimals, ignoring
@ -47,20 +45,25 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
Ok(DateTime::from_local_time(DateTimeBuilder::from_lua(date_time, lua).ok())) Ok(DateTime::from_local_time(DateTimeBuilder::from_lua(date_time, lua).ok()))
})? })?
.with_function("toLocalTime", |_, this: DateTime| { .with_function("toLocalTime", |_, this: DateTime| {
Ok(this.to_local_time()) Ok(DateTime::to_local_time(&this))
})? })?
.with_function("fromIsoDate", |_, iso_date: LuaString| { .with_function("fromIsoDate", |_, iso_date: LuaString| {
Ok(DateTime::from_iso_date(iso_date.to_string_lossy())) Ok(DateTime::from_iso_date(iso_date.to_string_lossy()))
})? })?
.with_function("toIsoDate", |_, this| Ok(DateTime::to_iso_date(&this)))? .with_function("toIsoDate", |_, this| Ok(DateTime::to_iso_date(&this).map_err(|()| LuaError::external(
"failed to parse DateTime object, invalid",
))))?
.with_function( .with_function(
"formatTime", "formatTime",
|_, (this, timezone, fmt_str, locale): (DateTime, LuaValue, LuaString, LuaString)| { |_, (this, timezone, fmt_str, locale): (DateTime, LuaValue, LuaString, LuaString)| {
Ok(this.format_time( Ok(DateTime::format_time(
&this,
Timezone::from_lua(timezone, lua)?, Timezone::from_lua(timezone, lua)?,
fmt_str.to_string_lossy(), fmt_str.to_string_lossy(),
locale.to_string_lossy(), locale.to_string_lossy(),
)) ).map_err(|()| LuaError::external(
"failed to parse DateTime object, invalid",
)))
}, },
)? )?
.build_readonly() .build_readonly()
@ -103,16 +106,22 @@ impl LuaUserData for DateTime {
} }
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date())); methods.add_method("toIsoDate", |_, this, ()| {
Ok(this
.to_iso_date()
.map_err(|()| LuaError::external("failed to parse DateTime object, invalid")))
});
methods.add_method( methods.add_method(
"formatTime", "formatTime",
|_, this, (timezone, fmt_str, locale): (LuaValue, LuaString, LuaString)| { |_, this, (timezone, fmt_str, locale): (LuaValue, LuaString, LuaString)| {
Ok(this.format_time( Ok(this
Timezone::from_lua(timezone, &Lua::new())?, .format_time(
fmt_str.to_string_lossy(), Timezone::from_lua(timezone, &Lua::new())?,
locale.to_string_lossy(), fmt_str.to_string_lossy(),
)) locale.to_string_lossy(),
)
.map_err(|()| LuaError::external("failed to parse DateTime object, invalid")))
}, },
); );
@ -138,3 +147,65 @@ impl<'lua> FromLua<'lua> for DateTime {
} }
} }
} }
impl LuaUserData for DateTimeBuilder {}
impl<'lua> FromLua<'lua> for DateTimeBuilder {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
match value {
LuaValue::Table(t) => Ok(Self::default()
.with_year(t.get("year")?)
.with_month(
(match t.get("month")? {
LuaValue::String(str) => Ok(str.to_str()?.parse::<Month>().or(Err(
LuaError::external("could not cast month string to Month"),
))?),
LuaValue::Nil => {
Err(LuaError::external("cannot find mandatory month argument"))
}
LuaValue::Number(num) => Ok(Month::try_from(num as u8).or(Err(
LuaError::external("could not cast month number to Month"),
))?),
LuaValue::Integer(int) => Ok(Month::try_from(int as u8).or(Err(
LuaError::external("could not cast month integer to Month"),
))?),
_ => Err(LuaError::external("unexpected month field type")),
})?,
)
.with_day(t.get("day")?)
.with_hour(t.get("hour")?)
.with_minute(t.get("minute")?)
.with_second(t.get("second")?)
// TODO: millisecond support
.build()),
_ => Err(LuaError::external(
"expected type table for DateTimeBuilder",
)),
}
}
}
impl<'lua> FromLua<'lua> for Timezone {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
fn num_to_enum(num: i32) -> LuaResult<Timezone> {
match num {
1 => Ok(Timezone::Utc),
2 => Ok(Timezone::Local),
_ => Err(LuaError::external("Invalid enum member!")),
}
}
match value {
LuaValue::Integer(num) => num_to_enum(num),
LuaValue::Number(num) => num_to_enum(num as i32),
LuaValue::String(str) => match str.to_str()?.to_lowercase().as_str() {
"utc" => Ok(Timezone::Utc),
"local" => Ok(Timezone::Local),
&_ => Err(LuaError::external("Invalid enum member!")),
},
_ => Err(LuaError::external(
"Invalid enum type, number or string expected",
)),
}
}
}