use derive_more::From;
use orion_error::{
print_error, ContextRecord, ErrorCode, ErrorConv, ErrorWith, OperationContext, RedactPolicy,
RenderMode, StructError, UvsReason,
};
use std::sync::atomic::Ordering;
use thiserror::Error;
#[derive(Debug, PartialEq, Clone, Error, From)]
pub enum OrderReason {
#[error("format error")]
FormatError,
#[error("insufficient funds")]
InsufficientFunds,
#[error("storage full")]
StorageFull,
#[error("user not found")]
UserNotFound,
#[error("{0}")]
Uvs(UvsReason),
}
impl ErrorCode for OrderReason {
fn error_code(&self) -> i32 {
match self {
Self::Uvs(uvs_reason) => uvs_reason.error_code(),
_ => 500,
}
}
}
#[derive(Debug, PartialEq, Clone, Error, From)]
pub enum StoreReason {
#[error("storage full")]
StorageFull,
#[error("{0}")]
Uvs(UvsReason),
}
impl ErrorCode for StoreReason {
fn error_code(&self) -> i32 {
match self {
Self::Uvs(uvs_reason) => uvs_reason.error_code(),
_ => 500,
}
}
}
#[derive(Debug, Error, PartialEq, Clone, From)]
pub enum ParseReason {
#[error("format error")]
FormatError,
#[error("{0}")]
Uvs(UvsReason),
}
impl ErrorCode for ParseReason {
fn error_code(&self) -> i32 {
match self {
ParseReason::FormatError => 400,
ParseReason::Uvs(uvs_reason) => uvs_reason.error_code(),
}
}
}
impl From<ParseReason> for OrderReason {
fn from(value: ParseReason) -> Self {
match value {
ParseReason::FormatError => Self::FormatError,
ParseReason::Uvs(uvs_reason) => Self::Uvs(uvs_reason),
}
}
}
#[derive(Debug, PartialEq, Clone, Error, From)]
pub enum UserReason {
#[error("not found")]
NotFound,
#[error("{0}")]
Uvs(UvsReason),
}
impl From<UserReason> for OrderReason {
fn from(value: UserReason) -> Self {
match value {
UserReason::NotFound => Self::UserNotFound,
UserReason::Uvs(uvs_reason) => Self::Uvs(uvs_reason),
}
}
}
impl From<StoreReason> for OrderReason {
fn from(value: StoreReason) -> Self {
match value {
StoreReason::StorageFull => Self::StorageFull,
StoreReason::Uvs(uvs_reason) => Self::Uvs(uvs_reason),
}
}
}
pub type OrderError = StructError<OrderReason>;
pub type StoreError = StructError<StoreReason>;
pub type ParseError = StructError<ParseReason>;
pub type UserError = StructError<UserReason>;
struct ExampleRedactPolicy;
impl RedactPolicy for ExampleRedactPolicy {
fn redact_key(&self, key: &str) -> bool {
matches!(key, "order" | "config.secret")
}
fn redact_value(&self, _key: Option<&str>, _value: &str) -> Option<String> {
Some("<redacted>".to_string())
}
}
pub mod storage {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Mutex,
};
use super::*;
#[derive(Clone)]
pub struct Order {
pub user_id: u32,
pub amount: f64,
}
pub static STORAGE_CAPACITY: AtomicUsize = AtomicUsize::new(2);
static ORDERS: Mutex<Vec<Order>> = Mutex::new(Vec::new());
pub fn save(order: Order) -> Result<(), StoreError> {
save_db_impl(order).map_err(|e| match e.kind() {
std::io::ErrorKind::OutOfMemory => StructError::from(StoreReason::StorageFull)
.with_detail("storage capacity exceeded")
.with_source(e),
_ => StructError::from(StoreReason::from(UvsReason::system_error()))
.with_detail("persist order failed")
.with_source(e),
})
}
fn save_db_impl(order: Order) -> Result<(), std::io::Error> {
let capacity = STORAGE_CAPACITY.load(Ordering::Relaxed);
let mut orders = ORDERS
.lock()
.map_err(|_| std::io::Error::other("Failed to lock orders mutex"))?;
if orders.len() >= capacity {
return Err(std::io::Error::new(
std::io::ErrorKind::OutOfMemory,
"Storage capacity exceeded",
));
}
orders.push(order);
Ok(())
}
}
struct OrderService;
impl OrderService {
pub fn place_order(
user_id: u32,
amount: f64,
order_txt: &str,
) -> Result<storage::Order, OrderError> {
let mut ctx = OperationContext::want("place_order");
ctx.record("order", order_txt);
ctx.record_meta("component.name", "order_service");
ctx.record_meta("config.secret", "/prod/orders/api-token");
let order = Self::parse_order(order_txt, amount)
.want("解析订单")
.with(&ctx)
.err_conv()?;
Self::validate_funds(user_id, order.amount)
.want("验证资金")
.with(&ctx)?;
let order = storage::Order { user_id, amount };
Self::save_order(order).want("保存订单").with(&ctx)
}
fn parse_order(txt: &str, amount: f64) -> Result<storage::Order, ParseError> {
if txt.is_empty() {
return Err(StructError::builder(ParseReason::FormatError)
.detail("订单文本不能为空")
.context(
OperationContext::want("parse order text")
.with_meta("config.kind", "order_txt")
.with_meta("parse.field", "order_txt"),
)
.finish());
}
if amount <= 0.0 {
return Err(StructError::builder(ParseReason::FormatError)
.detail("订单金额必须大于零")
.context(
OperationContext::want("parse order amount").with_meta("parse.field", "amount"),
)
.finish());
}
Ok(storage::Order {
user_id: 123,
amount,
})
}
fn validate_funds(user_id: u32, amount: f64) -> Result<(), OrderError> {
let balance = Self::get_balance(user_id).err_conv()?;
if balance < amount {
Err(StructError::builder(OrderReason::InsufficientFunds)
.detail(format!("当前余额:{balance},需要:{amount}"))
.finish())
} else {
Ok(())
}
}
fn get_balance(user_id: u32) -> Result<f64, UserError> {
if user_id != 123 {
Err(StructError::builder(UserReason::NotFound)
.detail(format!("uid:{user_id}"))
.finish())
} else {
Ok(500.0)
}
}
fn save_order(order: storage::Order) -> Result<storage::Order, OrderError> {
storage::save(order.clone()).err_conv()?;
Ok(order)
}
}
fn print_verbose_report(err: &OrderError) {
println!("verbose report:\n{}", err.render(RenderMode::Verbose));
println!(
"redacted verbose report:\n{}",
err.render_redacted(RenderMode::Verbose, &ExampleRedactPolicy)
);
}
fn main() {
let case1 = OrderService::place_order(123, 200.0, "");
if let Err(e) = case1 {
print_error(&e);
println!("root metadata: {:?}", e.context_metadata().as_map());
let report = e.report();
println!("report path: {:?}", report.path);
print_verbose_report(&e);
}
let case2 = OrderService::place_order(456, 200.0, "valid_order");
if let Err(e) = case2 {
print_error(&e);
}
let case3 = OrderService::place_order(123, 600.0, "valid_order");
if let Err(e) = case3 {
print_error(&e);
}
storage::STORAGE_CAPACITY.store(0, Ordering::Relaxed);
let case4 = OrderService::place_order(123, 200.0, "valid_order");
if let Err(e) = case4 {
print_error(&e);
if let Some(frame) = e.source_frames().first() {
println!("first source metadata: {:?}", frame.metadata.as_map());
}
}
let case5 = OrderService::place_order(123, 0.0, "negative_amount");
if let Err(e) = case5 {
print_error(&e);
println!("root metadata: {:?}", e.context_metadata().as_map());
}
}