use thiserror::Error;
#[derive(Debug, Clone, Error)]
#[error("{file}:{line}: {message}")]
pub struct QlError {
message: String,
file: &'static str,
line: u32,
}
impl QlError {
pub fn new(message: impl Into<String>, file: &'static str, line: u32) -> Self {
QlError {
message: message.into(),
file,
line,
}
}
pub fn message(&self) -> &str {
&self.message
}
}
pub type QlResult<T> = Result<T, QlError>;
#[macro_export]
macro_rules! fail {
($($arg:tt)*) => {
return ::core::result::Result::Err($crate::errors::QlError::new(
::std::format!($($arg)*),
::core::file!(),
::core::line!(),
))
};
}
#[macro_export]
macro_rules! require {
($cond:expr, $($arg:tt)*) => {
if !($cond) {
$crate::fail!($($arg)*);
}
};
}
#[macro_export]
macro_rules! ensure {
($cond:expr, $($arg:tt)*) => {
if !($cond) {
$crate::fail!($($arg)*);
}
};
}
#[macro_export]
macro_rules! assert_ql {
($cond:expr, $($arg:tt)*) => {
if !($cond) {
$crate::fail!($($arg)*);
}
};
}
#[cfg(test)]
mod tests {
use super::*;
fn fails_with(value: i32) -> QlResult<i32> {
fail!("bad value {value}");
}
fn requires_positive(value: i32) -> QlResult<i32> {
require!(value > 0, "value must be positive, got {value}");
Ok(value)
}
fn ensures_positive(value: i32) -> QlResult<i32> {
ensure!(value > 0, "post-condition violated: {value}");
Ok(value)
}
#[test]
fn fail_returns_err_with_message() {
let err = fails_with(7).unwrap_err();
assert_eq!(err.message(), "bad value 7");
}
#[test]
fn require_passes_and_fails() {
assert_eq!(requires_positive(3).unwrap(), 3);
let err = requires_positive(-1).unwrap_err();
assert_eq!(err.message(), "value must be positive, got -1");
}
#[test]
fn ensure_returns_err_on_violation() {
assert!(ensures_positive(1).is_ok());
assert!(ensures_positive(0).is_err());
}
#[test]
fn display_includes_location() {
let err = requires_positive(-1).unwrap_err();
let text = err.to_string();
assert!(text.contains("errors.rs"));
assert!(text.contains("value must be positive"));
}
}