Skip to main content

coil_commerce/
error.rs

1use crate::identifiers::CurrencyCode;
2use crate::model::{CheckoutStatus, OrderStatus, ProductStatus};
3use coil_data::DataModelError;
4use std::error::Error;
5use std::fmt;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum CommerceModelError {
9    EmptyField {
10        field: &'static str,
11    },
12    InvalidToken {
13        field: &'static str,
14        value: String,
15    },
16    InvalidRoute {
17        field: &'static str,
18        value: String,
19    },
20    DataPlan {
21        error: DataModelError,
22    },
23    NegativeAmount {
24        field: &'static str,
25        amount_minor: i64,
26    },
27    ZeroQuantity {
28        field: &'static str,
29    },
30    AmountOverflow {
31        field: &'static str,
32    },
33    BasisPointsOutOfRange {
34        field: &'static str,
35        basis_points: u32,
36    },
37    CurrencyMismatch {
38        expected: CurrencyCode,
39        actual: CurrencyCode,
40    },
41    DuplicateVariant {
42        sku: String,
43    },
44    MissingVariant {
45        sku: String,
46    },
47    DuplicateProduct {
48        product_id: String,
49    },
50    MissingProduct {
51        product_id: String,
52    },
53    DuplicateCollection {
54        collection_id: String,
55    },
56    MissingCollection {
57        collection_id: String,
58    },
59    ProductNotSellable {
60        product_id: String,
61        status: ProductStatus,
62    },
63    MissingLine {
64        sku: String,
65    },
66    EmptyCheckout,
67    CheckoutNotReady {
68        status: CheckoutStatus,
69    },
70    InvalidStatusTransition {
71        from: CheckoutStatus,
72        to: CheckoutStatus,
73    },
74    TotalWouldBecomeNegative {
75        total_minor: i64,
76    },
77    OrderNotRefundable {
78        order_id: String,
79        status: OrderStatus,
80    },
81    RefundExceedsCaptured {
82        order_id: String,
83        captured_minor: i64,
84        refunded_minor: i64,
85        requested_minor: i64,
86    },
87    MissingModuleSetting {
88        module: String,
89        field: String,
90    },
91    InvalidModuleSetting {
92        module: String,
93        field: String,
94        reason: String,
95    },
96    UnsupportedModuleSetting {
97        module: String,
98        field: String,
99        value: String,
100    },
101}
102
103impl fmt::Display for CommerceModelError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Self::EmptyField { field } => write!(f, "`{field}` cannot be empty"),
107            Self::InvalidToken { field, value } => {
108                write!(f, "`{field}` contains an invalid token `{value}`")
109            }
110            Self::InvalidRoute { field, value } => {
111                write!(f, "`{field}` must start with `/`, got `{value}`")
112            }
113            Self::DataPlan { error } => write!(f, "{error}"),
114            Self::NegativeAmount {
115                field,
116                amount_minor,
117            } => write!(f, "`{field}` cannot be negative, got `{amount_minor}`"),
118            Self::ZeroQuantity { field } => write!(f, "`{field}` must be greater than zero"),
119            Self::AmountOverflow { field } => {
120                write!(f, "arithmetic overflow while calculating `{field}`")
121            }
122            Self::BasisPointsOutOfRange {
123                field,
124                basis_points,
125            } => {
126                write!(
127                    f,
128                    "`{field}` must be between 0 and 10000, got `{basis_points}`"
129                )
130            }
131            Self::CurrencyMismatch { expected, actual } => {
132                write!(
133                    f,
134                    "currency mismatch: expected `{expected}`, got `{actual}`"
135                )
136            }
137            Self::DuplicateVariant { sku } => write!(f, "variant `{sku}` is duplicated"),
138            Self::MissingVariant { sku } => write!(f, "variant `{sku}` was not found"),
139            Self::DuplicateProduct { product_id } => {
140                write!(f, "catalog product `{product_id}` is duplicated")
141            }
142            Self::MissingProduct { product_id } => {
143                write!(f, "catalog product `{product_id}` was not found")
144            }
145            Self::DuplicateCollection { collection_id } => {
146                write!(f, "catalog collection `{collection_id}` is duplicated")
147            }
148            Self::MissingCollection { collection_id } => {
149                write!(f, "catalog collection `{collection_id}` was not found")
150            }
151            Self::ProductNotSellable { product_id, status } => {
152                write!(f, "product `{product_id}` is not sellable while `{status}`")
153            }
154            Self::MissingLine { sku } => write!(f, "checkout line for `{sku}` was not found"),
155            Self::EmptyCheckout => f.write_str("checkout must contain at least one line"),
156            Self::CheckoutNotReady { status } => {
157                write!(f, "checkout cannot advance from status `{status}`")
158            }
159            Self::InvalidStatusTransition { from, to } => {
160                write!(f, "cannot transition checkout from `{from}` to `{to}`")
161            }
162            Self::TotalWouldBecomeNegative { total_minor } => {
163                write!(f, "priced total would become negative: `{total_minor}`")
164            }
165            Self::OrderNotRefundable { order_id, status } => {
166                write!(f, "order `{order_id}` cannot be refunded while `{status}`")
167            }
168            Self::RefundExceedsCaptured {
169                order_id,
170                captured_minor,
171                refunded_minor,
172                requested_minor,
173            } => write!(
174                f,
175                "refund for order `{order_id}` exceeds captured amount: captured={captured_minor} refunded={refunded_minor} requested={requested_minor}"
176            ),
177            Self::MissingModuleSetting { module, field } => {
178                write!(f, "module `{module}` requires setting `{field}`")
179            }
180            Self::InvalidModuleSetting {
181                module,
182                field,
183                reason,
184            } => {
185                write!(
186                    f,
187                    "module `{module}` has invalid setting `{field}`: {reason}"
188                )
189            }
190            Self::UnsupportedModuleSetting {
191                module,
192                field,
193                value,
194            } => {
195                write!(
196                    f,
197                    "module `{module}` does not support `{field} = {value}` in the current checkout contract"
198                )
199            }
200        }
201    }
202}
203
204impl Error for CommerceModelError {}
205
206impl From<DataModelError> for CommerceModelError {
207    fn from(error: DataModelError) -> Self {
208        Self::DataPlan { error }
209    }
210}