Skip to content

Commit 4059677

Browse files
committed
Support explicit quantity_max = 1 in Offer
The spec was modified to allow setting offer_quantity_max explicitly to one. This is to support a use case where more than one item is supported but only one item is left in the inventory. Introduce a Quantity::One variant to replace Quantity::Bounded(1) so the later can be used for the explicit setting.
1 parent 15f1295 commit 4059677

File tree

2 files changed

+71
-42
lines changed

2 files changed

+71
-42
lines changed

lightning/src/offers/invoice_request.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -845,11 +845,12 @@ mod tests {
845845

846846
#[test]
847847
fn builds_invoice_request_with_quantity() {
848+
let one = NonZeroU64::new(1).unwrap();
848849
let ten = NonZeroU64::new(10).unwrap();
849850

850851
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
851852
.amount_msats(1000)
852-
.supported_quantity(Quantity::one())
853+
.supported_quantity(Quantity::One)
853854
.build().unwrap()
854855
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
855856
.build().unwrap()
@@ -860,7 +861,7 @@ mod tests {
860861

861862
match OfferBuilder::new("foo".into(), recipient_pubkey())
862863
.amount_msats(1000)
863-
.supported_quantity(Quantity::one())
864+
.supported_quantity(Quantity::One)
864865
.build().unwrap()
865866
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
866867
.amount_msats(2_000).unwrap()
@@ -918,6 +919,17 @@ mod tests {
918919
Ok(_) => panic!("expected error"),
919920
Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
920921
}
922+
923+
match OfferBuilder::new("foo".into(), recipient_pubkey())
924+
.amount_msats(1000)
925+
.supported_quantity(Quantity::Bounded(one))
926+
.build().unwrap()
927+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
928+
.build()
929+
{
930+
Ok(_) => panic!("expected error"),
931+
Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
932+
}
921933
}
922934

923935
#[test]
@@ -1102,11 +1114,12 @@ mod tests {
11021114

11031115
#[test]
11041116
fn parses_invoice_request_with_quantity() {
1117+
let one = NonZeroU64::new(1).unwrap();
11051118
let ten = NonZeroU64::new(10).unwrap();
11061119

11071120
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
11081121
.amount_msats(1000)
1109-
.supported_quantity(Quantity::one())
1122+
.supported_quantity(Quantity::One)
11101123
.build().unwrap()
11111124
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
11121125
.build().unwrap()
@@ -1121,7 +1134,7 @@ mod tests {
11211134

11221135
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
11231136
.amount_msats(1000)
1124-
.supported_quantity(Quantity::one())
1137+
.supported_quantity(Quantity::One)
11251138
.build().unwrap()
11261139
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
11271140
.amount_msats(2_000).unwrap()
@@ -1206,6 +1219,22 @@ mod tests {
12061219
Ok(_) => panic!("expected error"),
12071220
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
12081221
}
1222+
1223+
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
1224+
.amount_msats(1000)
1225+
.supported_quantity(Quantity::Bounded(one))
1226+
.build().unwrap()
1227+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1228+
.build_unchecked()
1229+
.sign(payer_sign).unwrap();
1230+
1231+
let mut buffer = Vec::new();
1232+
invoice_request.write(&mut buffer).unwrap();
1233+
1234+
match InvoiceRequest::try_from(buffer) {
1235+
Ok(_) => panic!("expected error"),
1236+
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
1237+
}
12091238
}
12101239

12111240
#[test]

lightning/src/offers/offer.rs

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ impl OfferBuilder {
106106
let offer = OfferContents {
107107
chains: None, metadata: None, amount: None, description,
108108
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
109-
supported_quantity: Quantity::one(), signing_pubkey,
109+
supported_quantity: Quantity::One, signing_pubkey,
110110
};
111111
OfferBuilder { offer }
112112
}
@@ -178,7 +178,7 @@ impl OfferBuilder {
178178
}
179179

180180
/// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to
181-
/// [`Quantity::one`].
181+
/// [`Quantity::One`].
182182
///
183183
/// Successive calls to this method will override the previous setting.
184184
pub fn supported_quantity(mut self, quantity: Quantity) -> Self {
@@ -464,19 +464,17 @@ impl OfferContents {
464464

465465
fn is_valid_quantity(&self, quantity: u64) -> bool {
466466
match self.supported_quantity {
467-
Quantity::Bounded(n) => {
468-
let n = n.get();
469-
if n == 1 { false }
470-
else { quantity > 0 && quantity <= n }
471-
},
467+
Quantity::Bounded(n) => quantity <= n.get(),
472468
Quantity::Unbounded => quantity > 0,
469+
Quantity::One => quantity == 1,
473470
}
474471
}
475472

476473
fn expects_quantity(&self) -> bool {
477474
match self.supported_quantity {
478-
Quantity::Bounded(n) => n.get() != 1,
475+
Quantity::Bounded(_) => true,
479476
Quantity::Unbounded => true,
477+
Quantity::One => false,
480478
}
481479
}
482480

@@ -549,25 +547,24 @@ pub type CurrencyCode = [u8; 3];
549547
/// Quantity of items supported by an [`Offer`].
550548
#[derive(Clone, Copy, Debug, PartialEq)]
551549
pub enum Quantity {
552-
/// Up to a specific number of items (inclusive).
550+
/// Up to a specific number of items (inclusive). Use when more than one item can be requested
551+
/// but is limited (e.g., because of per customer or inventory limits).
552+
///
553+
/// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item
554+
/// is supported.
553555
Bounded(NonZeroU64),
554-
/// One or more items.
556+
/// One or more items. Use when more than one item can be requested without any limit.
555557
Unbounded,
558+
/// Only one item. Use when only a single item can be requested.
559+
One,
556560
}
557561

558562
impl Quantity {
559-
/// The default quantity of one.
560-
pub fn one() -> Self {
561-
Quantity::Bounded(NonZeroU64::new(1).unwrap())
562-
}
563-
564563
fn to_tlv_record(&self) -> Option<u64> {
565564
match self {
566-
Quantity::Bounded(n) => {
567-
let n = n.get();
568-
if n == 1 { None } else { Some(n) }
569-
},
565+
Quantity::Bounded(n) => Some(n.get()),
570566
Quantity::Unbounded => Some(0),
567+
Quantity::One => None,
571568
}
572569
}
573570
}
@@ -639,9 +636,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
639636
.map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
640637

641638
let supported_quantity = match quantity_max {
642-
None => Quantity::one(),
639+
None => Quantity::One,
643640
Some(0) => Quantity::Unbounded,
644-
Some(1) => return Err(SemanticError::InvalidQuantity),
645641
Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
646642
};
647643

@@ -708,7 +704,7 @@ mod tests {
708704
assert!(!offer.is_expired());
709705
assert_eq!(offer.paths(), &[]);
710706
assert_eq!(offer.issuer(), None);
711-
assert_eq!(offer.supported_quantity(), Quantity::one());
707+
assert_eq!(offer.supported_quantity(), Quantity::One);
712708
assert_eq!(offer.signing_pubkey(), pubkey(42));
713709

714710
assert_eq!(
@@ -930,14 +926,15 @@ mod tests {
930926

931927
#[test]
932928
fn builds_offer_with_supported_quantity() {
929+
let one = NonZeroU64::new(1).unwrap();
933930
let ten = NonZeroU64::new(10).unwrap();
934931

935932
let offer = OfferBuilder::new("foo".into(), pubkey(42))
936-
.supported_quantity(Quantity::one())
933+
.supported_quantity(Quantity::One)
937934
.build()
938935
.unwrap();
939936
let tlv_stream = offer.as_tlv_stream();
940-
assert_eq!(offer.supported_quantity(), Quantity::one());
937+
assert_eq!(offer.supported_quantity(), Quantity::One);
941938
assert_eq!(tlv_stream.quantity_max, None);
942939

943940
let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -956,13 +953,21 @@ mod tests {
956953
assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
957954
assert_eq!(tlv_stream.quantity_max, Some(10));
958955

956+
let offer = OfferBuilder::new("foo".into(), pubkey(42))
957+
.supported_quantity(Quantity::Bounded(one))
958+
.build()
959+
.unwrap();
960+
let tlv_stream = offer.as_tlv_stream();
961+
assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
962+
assert_eq!(tlv_stream.quantity_max, Some(1));
963+
959964
let offer = OfferBuilder::new("foo".into(), pubkey(42))
960965
.supported_quantity(Quantity::Bounded(ten))
961-
.supported_quantity(Quantity::one())
966+
.supported_quantity(Quantity::One)
962967
.build()
963968
.unwrap();
964969
let tlv_stream = offer.as_tlv_stream();
965-
assert_eq!(offer.supported_quantity(), Quantity::one());
970+
assert_eq!(offer.supported_quantity(), Quantity::One);
966971
assert_eq!(tlv_stream.quantity_max, None);
967972
}
968973

@@ -1094,7 +1099,7 @@ mod tests {
10941099
#[test]
10951100
fn parses_offer_with_quantity() {
10961101
let offer = OfferBuilder::new("foo".into(), pubkey(42))
1097-
.supported_quantity(Quantity::one())
1102+
.supported_quantity(Quantity::One)
10981103
.build()
10991104
.unwrap();
11001105
if let Err(e) = offer.to_string().parse::<Offer>() {
@@ -1117,17 +1122,12 @@ mod tests {
11171122
panic!("error parsing offer: {:?}", e);
11181123
}
11191124

1120-
let mut tlv_stream = offer.as_tlv_stream();
1121-
tlv_stream.quantity_max = Some(1);
1122-
1123-
let mut encoded_offer = Vec::new();
1124-
tlv_stream.write(&mut encoded_offer).unwrap();
1125-
1126-
match Offer::try_from(encoded_offer) {
1127-
Ok(_) => panic!("expected error"),
1128-
Err(e) => {
1129-
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity));
1130-
},
1125+
let offer = OfferBuilder::new("foo".into(), pubkey(42))
1126+
.supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
1127+
.build()
1128+
.unwrap();
1129+
if let Err(e) = offer.to_string().parse::<Offer>() {
1130+
panic!("error parsing offer: {:?}", e);
11311131
}
11321132
}
11331133

0 commit comments

Comments
 (0)