Skip to content

Commit bdd602d

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 bdd602d

File tree

2 files changed

+68
-40
lines changed

2 files changed

+68
-40
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: 35 additions & 36 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

@@ -552,22 +550,20 @@ pub enum Quantity {
552550
/// Up to a specific number of items (inclusive).
553551
Bounded(NonZeroU64),
554552
/// One or more items.
553+
///
554+
/// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item
555+
/// is supported.
555556
Unbounded,
557+
/// Only one item.
558+
One,
556559
}
557560

558561
impl Quantity {
559-
/// The default quantity of one.
560-
pub fn one() -> Self {
561-
Quantity::Bounded(NonZeroU64::new(1).unwrap())
562-
}
563-
564562
fn to_tlv_record(&self) -> Option<u64> {
565563
match self {
566-
Quantity::Bounded(n) => {
567-
let n = n.get();
568-
if n == 1 { None } else { Some(n) }
569-
},
564+
Quantity::Bounded(n) => Some(n.get()),
570565
Quantity::Unbounded => Some(0),
566+
Quantity::One => None,
571567
}
572568
}
573569
}
@@ -639,9 +635,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
639635
.map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
640636

641637
let supported_quantity = match quantity_max {
642-
None => Quantity::one(),
638+
None => Quantity::One,
643639
Some(0) => Quantity::Unbounded,
644-
Some(1) => return Err(SemanticError::InvalidQuantity),
645640
Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
646641
};
647642

@@ -708,7 +703,7 @@ mod tests {
708703
assert!(!offer.is_expired());
709704
assert_eq!(offer.paths(), &[]);
710705
assert_eq!(offer.issuer(), None);
711-
assert_eq!(offer.supported_quantity(), Quantity::one());
706+
assert_eq!(offer.supported_quantity(), Quantity::One);
712707
assert_eq!(offer.signing_pubkey(), pubkey(42));
713708

714709
assert_eq!(
@@ -930,14 +925,15 @@ mod tests {
930925

931926
#[test]
932927
fn builds_offer_with_supported_quantity() {
928+
let one = NonZeroU64::new(1).unwrap();
933929
let ten = NonZeroU64::new(10).unwrap();
934930

935931
let offer = OfferBuilder::new("foo".into(), pubkey(42))
936-
.supported_quantity(Quantity::one())
932+
.supported_quantity(Quantity::One)
937933
.build()
938934
.unwrap();
939935
let tlv_stream = offer.as_tlv_stream();
940-
assert_eq!(offer.supported_quantity(), Quantity::one());
936+
assert_eq!(offer.supported_quantity(), Quantity::One);
941937
assert_eq!(tlv_stream.quantity_max, None);
942938

943939
let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -956,13 +952,21 @@ mod tests {
956952
assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
957953
assert_eq!(tlv_stream.quantity_max, Some(10));
958954

955+
let offer = OfferBuilder::new("foo".into(), pubkey(42))
956+
.supported_quantity(Quantity::Bounded(one))
957+
.build()
958+
.unwrap();
959+
let tlv_stream = offer.as_tlv_stream();
960+
assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
961+
assert_eq!(tlv_stream.quantity_max, Some(1));
962+
959963
let offer = OfferBuilder::new("foo".into(), pubkey(42))
960964
.supported_quantity(Quantity::Bounded(ten))
961-
.supported_quantity(Quantity::one())
965+
.supported_quantity(Quantity::One)
962966
.build()
963967
.unwrap();
964968
let tlv_stream = offer.as_tlv_stream();
965-
assert_eq!(offer.supported_quantity(), Quantity::one());
969+
assert_eq!(offer.supported_quantity(), Quantity::One);
966970
assert_eq!(tlv_stream.quantity_max, None);
967971
}
968972

@@ -1094,7 +1098,7 @@ mod tests {
10941098
#[test]
10951099
fn parses_offer_with_quantity() {
10961100
let offer = OfferBuilder::new("foo".into(), pubkey(42))
1097-
.supported_quantity(Quantity::one())
1101+
.supported_quantity(Quantity::One)
10981102
.build()
10991103
.unwrap();
11001104
if let Err(e) = offer.to_string().parse::<Offer>() {
@@ -1117,17 +1121,12 @@ mod tests {
11171121
panic!("error parsing offer: {:?}", e);
11181122
}
11191123

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-
},
1124+
let offer = OfferBuilder::new("foo".into(), pubkey(42))
1125+
.supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
1126+
.build()
1127+
.unwrap();
1128+
if let Err(e) = offer.to_string().parse::<Offer>() {
1129+
panic!("error parsing offer: {:?}", e);
11311130
}
11321131
}
11331132

0 commit comments

Comments
 (0)