From e4a65e301bda39267ff5edb4140810ec733c9670 Mon Sep 17 00:00:00 2001 From: Compey Date: Mon, 28 Aug 2023 15:58:11 +0530 Subject: [PATCH] feat: proper error handling & error propagation --- src/lune/builtins/datetime/builder.rs | 82 +++---------------- src/lune/builtins/datetime/date_time.rs | 10 +-- src/lune/builtins/datetime/mod.rs | 101 ++++++++++++++++++++---- 3 files changed, 104 insertions(+), 89 deletions(-) diff --git a/src/lune/builtins/datetime/builder.rs b/src/lune/builtins/datetime/builder.rs index eb62621..8cfc48f 100644 --- a/src/lune/builtins/datetime/builder.rs +++ b/src/lune/builtins/datetime/builder.rs @@ -1,7 +1,6 @@ use crate::lune::builtins::datetime::date_time::Timezone; use chrono::prelude::*; use chrono_locale::LocaleDate; -use mlua::prelude::*; use once_cell::sync::Lazy; #[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 { - fn num_to_enum(num: i32) -> LuaResult { - 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 { /// Builder method to set the `Year`. 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. - pub fn to_string(self, timezone: Timezone, format: Option, locale: Option) -> String + pub fn to_string( + self, + timezone: Timezone, + format: Option, + locale: Option, + ) -> Result where T: ToString, { @@ -134,7 +113,7 @@ impl DateTimeBuilder { } }); - match timezone { + Ok(match timezone { Timezone::Utc => Utc .with_ymd_and_hms( self.year, @@ -144,7 +123,8 @@ impl DateTimeBuilder { self.minute, self.second, ) - .unwrap() + .single() + .ok_or(())? .formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) .to_string(), Timezone::Local => Local @@ -156,50 +136,14 @@ impl DateTimeBuilder { self.minute, self.second, ) - .unwrap() + .single() + .ok_or(())? .formatl((*format_lazy).as_str(), (*locale_lazy).as_str()) .to_string(), - } + }) } - fn build(self) -> Self { + pub fn build(self) -> Self { self } } - -impl LuaUserData for DateTimeBuilder {} - -impl<'lua> FromLua<'lua> for DateTimeBuilder { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - 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::().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", - )), - } - } -} diff --git a/src/lune/builtins/datetime/date_time.rs b/src/lune/builtins/datetime/date_time.rs index 94c0990..1360617 100644 --- a/src/lune/builtins/datetime/date_time.rs +++ b/src/lune/builtins/datetime/date_time.rs @@ -211,10 +211,10 @@ impl DateTime { 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 - /// function could be passed to `from_local_time` to produce the original `DateTime` - /// object. - pub fn to_iso_date(&self) -> String { + /// Formats a date as a ISO 8601 date-time string, returns None if the DateTime object is invalid. + /// The value returned by this function could be passed to `from_local_time` to produce the + /// original `DateTime` object. + pub fn to_iso_date(&self) -> Result { self.to_universal_time() .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 /// 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). - pub fn format_time(&self, timezone: Timezone, fmt_str: T, locale: T) -> String + pub fn format_time(&self, timezone: Timezone, fmt_str: T, locale: T) -> Result where T: ToString, { diff --git a/src/lune/builtins/datetime/mod.rs b/src/lune/builtins/datetime/mod.rs index 1a28378..f81fa7d 100644 --- a/src/lune/builtins/datetime/mod.rs +++ b/src/lune/builtins/datetime/mod.rs @@ -1,3 +1,4 @@ +use chrono::Month; use mlua::prelude::*; pub(crate) mod builder; @@ -10,7 +11,6 @@ use self::{ use crate::lune::util::TableBuilder; // TODO: Proper error handling and stuff -// FIX: fromUnixTimestamp calculation is broken pub fn create(lua: &'static Lua) -> LuaResult { TableBuilder::new(lua)? @@ -19,11 +19,9 @@ pub fn create(lua: &'static Lua) -> LuaResult { let timestamp_cloned = timestamp.clone(); let timestamp_kind = TimestampType::from_lua(timestamp, lua)?; 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 => { - // FIXME: Remove the unwrap - // If something breaks, blame this. - let timestamp = timestamp_cloned.as_f64().unwrap(); + let timestamp = timestamp_cloned.as_f64().ok_or(LuaError::external("invalid float timestamp with millis component supplied"))?; ((((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 @@ -47,20 +45,25 @@ pub fn create(lua: &'static Lua) -> LuaResult { Ok(DateTime::from_local_time(DateTimeBuilder::from_lua(date_time, lua).ok())) })? .with_function("toLocalTime", |_, this: DateTime| { - Ok(this.to_local_time()) + Ok(DateTime::to_local_time(&this)) })? .with_function("fromIsoDate", |_, iso_date: LuaString| { 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( "formatTime", |_, (this, timezone, fmt_str, locale): (DateTime, LuaValue, LuaString, LuaString)| { - Ok(this.format_time( + Ok(DateTime::format_time( + &this, Timezone::from_lua(timezone, lua)?, fmt_str.to_string_lossy(), locale.to_string_lossy(), - )) + ).map_err(|()| LuaError::external( + "failed to parse DateTime object, invalid", + ))) }, )? .build_readonly() @@ -103,16 +106,22 @@ impl LuaUserData for DateTime { } 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( "formatTime", |_, this, (timezone, fmt_str, locale): (LuaValue, LuaString, LuaString)| { - Ok(this.format_time( - Timezone::from_lua(timezone, &Lua::new())?, - fmt_str.to_string_lossy(), - locale.to_string_lossy(), - )) + Ok(this + .format_time( + Timezone::from_lua(timezone, &Lua::new())?, + 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 { + 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::().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 { + fn num_to_enum(num: i32) -> LuaResult { + 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", + )), + } + } +}