blob: d0bed804b70dde4ebb04e2beabc2790ada7031d1 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Monadic mayfail notation for chained error handling
#![cfg_attr(feature = "mesalock_sgx", no_std)]
#[cfg(feature = "mesalock_sgx")]
extern crate sgx_tstd as std;
/// maiyfail use duck typing.
///
/// Syntax:
/// `(instr)*; ret expr`
///
/// instr can be:
///
/// * `pattern =<< expression`: unbox the value as `pattern` from `expression`.
/// `expression` can be converted to a monadic value of type `M<T> through a
/// `to_mt_result` method. `M<T>` should support a monadic bind operation
/// `and_then<U, F>(self, f: F)-> M<U> where F: FnOnce(T)->M<U>`. `expression`s
/// in the same mayfail! block should be convertable to the same `M<T>`.
///
/// * `let pattern = expression`: assign expression to pattern, as
/// normal rust let.
///
/// The mayfial module provides the `MayfailNop` trait to trivially convert
/// `std::option::Option<T>` and `std::result::Result<T, E>` to `mesatee_core::Result<T>`.
/// Users can define their own traits to implement `to_mt_result` for types
/// appearing in `mayfail!` blocks.
///
/// Example:
///
/// ```
/// fn main() {
/// use mesatee_core::mayfail::MayfailNop;
///
/// let result_x: Option<i32> = Some(1);
/// let ret = mayfail! {
/// let n = 3;
/// x =<< result_x;
/// y =<< Some(n);
/// ret x + y
/// };
/// assert!(ret.is_ok() && ret.unwrap() == 4);
/// }
/// ```
macro_rules! mayfail {
(let $p: pat = $e: expr ; $($t: tt)*) => (
{ let $p = $e; mayfail! { $($t)* } }
);
(let $p: ident : $ty: ty = $e: expr; $($t: tt)*) => (
{ let $p: $ty = $e; mayfail! { $($t)* } }
);
($p: pat =<< $e: expr; $($t: tt)*) => (
($e).to_mt_result(file!(), line!()).and_then(move |$p| mayfail! { $($t)* })
);
($p: ident : $ty: ty =<< $e: expr ; $($t: tt)*) => (
($e).to_mt_result(file!(), line!()).and_then(move |$p : $ty| mayfail! { $($t)* })
);
(ret $f: expr) => (Ok($f))
}
use mesatee_core::Error;
use mesatee_core::ErrorKind;
use mesatee_core::Result;
pub trait MayfailNop<T> {
fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T>;
}
impl<T> MayfailNop<T> for Option<T> {
#[inline]
fn to_mt_result(self: Self, _file: &'static str, _line: u32) -> Result<T> {
self.ok_or_else(|| Error::from(ErrorKind::MissingValue))
}
}
impl<T, E> MayfailNop<T> for std::result::Result<T, E> {
#[inline]
fn to_mt_result(self: Self, _file: &'static str, _line: u32) -> Result<T> {
self.map_err(|_| Error::unknown())
}
}
pub trait MayfailTrace<T> {
fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T>;
}
impl<T> MayfailTrace<T> for Option<T> {
#[inline]
fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T> {
self.ok_or_else(|| {
trace!("error at {}:{}", file, line);
Error::from(ErrorKind::MissingValue)
})
}
}
impl<T, E> MayfailTrace<T> for std::result::Result<T, E> {
#[inline]
default fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T> {
self.map_err(|_| {
trace!("error at {}:{}", file, line);
Error::unknown()
})
}
}
impl<T> MayfailTrace<T> for Result<T> {
#[inline]
fn to_mt_result(self: Self, _file: &'static str, _line: u32) -> Result<T> {
self
}
}
#[cfg(test)]
mod test {
use crate::ErrorKind;
#[test]
fn test_mayfail_option() {
let ret = mayfail! {
let n = 3;
x =<< Some(1);
y =<< Some(n);
ret x + y
};
assert!(ret.is_ok() && ret.unwrap() == 4);
let ret = mayfail! {
let n: Option<i32> = None;
x =<< Some(1i32);
y =<< n;
ret x + y
};
assert!(ret.is_err());
assert_eq!(ret.unwrap_err().kind(), ErrorKind::MissingValue);
}
#[derive(Debug)]
struct SomeThirdPartyError;
impl std::fmt::Display for SomeThirdPartyError {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl std::error::Error for SomeThirdPartyError {
fn description(&self) -> &str {
"test"
}
}
type TestResult<T> = std::result::Result<T, SomeThirdPartyError>;
use super::MayfailNop;
#[test]
fn test_mayfail_result() {
let result_x: TestResult<i32> = Ok(1);
let ret = mayfail! {
let n = 3;
x =<< result_x;
y =<< TestResult::<i32>::Ok(n);
ret x + y
};
assert!(ret.is_ok() && ret.unwrap() == 4);
let result_x: TestResult<i32> = Ok(1);
let ret = mayfail! {
let err = SomeThirdPartyError;
x =<< result_x;
y =<< TestResult::<i32>::Err(err);
ret x + y
};
assert!(ret.is_err());
assert_eq!(ret.unwrap_err().kind(), ErrorKind::Unknown);
}
#[test]
fn test_mayfail_mix() {
let result_x: TestResult<i32> = Ok(1);
let ret = mayfail! {
x =<< result_x;
y =<< Option::<i32>::None;
ret x + y
};
assert!(ret.is_err());
assert_eq!(ret.unwrap_err().kind(), ErrorKind::MissingValue);
}
}