use derive_more::From;
use orion_error::{
report::print_error, DefaultExposurePolicy, ErrorConv, ErrorWith, OperationContext, OrionError,
StructError, UvsReason,
};
#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum ParseReason {
#[orion_error(identity = "biz.order_invalid_text")]
InvalidText,
#[orion_error(identity = "biz.order_invalid_amount")]
InvalidAmount,
#[orion_error(transparent)]
Uvs(UvsReason),
}
#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum UserReason {
#[orion_error(identity = "biz.user_not_found")]
NotFound,
#[orion_error(transparent)]
Uvs(UvsReason),
}
#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum StoreReason {
#[orion_error(identity = "sys.storage_full")]
StorageFull,
#[orion_error(transparent)]
Uvs(UvsReason),
}
#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum OrderReason {
#[orion_error(identity = "biz.order_invalid")]
InvalidOrder,
#[orion_error(identity = "biz.user_not_found")]
UserNotFound,
#[orion_error(identity = "biz.insufficient_funds")]
InsufficientFunds,
#[orion_error(identity = "sys.storage_full")]
StorageFull,
#[orion_error(transparent)]
Uvs(UvsReason),
}
impl From<ParseReason> for OrderReason {
fn from(value: ParseReason) -> Self {
match value {
ParseReason::InvalidText | ParseReason::InvalidAmount => Self::InvalidOrder,
ParseReason::Uvs(reason) => Self::Uvs(reason),
}
}
}
impl From<UserReason> for OrderReason {
fn from(value: UserReason) -> Self {
match value {
UserReason::NotFound => Self::UserNotFound,
UserReason::Uvs(reason) => Self::Uvs(reason),
}
}
}
impl From<StoreReason> for OrderReason {
fn from(value: StoreReason) -> Self {
match value {
StoreReason::StorageFull => Self::StorageFull,
StoreReason::Uvs(reason) => Self::Uvs(reason),
}
}
}
type ParseError = StructError<ParseReason>;
type UserError = StructError<UserReason>;
type StoreError = StructError<StoreReason>;
type OrderError = StructError<OrderReason>;
#[derive(Debug, Clone)]
struct OrderDraft {
user_id: u32,
amount: u64,
item: String,
}
struct OrderService;
impl OrderService {
fn place_order(user_id: u32, amount: u64, raw_order: &str) -> Result<OrderDraft, OrderError> {
let mut ctx = OperationContext::doing("place_order");
ctx.record_field("user_id", user_id.to_string());
ctx.record_field("order.raw", raw_order);
ctx.record_meta("component.name", "order_service");
ctx.record_meta("trace.secret", "prod-token");
let draft = Self::parse_order(user_id, amount, raw_order)
.doing("parse order")
.with_context(&ctx)
.err_conv()?;
Self::load_user(draft.user_id)
.doing("load user")
.with_context(&ctx)
.err_conv()?;
Self::ensure_balance(draft.amount)
.doing("check balance")
.with_context(&ctx)?;
Self::save_order(&draft)
.doing("save order")
.with_context(&ctx)
.err_conv()?;
Ok(draft)
}
fn parse_order(user_id: u32, amount: u64, raw_order: &str) -> Result<OrderDraft, ParseError> {
if raw_order.trim().is_empty() {
return Err(StructError::from(ParseReason::InvalidText)
.with_detail("order text must not be empty"));
}
if amount == 0 {
return Err(StructError::from(ParseReason::InvalidAmount)
.with_detail("amount must be greater than zero"));
}
Ok(OrderDraft {
user_id,
amount,
item: raw_order.trim().to_string(),
})
}
fn load_user(user_id: u32) -> Result<(), UserError> {
if user_id == 42 {
Ok(())
} else {
Err(StructError::from(UserReason::NotFound)
.with_detail(format!("user {user_id} does not exist")))
}
}
fn ensure_balance(amount: u64) -> Result<(), OrderError> {
let balance = 300;
if amount > balance {
Err(StructError::from(OrderReason::InsufficientFunds)
.with_detail(format!("balance={balance}, required={amount}")))
} else {
Ok(())
}
}
fn save_order(draft: &OrderDraft) -> Result<(), StoreError> {
persist_order(draft.item.as_str())
}
}
fn persist_order(item: &str) -> Result<(), StoreError> {
write_impl(item).map_err(|err| match err.kind() {
std::io::ErrorKind::OutOfMemory => StructError::from(StoreReason::StorageFull)
.with_detail("storage quota exceeded")
.with_source(err),
_ => StructError::from(StoreReason::from(UvsReason::system_error()))
.with_detail("write order record failed")
.with_source(err),
})
}
fn write_impl(item: &str) -> Result<(), std::io::Error> {
if item == "overflow" {
return Err(std::io::Error::new(
std::io::ErrorKind::OutOfMemory,
"storage full",
));
}
Ok(())
}
fn print_protocol_views(err: &OrderError) {
print_error(err);
println!();
let exposure_policy = DefaultExposurePolicy;
println!("{}", err.exposure_snapshot(&exposure_policy).render_user_debug());
}
fn run_case(name: &str, user_id: u32, amount: u64, raw_order: &str) {
println!("\n== {name} ==");
match OrderService::place_order(user_id, amount, raw_order) {
Ok(order) => println!(
"created order: user={} amount={}",
order.user_id, order.amount
),
Err(err) => print_protocol_views(&err),
}
}
fn main() {
run_case("invalid input", 42, 100, "");
run_case("user missing", 7, 100, "coffee");
run_case("insufficient funds", 42, 500, "coffee");
run_case("storage full", 42, 100, "overflow");
}