1use core::convert::Infallible;
6use core::{fmt, ops};
7
8#[cfg(feature = "arbitrary")]
9use arbitrary::{Arbitrary, Unstructured};
10use NumOpResult as R;
11
12use crate::{Amount, FeeRate, SignedAmount, Weight};
13
14#[rustfmt::skip] #[doc(no_inline)]
16pub use self::error::NumOpError;
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
88#[must_use]
89pub enum NumOpResult<T> {
90 Valid(T),
92 Error(NumOpError),
94}
95
96impl<T> NumOpResult<T> {
97 #[inline]
100 pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> NumOpResult<U> {
101 match self {
102 Self::Valid(t) => NumOpResult::Valid(op(t)),
103 Self::Error(e) => NumOpResult::Error(e),
104 }
105 }
106}
107
108impl<T: fmt::Debug> NumOpResult<T> {
109 #[inline]
115 #[track_caller]
116 pub fn expect(self, msg: &str) -> T {
117 match self {
118 Self::Valid(x) => x,
119 Self::Error(_) => panic!("{}", msg),
120 }
121 }
122
123 #[inline]
129 #[track_caller]
130 pub fn unwrap(self) -> T {
131 match self {
132 Self::Valid(x) => x,
133 Self::Error(e) => panic!("tried to unwrap an invalid numeric result: {:?}", e),
134 }
135 }
136
137 #[inline]
143 #[track_caller]
144 pub fn unwrap_err(self) -> NumOpError {
145 match self {
146 Self::Error(e) => e,
147 Self::Valid(a) => panic!("tried to unwrap a valid numeric result: {:?}", a),
148 }
149 }
150
151 #[inline]
156 #[track_caller]
157 pub fn unwrap_or(self, default: T) -> T {
158 match self {
159 Self::Valid(x) => x,
160 Self::Error(_) => default,
161 }
162 }
163
164 #[inline]
166 #[track_caller]
167 pub fn unwrap_or_else<F>(self, f: F) -> T
168 where
169 F: FnOnce() -> T,
170 {
171 match self {
172 Self::Valid(x) => x,
173 Self::Error(_) => f(),
174 }
175 }
176
177 #[inline]
179 pub fn ok(self) -> Option<T> {
180 match self {
181 Self::Valid(x) => Some(x),
182 Self::Error(_) => None,
183 }
184 }
185
186 #[inline]
188 #[allow(clippy::missing_errors_doc)]
189 pub fn into_result(self) -> Result<T, NumOpError> {
190 match self {
191 Self::Valid(x) => Ok(x),
192 Self::Error(e) => Err(e),
193 }
194 }
195
196 #[inline]
198 pub fn and_then<F>(self, op: F) -> Self
199 where
200 F: FnOnce(T) -> Self,
201 {
202 match self {
203 Self::Valid(x) => op(x),
204 Self::Error(e) => Self::Error(e),
205 }
206 }
207
208 #[inline]
210 pub fn is_valid(&self) -> bool {
211 match self {
212 Self::Valid(_) => true,
213 Self::Error(_) => false,
214 }
215 }
216
217 #[inline]
219 pub fn is_error(&self) -> bool { !self.is_valid() }
220}
221
222crate::internal_macros::impl_op_for_references! {
224 impl<T> ops::Add<NumOpResult<T>> for NumOpResult<T>
225 where
226 (T: Copy + ops::Add<Output = NumOpResult<T>>)
227 {
228 type Output = NumOpResult<T>;
229
230 fn add(self, rhs: Self) -> Self::Output {
231 match (self, rhs) {
232 (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
233 (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)),
234 }
235 }
236 }
237
238 impl<T> ops::Add<T> for NumOpResult<T>
239 where
240 (T: Copy + ops::Add<NumOpResult<T>, Output = NumOpResult<T>>)
241 {
242 type Output = NumOpResult<T>;
243
244 fn add(self, rhs: T) -> Self::Output { rhs + self }
245 }
246
247 impl<T> ops::Sub<NumOpResult<T>> for NumOpResult<T>
248 where
249 (T: Copy + ops::Sub<Output = NumOpResult<T>>)
250 {
251 type Output = NumOpResult<T>;
252
253 fn sub(self, rhs: Self) -> Self::Output {
254 match (self, rhs) {
255 (R::Valid(lhs), R::Valid(rhs)) => lhs - rhs,
256 (_, _) => R::Error(NumOpError::while_doing(MathOp::Sub)),
257 }
258 }
259 }
260
261 impl<T> ops::Sub<T> for NumOpResult<T>
262 where
263 (T: Copy + ops::Sub<Output = NumOpResult<T>>)
264 {
265 type Output = NumOpResult<T>;
266
267 fn sub(self, rhs: T) -> Self::Output {
268 match self {
269 R::Valid(amount) => amount - rhs,
270 R::Error(_) => self,
271 }
272 }
273 }
274}
275
276impl<T: ops::AddAssign> ops::AddAssign<T> for NumOpResult<T> {
278 #[inline]
279 fn add_assign(&mut self, rhs: T) {
280 if let Self::Valid(ref mut lhs) = self {
281 *lhs += rhs;
282 }
283 }
284}
285
286impl<T: ops::AddAssign + Copy> ops::AddAssign<Self> for NumOpResult<T> {
287 #[inline]
288 fn add_assign(&mut self, rhs: Self) {
289 match (&self, rhs) {
290 (Self::Valid(_), Self::Valid(rhs)) => *self += rhs,
291 (_, _) => *self = Self::Error(NumOpError::while_doing(MathOp::Add)),
292 }
293 }
294}
295
296impl<T: ops::SubAssign> ops::SubAssign<T> for NumOpResult<T> {
298 #[inline]
299 fn sub_assign(&mut self, rhs: T) {
300 if let Self::Valid(ref mut lhs) = self {
301 *lhs -= rhs;
302 }
303 }
304}
305
306impl<T: ops::SubAssign + Copy> ops::SubAssign<Self> for NumOpResult<T> {
307 #[inline]
308 fn sub_assign(&mut self, rhs: Self) {
309 match (&self, rhs) {
310 (Self::Valid(_), Self::Valid(rhs)) => *self -= rhs,
311 (_, _) => *self = Self::Error(NumOpError::while_doing(MathOp::Sub)),
312 }
313 }
314}
315
316pub(crate) trait OptionExt<T> {
317 fn valid_or_error(self, op: MathOp) -> NumOpResult<T>;
318}
319
320macro_rules! impl_opt_ext {
321 ($($ty:ident),* $(,)?) => {
322 $(
323 impl OptionExt<$ty> for Option<$ty> {
324 #[inline]
325 fn valid_or_error(self, op: MathOp) -> NumOpResult<$ty> {
326 match self {
327 Some(amount) => R::Valid(amount),
328 None => R::Error(NumOpError(op)),
329 }
330 }
331 }
332 )*
333 }
334}
335impl_opt_ext!(Amount, SignedAmount, u64, i64, FeeRate, Weight);
336
337#[derive(Debug, Copy, Clone, PartialEq, Eq)]
339#[non_exhaustive]
340pub enum MathOp {
341 Add,
343 Sub,
345 Mul,
347 Div,
349 Rem,
351 Neg,
353 #[doc(hidden)]
356 _DoNotUse(Infallible),
357}
358
359impl MathOp {
360 pub fn is_overflow(self) -> bool {
362 matches!(self, Self::Add | Self::Sub | Self::Mul | Self::Neg)
363 }
364
365 #[inline]
367 pub fn is_div_by_zero(self) -> bool { !self.is_overflow() }
368
369 #[inline]
371 pub fn is_addition(self) -> bool { self == Self::Add }
372
373 #[inline]
375 pub fn is_subtraction(self) -> bool { self == Self::Sub }
376
377 #[inline]
379 pub fn is_multiplication(self) -> bool { self == Self::Mul }
380
381 #[inline]
383 pub fn is_negation(self) -> bool { self == Self::Neg }
384}
385
386impl fmt::Display for MathOp {
387 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
388 match *self {
389 Self::Add => write!(f, "add"),
390 Self::Sub => write!(f, "sub"),
391 Self::Mul => write!(f, "mul"),
392 Self::Div => write!(f, "div"),
393 Self::Rem => write!(f, "rem"),
394 Self::Neg => write!(f, "neg"),
395 Self::_DoNotUse(infallible) => match infallible {},
396 }
397 }
398}
399
400pub mod error {
402 use core::convert::Infallible;
403 use core::fmt;
404
405 use super::MathOp;
406
407 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
409 #[non_exhaustive]
410 pub struct NumOpError(pub(super) MathOp);
411
412 impl NumOpError {
413 #[inline]
415 pub(crate) const fn while_doing(op: MathOp) -> Self { Self(op) }
416
417 #[inline]
419 pub fn is_overflow(self) -> bool { self.0.is_overflow() }
420
421 #[inline]
423 pub fn is_div_by_zero(self) -> bool { self.0.is_div_by_zero() }
424
425 #[inline]
427 pub fn operation(self) -> MathOp { self.0 }
428 }
429
430 impl From<Infallible> for NumOpError {
431 fn from(never: Infallible) -> Self { match never {} }
432 }
433
434 impl fmt::Display for NumOpError {
435 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
436 write!(f, "math operation '{}' gave an invalid numeric result", self.operation())
437 }
438 }
439
440 #[cfg(feature = "std")]
441 impl std::error::Error for NumOpError {
442 #[inline]
443 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
444 }
445}
446
447#[cfg(feature = "arbitrary")]
448impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for NumOpResult<T> {
449 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
450 match bool::arbitrary(u)? {
451 true => Ok(Self::Valid(T::arbitrary(u)?)),
452 false => Ok(Self::Error(NumOpError(MathOp::arbitrary(u)?))),
453 }
454 }
455}
456
457#[cfg(feature = "arbitrary")]
458impl<'a> Arbitrary<'a> for MathOp {
459 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
460 let choice = u.int_in_range(0..=5)?;
461 match choice {
462 0 => Ok(Self::Add),
463 1 => Ok(Self::Sub),
464 2 => Ok(Self::Mul),
465 3 => Ok(Self::Div),
466 4 => Ok(Self::Rem),
467 _ => Ok(Self::Neg),
468 }
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 #[cfg(feature = "alloc")]
475 use alloc::string::ToString;
476 #[cfg(feature = "std")]
477 use std::error::Error;
478
479 use crate::result::{MathOp, NumOpError, NumOpResult};
480 use crate::{Amount, FeeRate, Weight};
481
482 #[test]
483 fn mathop_predicates() {
484 assert!(MathOp::Add.is_overflow());
485 assert!(MathOp::Sub.is_overflow());
486 assert!(MathOp::Mul.is_overflow());
487 assert!(MathOp::Neg.is_overflow());
488 assert!(!MathOp::Div.is_overflow());
489 assert!(!MathOp::Rem.is_overflow());
490
491 assert!(MathOp::Div.is_div_by_zero());
492 assert!(MathOp::Rem.is_div_by_zero());
493 assert!(!MathOp::Add.is_div_by_zero());
494
495 assert!(MathOp::Add.is_addition());
496 assert!(!MathOp::Sub.is_addition());
497
498 assert!(MathOp::Sub.is_subtraction());
499 assert!(!MathOp::Add.is_subtraction());
500
501 assert!(MathOp::Mul.is_multiplication());
502 assert!(!MathOp::Div.is_multiplication());
503
504 assert!(MathOp::Neg.is_negation());
505 assert!(!MathOp::Add.is_negation());
506 }
507
508 #[test]
509 fn mathop_map() {
510 let res = NumOpResult::Valid(Amount::from_sat_u32(100));
512 let new_value = res.map(|val| (val / FeeRate::from_sat_per_kwu(10)).unwrap());
513 assert_eq!(new_value, NumOpResult::Valid(Weight::from_wu(10_000)));
514
515 let res = NumOpResult::<Weight>::Error(NumOpError::while_doing(MathOp::Add));
517 let res_err = res.map(|_| {
518 panic!("map should not evaluate for wrapped error values");
519 });
520 assert_eq!(res_err, res);
521 }
522
523 #[test]
524 fn mathop_expect() {
525 let amounts = [
526 Amount::from_sat_u32(0),
527 Amount::from_sat_u32(10_000_000),
528 Amount::from_sat_u32(u32::MAX),
529 ];
530 for amount in amounts {
531 assert_eq!(
532 NumOpResult::Valid(amount).expect("unreachable"),
533 NumOpResult::Valid(amount).unwrap(),
534 );
535 assert_eq!(NumOpResult::Valid(amount).expect("unreachable"), amount);
536 }
537 }
538
539 #[test]
540 #[should_panic(expected = "test error message")]
541 fn mathop_expect_panics_on_error() {
542 NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add))
543 .expect("test error message");
544 }
545
546 #[test]
547 fn mathop_unwrap() {
548 let amounts = [
549 Amount::from_sat_u32(0),
550 Amount::from_sat_u32(10_000_000),
551 Amount::from_sat_u32(u32::MAX),
552 ];
553 for amount in amounts {
554 assert_eq!(NumOpResult::Valid(amount).unwrap(), amount);
555 }
556 let weights = [Weight::from_wu(0), Weight::from_wu(16_384_000), Weight::from_wu(u64::MAX)];
557 for weight in weights {
558 assert_eq!(NumOpResult::Valid(weight).unwrap(), weight);
559 }
560 }
561
562 #[test]
563 #[should_panic(expected = "")]
564 fn mathop_unwrap_panics_on_err() {
565 NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add)).unwrap();
566 }
567
568 #[test]
569 fn mathop_unwrap_err() {
570 let errs = [
571 NumOpError::while_doing(MathOp::Add),
572 NumOpError::while_doing(MathOp::Sub),
573 NumOpError::while_doing(MathOp::Mul),
574 NumOpError::while_doing(MathOp::Div),
575 NumOpError::while_doing(MathOp::Neg),
576 NumOpError::while_doing(MathOp::Rem),
577 ];
578 for err in errs {
579 assert_eq!(NumOpResult::<Amount>::Error(err).unwrap_err(), err);
580 }
581 }
582
583 #[test]
584 #[should_panic(expected = "")]
585 fn mathop_unwrap_err_panics_on_valid() {
586 let value = Amount::from_sat_u32(150);
587 NumOpResult::<Amount>::Valid(value).unwrap_err();
588 }
589
590 #[test]
591 fn mathop_unwrap_or() {
592 let base_amount = Amount::from_sat_u32(100);
593
594 let res = NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add));
596 let res_default = res.unwrap_or(base_amount);
597 assert_eq!(res_default, base_amount);
598
599 let res = NumOpResult::Valid(base_amount);
601 let new_amount = res.unwrap_or(Amount::from_sat_u32(50));
602 assert_eq!(new_amount, base_amount);
603 }
604
605 #[test]
606 fn mathop_unwrap_or_else() {
607 let base_amount = Amount::from_sat_u32(100);
608
609 let res = NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add));
611 let res_default = res.unwrap_or_else(|| base_amount);
612 assert_eq!(res_default, base_amount);
613
614 let res = NumOpResult::<Amount>::Valid(base_amount);
616 let new_amount = res.unwrap_or_else(|| {
617 panic!("unwrap_or_else should not evaluate for wrapped valid values");
618 });
619 assert_eq!(new_amount, base_amount);
620 }
621
622 #[test]
623 fn mathop_ok() {
624 let amt = Amount::from_sat_u32(150);
625 assert_eq!(NumOpResult::Valid(amt).ok(), Some(amt));
626
627 let err = NumOpError::while_doing(MathOp::Add);
628 assert_eq!(NumOpResult::<Amount>::Error(err).ok(), None);
629 }
630
631 #[test]
632 fn mathop_and_then() {
633 let res = NumOpResult::Valid(Amount::from_sat_u32(100));
635 let new_value = res.and_then(|val| val + Amount::from_sat_u32(50));
636 assert_eq!(new_value, NumOpResult::Valid(Amount::from_sat_u32(150)));
637
638 let res = NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add));
640 let res_err = res.and_then(|_| {
641 panic!("and_then should not evaluate for wrapped error values");
642 });
643 assert_eq!(res_err, res);
644 }
645
646 #[test]
647 #[cfg(feature = "alloc")]
648 fn error_display_is_non_empty() {
649 let e = (Amount::MAX + Amount::MAX).unwrap_err();
651 assert!(!e.to_string().is_empty());
652 #[cfg(feature = "std")]
653 assert!(e.source().is_none());
654 }
655}