Fix (and DRY) the conditionals before calling `peer_disconnected`
[rust-lightning] / lightning / src / offers / offer.rs
index 6451d9431a188f3b15440c1d687435e7068f7240..a2008b6a0b5a0899e47c09c495829fd6a567bfd4 100644 (file)
@@ -106,7 +106,7 @@ impl OfferBuilder {
                let offer = OfferContents {
                        chains: None, metadata: None, amount: None, description,
                        features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
-                       supported_quantity: Quantity::one(), signing_pubkey,
+                       supported_quantity: Quantity::One, signing_pubkey,
                };
                OfferBuilder { offer }
        }
@@ -178,7 +178,7 @@ impl OfferBuilder {
        }
 
        /// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to
-       /// [`Quantity::one`].
+       /// [`Quantity::One`].
        ///
        /// Successive calls to this method will override the previous setting.
        pub fn supported_quantity(mut self, quantity: Quantity) -> Self {
@@ -232,7 +232,7 @@ impl OfferBuilder {
 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
 ///
 /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
+/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to
 /// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
 ///
 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
@@ -241,6 +241,7 @@ impl OfferBuilder {
 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
 #[derive(Clone, Debug)]
 pub struct Offer {
        // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
@@ -249,9 +250,10 @@ pub struct Offer {
        pub(super) contents: OfferContents,
 }
 
-/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an [`Invoice`].
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
 #[derive(Clone, Debug)]
 pub(super) struct OfferContents {
        chains: Option<Vec<ChainHash>>,
@@ -319,13 +321,7 @@ impl Offer {
        /// Whether the offer has expired.
        #[cfg(feature = "std")]
        pub fn is_expired(&self) -> bool {
-               match self.absolute_expiry() {
-                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
-                               Ok(elapsed) => elapsed > seconds_from_epoch,
-                               Err(_) => false,
-                       },
-                       None => false,
-               }
+               self.contents.is_expired()
        }
 
        /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
@@ -359,7 +355,7 @@ impl Offer {
 
        /// The public key used by the recipient to sign invoices.
        pub fn signing_pubkey(&self) -> PublicKey {
-               self.contents.signing_pubkey
+               self.contents.signing_pubkey()
        }
 
        /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which
@@ -410,6 +406,17 @@ impl OfferContents {
                self.chains().contains(&chain)
        }
 
+       #[cfg(feature = "std")]
+       pub(super) fn is_expired(&self) -> bool {
+               match self.absolute_expiry {
+                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+                               Ok(elapsed) => elapsed > seconds_from_epoch,
+                               Err(_) => false,
+                       },
+                       None => false,
+               }
+       }
+
        pub fn amount(&self) -> Option<&Amount> {
                self.amount.as_ref()
        }
@@ -457,22 +464,24 @@ impl OfferContents {
 
        fn is_valid_quantity(&self, quantity: u64) -> bool {
                match self.supported_quantity {
-                       Quantity::Bounded(n) => {
-                               let n = n.get();
-                               if n == 1 { false }
-                               else { quantity > 0 && quantity <= n }
-                       },
+                       Quantity::Bounded(n) => quantity <= n.get(),
                        Quantity::Unbounded => quantity > 0,
+                       Quantity::One => quantity == 1,
                }
        }
 
        fn expects_quantity(&self) -> bool {
                match self.supported_quantity {
-                       Quantity::Bounded(n) => n.get() != 1,
+                       Quantity::Bounded(_) => true,
                        Quantity::Unbounded => true,
+                       Quantity::One => false,
                }
        }
 
+       pub(super) fn signing_pubkey(&self) -> PublicKey {
+               self.signing_pubkey
+       }
+
        pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
                let (currency, amount) = match &self.amount {
                        None => (None, None),
@@ -538,25 +547,24 @@ pub type CurrencyCode = [u8; 3];
 /// Quantity of items supported by an [`Offer`].
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum Quantity {
-       /// Up to a specific number of items (inclusive).
+       /// Up to a specific number of items (inclusive). Use when more than one item can be requested
+       /// but is limited (e.g., because of per customer or inventory limits).
+       ///
+       /// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item
+       /// is supported.
        Bounded(NonZeroU64),
-       /// One or more items.
+       /// One or more items. Use when more than one item can be requested without any limit.
        Unbounded,
+       /// Only one item. Use when only a single item can be requested.
+       One,
 }
 
 impl Quantity {
-       /// The default quantity of one.
-       pub fn one() -> Self {
-               Quantity::Bounded(NonZeroU64::new(1).unwrap())
-       }
-
        fn to_tlv_record(&self) -> Option<u64> {
                match self {
-                       Quantity::Bounded(n) => {
-                               let n = n.get();
-                               if n == 1 { None } else { Some(n) }
-                       },
+                       Quantity::Bounded(n) => Some(n.get()),
                        Quantity::Unbounded => Some(0),
+                       Quantity::One => None,
                }
        }
 }
@@ -567,7 +575,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
        (6, currency: CurrencyCode),
        (8, amount: (u64, HighZeroBytesDroppedBigSize)),
        (10, description: (String, WithoutLength)),
-       (12, features: OfferFeatures),
+       (12, features: (OfferFeatures, WithoutLength)),
        (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
        (16, paths: (Vec<BlindedPath>, WithoutLength)),
        (18, issuer: (String, WithoutLength)),
@@ -628,9 +636,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
                        .map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
 
                let supported_quantity = match quantity_max {
-                       None => Quantity::one(),
+                       None => Quantity::One,
                        Some(0) => Quantity::Unbounded,
-                       Some(1) => return Err(SemanticError::InvalidQuantity),
                        Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
                };
 
@@ -697,7 +704,7 @@ mod tests {
                assert!(!offer.is_expired());
                assert_eq!(offer.paths(), &[]);
                assert_eq!(offer.issuer(), None);
-               assert_eq!(offer.supported_quantity(), Quantity::one());
+               assert_eq!(offer.supported_quantity(), Quantity::One);
                assert_eq!(offer.signing_pubkey(), pubkey(42));
 
                assert_eq!(
@@ -919,14 +926,15 @@ mod tests {
 
        #[test]
        fn builds_offer_with_supported_quantity() {
+               let one = NonZeroU64::new(1).unwrap();
                let ten = NonZeroU64::new(10).unwrap();
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
-                       .supported_quantity(Quantity::one())
+                       .supported_quantity(Quantity::One)
                        .build()
                        .unwrap();
                let tlv_stream = offer.as_tlv_stream();
-               assert_eq!(offer.supported_quantity(), Quantity::one());
+               assert_eq!(offer.supported_quantity(), Quantity::One);
                assert_eq!(tlv_stream.quantity_max, None);
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -945,13 +953,21 @@ mod tests {
                assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
                assert_eq!(tlv_stream.quantity_max, Some(10));
 
+               let offer = OfferBuilder::new("foo".into(), pubkey(42))
+                       .supported_quantity(Quantity::Bounded(one))
+                       .build()
+                       .unwrap();
+               let tlv_stream = offer.as_tlv_stream();
+               assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
+               assert_eq!(tlv_stream.quantity_max, Some(1));
+
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
                        .supported_quantity(Quantity::Bounded(ten))
-                       .supported_quantity(Quantity::one())
+                       .supported_quantity(Quantity::One)
                        .build()
                        .unwrap();
                let tlv_stream = offer.as_tlv_stream();
-               assert_eq!(offer.supported_quantity(), Quantity::one());
+               assert_eq!(offer.supported_quantity(), Quantity::One);
                assert_eq!(tlv_stream.quantity_max, None);
        }
 
@@ -1083,7 +1099,7 @@ mod tests {
        #[test]
        fn parses_offer_with_quantity() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
-                       .supported_quantity(Quantity::one())
+                       .supported_quantity(Quantity::One)
                        .build()
                        .unwrap();
                if let Err(e) = offer.to_string().parse::<Offer>() {
@@ -1106,17 +1122,12 @@ mod tests {
                        panic!("error parsing offer: {:?}", e);
                }
 
-               let mut tlv_stream = offer.as_tlv_stream();
-               tlv_stream.quantity_max = Some(1);
-
-               let mut encoded_offer = Vec::new();
-               tlv_stream.write(&mut encoded_offer).unwrap();
-
-               match Offer::try_from(encoded_offer) {
-                       Ok(_) => panic!("expected error"),
-                       Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity));
-                       },
+               let offer = OfferBuilder::new("foo".into(), pubkey(42))
+                       .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
+                       .build()
+                       .unwrap();
+               if let Err(e) = offer.to_string().parse::<Offer>() {
+                       panic!("error parsing offer: {:?}", e);
                }
        }