Skip to content

Commit 8a344b7

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 8a344b7

File tree

2 files changed

+65
-40
lines changed

2 files changed

+65
-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: 32 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

@@ -553,21 +551,16 @@ pub enum Quantity {
553551
Bounded(NonZeroU64),
554552
/// One or more items.
555553
Unbounded,
554+
/// Only one item.
555+
One,
556556
}
557557

558558
impl Quantity {
559-
/// The default quantity of one.
560-
pub fn one() -> Self {
561-
Quantity::Bounded(NonZeroU64::new(1).unwrap())
562-
}
563-
564559
fn to_tlv_record(&self) -> Option<u64> {
565560
match self {
566-
Quantity::Bounded(n) => {
567-
let n = n.get();
568-
if n == 1 { None } else { Some(n) }
569-
},
561+
Quantity::Bounded(n) => Some(n.get()),
570562
Quantity::Unbounded => Some(0),
563+
Quantity::One => None,
571564
}
572565
}
573566
}
@@ -639,9 +632,8 @@ impl TryFrom<OfferTlvStream> for OfferContents {
639632
.map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
640633

641634
let supported_quantity = match quantity_max {
642-
None => Quantity::one(),
635+
None => Quantity::One,
643636
Some(0) => Quantity::Unbounded,
644-
Some(1) => return Err(SemanticError::InvalidQuantity),
645637
Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
646638
};
647639

@@ -708,7 +700,7 @@ mod tests {
708700
assert!(!offer.is_expired());
709701
assert_eq!(offer.paths(), &[]);
710702
assert_eq!(offer.issuer(), None);
711-
assert_eq!(offer.supported_quantity(), Quantity::one());
703+
assert_eq!(offer.supported_quantity(), Quantity::One);
712704
assert_eq!(offer.signing_pubkey(), pubkey(42));
713705

714706
assert_eq!(
@@ -930,14 +922,15 @@ mod tests {
930922

931923
#[test]
932924
fn builds_offer_with_supported_quantity() {
925+
let one = NonZeroU64::new(1).unwrap();
933926
let ten = NonZeroU64::new(10).unwrap();
934927

935928
let offer = OfferBuilder::new("foo".into(), pubkey(42))
936-
.supported_quantity(Quantity::one())
929+
.supported_quantity(Quantity::One)
937930
.build()
938931
.unwrap();
939932
let tlv_stream = offer.as_tlv_stream();
940-
assert_eq!(offer.supported_quantity(), Quantity::one());
933+
assert_eq!(offer.supported_quantity(), Quantity::One);
941934
assert_eq!(tlv_stream.quantity_max, None);
942935

943936
let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -956,13 +949,21 @@ mod tests {
956949
assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
957950
assert_eq!(tlv_stream.quantity_max, Some(10));
958951

952+
let offer = OfferBuilder::new("foo".into(), pubkey(42))
953+
.supported_quantity(Quantity::Bounded(one))
954+
.build()
955+
.unwrap();
956+
let tlv_stream = offer.as_tlv_stream();
957+
assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
958+
assert_eq!(tlv_stream.quantity_max, Some(1));
959+
959960
let offer = OfferBuilder::new("foo".into(), pubkey(42))
960961
.supported_quantity(Quantity::Bounded(ten))
961-
.supported_quantity(Quantity::one())
962+
.supported_quantity(Quantity::One)
962963
.build()
963964
.unwrap();
964965
let tlv_stream = offer.as_tlv_stream();
965-
assert_eq!(offer.supported_quantity(), Quantity::one());
966+
assert_eq!(offer.supported_quantity(), Quantity::One);
966967
assert_eq!(tlv_stream.quantity_max, None);
967968
}
968969

@@ -1094,7 +1095,7 @@ mod tests {
10941095
#[test]
10951096
fn parses_offer_with_quantity() {
10961097
let offer = OfferBuilder::new("foo".into(), pubkey(42))
1097-
.supported_quantity(Quantity::one())
1098+
.supported_quantity(Quantity::One)
10981099
.build()
10991100
.unwrap();
11001101
if let Err(e) = offer.to_string().parse::<Offer>() {
@@ -1117,17 +1118,12 @@ mod tests {
11171118
panic!("error parsing offer: {:?}", e);
11181119
}
11191120

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

0 commit comments

Comments
 (0)