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)> {
444 let Self(_) = self;
445 None
446 }
447 }
448}
449
450#[cfg(feature = "arbitrary")]
451impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for NumOpResult<T> {
452 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
453 match bool::arbitrary(u)? {
454 true => Ok(Self::Valid(T::arbitrary(u)?)),
455 false => Ok(Self::Error(NumOpError(MathOp::arbitrary(u)?))),
456 }
457 }
458}
459
460#[cfg(feature = "arbitrary")]
461impl<'a> Arbitrary<'a> for MathOp {
462 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
463 let choice = u.int_in_range(0..=5)?;
464 match choice {
465 0 => Ok(Self::Add),
466 1 => Ok(Self::Sub),
467 2 => Ok(Self::Mul),
468 3 => Ok(Self::Div),
469 4 => Ok(Self::Rem),
470 _ => Ok(Self::Neg),
471 }
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 #[cfg(feature = "alloc")]
478 use alloc::string::ToString;
479 #[cfg(feature = "std")]
480 use std::error::Error;
481
482 use crate::result::{MathOp, NumOpError, NumOpResult};
483 use crate::{Amount, FeeRate, Weight};
484
485 #[test]
486 fn mathop_predicates() {
487 assert!(MathOp::Add.is_overflow());
488 assert!(MathOp::Sub.is_overflow());
489 assert!(MathOp::Mul.is_overflow());
490 assert!(MathOp::Neg.is_overflow());
491 assert!(!MathOp::Div.is_overflow());
492 assert!(!MathOp::Rem.is_overflow());
493
494 assert!(MathOp::Div.is_div_by_zero());
495 assert!(MathOp::Rem.is_div_by_zero());
496 assert!(!MathOp::Add.is_div_by_zero());
497
498 assert!(MathOp::Add.is_addition());
499 assert!(!MathOp::Sub.is_addition());
500
501 assert!(MathOp::Sub.is_subtraction());
502 assert!(!MathOp::Add.is_subtraction());
503
504 assert!(MathOp::Mul.is_multiplication());
505 assert!(!MathOp::Div.is_multiplication());
506
507 assert!(MathOp::Neg.is_negation());
508 assert!(!MathOp::Add.is_negation());
509 }
510
511 #[test]
512 fn mathop_map() {
513 let res = NumOpResult::Valid(Amount::from_sat_u32(100));
515 let new_value = res.map(|val| (val / FeeRate::from_sat_per_kwu(10)).unwrap());
516 assert_eq!(new_value, NumOpResult::Valid(Weight::from_wu(10_000)));
517
518 let res = NumOpResult::<Weight>::Error(NumOpError::while_doing(MathOp::Add));
520 let res_err = res.map(|_| {
521 panic!("map should not evaluate for wrapped error values");
522 });
523 assert_eq!(res_err, res);
524 }
525
526 #[test]
527 fn mathop_expect() {
528 let amounts = [
529 Amount::from_sat_u32(0),
530 Amount::from_sat_u32(10_000_000),
531 Amount::from_sat_u32(u32::MAX),
532 ];
533 for amount in amounts {
534 assert_eq!(
535 NumOpResult::Valid(amount).expect("unreachable"),
536 NumOpResult::Valid(amount).unwrap(),
537 );
538 assert_eq!(NumOpResult::Valid(amount).expect("unreachable"), amount);
539 }
540 }
541
542 #[test]
543 #[should_panic(expected = "test error message")]
544 fn mathop_expect_panics_on_error() {
545 NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add))
546 .expect("test error message");
547 }
548
549 #[test]
550 fn mathop_unwrap() {
551 let amounts = [
552 Amount::from_sat_u32(0),
553 Amount::from_sat_u32(10_000_000),
554 Amount::from_sat_u32(u32::MAX),
555 ];
556 for amount in amounts {
557 assert_eq!(NumOpResult::Valid(amount).unwrap(), amount);
558 }
559 let weights = [Weight::from_wu(0), Weight::from_wu(16_384_000), Weight::from_wu(u64::MAX)];
560 for weight in weights {
561 assert_eq!(NumOpResult::Valid(weight).unwrap(), weight);
562 }
563 }
564
565 #[test]
566 #[should_panic(expected = "")]
567 fn mathop_unwrap_panics_on_err() {
568 NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add)).unwrap();
569 }
570
571 #[test]
572 fn mathop_unwrap_err() {
573 let errs = [
574 NumOpError::while_doing(MathOp::Add),
575 NumOpError::while_doing(MathOp::Sub),
576 NumOpError::while_doing(MathOp::Mul),
577 NumOpError::while_doing(MathOp::Div),
578 NumOpError::while_doing(MathOp::Neg),
579 NumOpError::while_doing(MathOp::Rem),
580 ];
581 for err in errs {
582 assert_eq!(NumOpResult::<Amount>::Error(err).unwrap_err(), err);
583 }
584 }
585
586 #[test]
587 #[should_panic(expected = "")]
588 fn mathop_unwrap_err_panics_on_valid() {
589 let value = Amount::from_sat_u32(150);
590 NumOpResult::<Amount>::Valid(value).unwrap_err();
591 }
592
593 #[test]
594 fn mathop_unwrap_or() {
595 let base_amount = Amount::from_sat_u32(100);
596
597 let res = NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add));
599 let res_default = res.unwrap_or(base_amount);
600 assert_eq!(res_default, base_amount);
601
602 let res = NumOpResult::Valid(base_amount);
604 let new_amount = res.unwrap_or(Amount::from_sat_u32(50));
605 assert_eq!(new_amount, base_amount);
606 }
607
608 #[test]
609 fn mathop_unwrap_or_else() {
610 let base_amount = Amount::from_sat_u32(100);
611
612 let res = NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add));
614 let res_default = res.unwrap_or_else(|| base_amount);
615 assert_eq!(res_default, base_amount);
616
617 let res = NumOpResult::<Amount>::Valid(base_amount);
619 let new_amount = res.unwrap_or_else(|| {
620 panic!("unwrap_or_else should not evaluate for wrapped valid values");
621 });
622 assert_eq!(new_amount, base_amount);
623 }
624
625 #[test]
626 fn mathop_ok() {
627 let amt = Amount::from_sat_u32(150);
628 assert_eq!(NumOpResult::Valid(amt).ok(), Some(amt));
629
630 let err = NumOpError::while_doing(MathOp::Add);
631 assert_eq!(NumOpResult::<Amount>::Error(err).ok(), None);
632 }
633
634 #[test]
635 fn mathop_and_then() {
636 let res = NumOpResult::Valid(Amount::from_sat_u32(100));
638 let new_value = res.and_then(|val| val + Amount::from_sat_u32(50));
639 assert_eq!(new_value, NumOpResult::Valid(Amount::from_sat_u32(150)));
640
641 let res = NumOpResult::<Amount>::Error(NumOpError::while_doing(MathOp::Add));
643 let res_err = res.and_then(|_| {
644 panic!("and_then should not evaluate for wrapped error values");
645 });
646 assert_eq!(res_err, res);
647 }
648
649 #[test]
650 #[cfg(feature = "alloc")]
651 fn error_display_is_non_empty() {
652 let e = (Amount::MAX + Amount::MAX).unwrap_err();
654 assert!(!e.to_string().is_empty());
655 #[cfg(feature = "std")]
656 assert!(e.source().is_none());
657 }
658}