#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
#[error("Incorrect number of parameters passed to function.")]
ParamCount,
#[error("Incorrect parameter value.")]
Value,
#[error("Incorrect parameter type.")]
Type,
#[error("The input value is invalid.")]
InvalidInput,
#[error("The input value is too long.")]
TooLong,
#[error("The input value can't be parsed as a valid date/time ({format}).")]
ParseDate {
format: String,
},
#[error("Operation not supported.")]
NotSupported,
#[error("Object no longer exists.")]
BadObject,
#[error("The second parameter can't be converted to a Date.")]
NotADate,
#[error("The second parameter is an invalid Date.")]
InvalidDate,
#[error("No event handler")]
NoEventHandler,
#[error("AFDate_KeystrokeEx's parameter size not correct")]
DateKeystrokeArity,
}
pub const ALERT_ICON_STATUS: u8 = 3;
pub const ALERT_BUTTON_OK: u8 = 0;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlertMessage {
InvalidInput,
TooLong,
ParseDate {
format: String,
},
RangeBetween {
min: String,
max: String,
},
RangeGreater {
min: String,
},
RangeLess {
max: String,
},
}
impl core::fmt::Display for AlertMessage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidInput => f.write_str("The input value is invalid."),
Self::TooLong => f.write_str("The input value is too long."),
Self::ParseDate { format } => write!(
f,
"The input value can't be parsed as a valid date/time ({format})."
),
Self::RangeBetween { min, max } => write!(
f,
"The input value must be greater than or equal to {min} and less than or equal to {max}."
),
Self::RangeGreater { min } => {
write!(f, "The input value must be greater than or equal to {min}.")
}
Self::RangeLess { max } => {
write!(f, "The input value must be less than or equal to {max}.")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AfAlert {
pub caller: &'static str,
pub message: AlertMessage,
pub icon: u8,
pub button: u8,
}
impl AfAlert {
#[must_use]
pub fn new(caller: &'static str, message: AlertMessage) -> Self {
Self {
caller,
message,
icon: ALERT_ICON_STATUS,
button: ALERT_BUTTON_OK,
}
}
}
impl core::fmt::Display for AfAlert {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}[icon={},type={}]: {}",
self.caller, self.icon, self.button, self.message
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AfColorSpace {
Transparent,
Gray,
Rgb,
Cmyk,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AfColor {
pub space: AfColorSpace,
pub components: [f32; 4],
}
impl AfColor {
pub const RED: Self = Self {
space: AfColorSpace::Rgb,
components: [1.0, 0.0, 0.0, 0.0],
};
pub const BLACK: Self = Self {
space: AfColorSpace::Rgb,
components: [0.0, 0.0, 0.0, 0.0],
};
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct AfEffects {
pub alerts: Vec<AfAlert>,
pub text_color: Option<AfColor>,
}
impl AfEffects {
#[must_use]
pub fn none() -> Self {
Self::default()
}
#[must_use]
pub fn alert(caller: &'static str, message: AlertMessage) -> Self {
Self {
alerts: vec![AfAlert::new(caller, message)],
text_color: None,
}
}
#[must_use]
pub fn color(color: AfColor) -> Self {
Self {
alerts: Vec::new(),
text_color: Some(color),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.alerts.is_empty() && self.text_color.is_none()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display_is_the_oracle_message_table() {
let cases: &[(Error, &str)] = &[
(
Error::ParamCount,
"Incorrect number of parameters passed to function.",
),
(Error::Value, "Incorrect parameter value."),
(Error::Type, "Incorrect parameter type."),
(Error::InvalidInput, "The input value is invalid."),
(Error::TooLong, "The input value is too long."),
(Error::NotSupported, "Operation not supported."),
(Error::BadObject, "Object no longer exists."),
(
Error::NotADate,
"The second parameter can't be converted to a Date.",
),
(
Error::InvalidDate,
"The second parameter is an invalid Date.",
),
];
for (err, expected) in cases {
assert_eq!(err.to_string(), *expected, "{err:?}");
}
}
#[test]
fn the_two_bare_literals_have_no_trailing_period() {
assert_eq!(Error::NoEventHandler.to_string(), "No event handler");
assert_eq!(
Error::DateKeystrokeArity.to_string(),
"AFDate_KeystrokeEx's parameter size not correct"
);
}
#[test]
fn parse_date_interpolates_the_format_verbatim() {
let e = Error::ParseDate {
format: "blooey".into(),
};
assert_eq!(
e.to_string(),
"The input value can't be parsed as a valid date/time (blooey)."
);
}
#[test]
fn alert_display_is_the_transcript_notification_line() {
let cases: &[(AfAlert, &str)] = &[
(
AfAlert::new("AFNumber_Keystroke", AlertMessage::InvalidInput),
"AFNumber_Keystroke[icon=3,type=0]: The input value is invalid.",
),
(
AfAlert::new("AFSpecial_KeystrokeEx", AlertMessage::TooLong),
"AFSpecial_KeystrokeEx[icon=3,type=0]: The input value is too long.",
),
(
AfAlert::new(
"AFDate_KeystrokeEx",
AlertMessage::ParseDate {
format: "m/d".into(),
},
),
"AFDate_KeystrokeEx[icon=3,type=0]: The input value can't be parsed as a valid date/time (m/d).",
),
(
AfAlert::new(
"AFRange_Validate",
AlertMessage::RangeBetween {
min: "2".into(),
max: "4".into(),
},
),
"AFRange_Validate[icon=3,type=0]: The input value must be greater than or equal to 2 and less than or equal to 4.",
),
(
AfAlert::new(
"AFRange_Validate",
AlertMessage::RangeGreater { min: "2".into() },
),
"AFRange_Validate[icon=3,type=0]: The input value must be greater than or equal to 2.",
),
(
AfAlert::new(
"AFRange_Validate",
AlertMessage::RangeLess { max: "4".into() },
),
"AFRange_Validate[icon=3,type=0]: The input value must be less than or equal to 4.",
),
];
for (alert, expected) in cases {
assert_eq!(alert.to_string(), *expected);
}
}
}