Simplify Swift invoice handling with non-optional return types
- Mentions.swift: convert_invoice_description now returns non-optional InvoiceDescription, returning empty description for BOLT11 compliance (both description and description_hash are optional per spec) - Block.swift, NdbBlock.swift, NostrEvent.swift, NoteContent.swift: Updated call sites to use non-optional invoice conversion - InvoiceTests.swift: Added test for specific failing invoice Signed-off-by: alltheseas <alltheseas@noreply.github.com> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Daniel D’Aquino
parent
845089bed1
commit
1505a8f2e4
@@ -294,7 +294,10 @@ func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String {
|
|||||||
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
|
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convert_invoice_description(b11: ndb_invoice) -> InvoiceDescription? {
|
/// Extracts the description from a BOLT11 invoice.
|
||||||
|
/// Returns empty description if invoice has neither description nor description_hash,
|
||||||
|
/// as both fields are optional per BOLT11 spec.
|
||||||
|
func convert_invoice_description(b11: ndb_invoice) -> InvoiceDescription {
|
||||||
if let desc = b11.description {
|
if let desc = b11.description {
|
||||||
return .description(String(cString: desc))
|
return .description(String(cString: desc))
|
||||||
}
|
}
|
||||||
@@ -303,7 +306,7 @@ func convert_invoice_description(b11: ndb_invoice) -> InvoiceDescription? {
|
|||||||
return .description_hash(Data(bytes: &deschash, count: 32))
|
return .description_hash(Data(bytes: &deschash, count: 32))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return .description("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
|
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
|
||||||
|
|||||||
@@ -839,9 +839,7 @@ func separate_invoices(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> [Invoice]?
|
|||||||
let invoiceBlocks: [Invoice] = (try? blockGroup.reduce(initialResult: [Invoice](), { index, invoices, block in
|
let invoiceBlocks: [Invoice] = (try? blockGroup.reduce(initialResult: [Invoice](), { index, invoices, block in
|
||||||
switch block {
|
switch block {
|
||||||
case .invoice(let invoice):
|
case .invoice(let invoice):
|
||||||
if let invoice = invoice.as_invoice() {
|
return .loopReturn(invoices + [invoice.as_invoice()])
|
||||||
return .loopReturn(invoices + [invoice])
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,7 @@ extension Block {
|
|||||||
guard let url = URL(string: block.as_str()) else { return nil }
|
guard let url = URL(string: block.as_str()) else { return nil }
|
||||||
self = .url(url)
|
self = .url(url)
|
||||||
case BLOCK_INVOICE:
|
case BLOCK_INVOICE:
|
||||||
guard let b = Block(invoice: block.block.invoice) else { return nil }
|
self = Block(invoice: block.block.invoice)
|
||||||
self = b
|
|
||||||
case BLOCK_MENTION_BECH32:
|
case BLOCK_MENTION_BECH32:
|
||||||
guard let b = Block(bech32: block.block.mention_bech32) else { return nil }
|
guard let b = Block(bech32: block.block.mention_bech32) else { return nil }
|
||||||
self = b
|
self = b
|
||||||
@@ -113,26 +112,20 @@ fileprivate extension Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension Block {
|
fileprivate extension Block {
|
||||||
/// Failable initializer for the C-backed type `invoice_block_t`.
|
/// Initializer for the C-backed type `invoice_block_t`.
|
||||||
init?(invoice: ndb_invoice_block) {
|
init(invoice: ndb_invoice_block) {
|
||||||
|
self = .invoice(invoice_block_as_invoice(invoice))
|
||||||
guard let invoice = invoice_block_as_invoice(invoice) else { return nil }
|
|
||||||
self = .invoice(invoice)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invoice_block_as_invoice(_ invoice: ndb_invoice_block) -> Invoice? {
|
/// Converts a C-backed invoice block to a Swift Invoice.
|
||||||
|
func invoice_block_as_invoice(_ invoice: ndb_invoice_block) -> Invoice {
|
||||||
let invstr = invoice.invstr.as_str()
|
let invstr = invoice.invstr.as_str()
|
||||||
let b11 = invoice.invoice
|
let b11 = invoice.invoice
|
||||||
|
let description = convert_invoice_description(b11: b11)
|
||||||
guard let description = convert_invoice_description(b11: b11) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let amount: Amount = b11.amount == 0 ? .any : .specific(Int64(b11.amount))
|
let amount: Amount = b11.amount == 0 ? .any : .specific(Int64(b11.amount))
|
||||||
|
|
||||||
return Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, created_at: b11.timestamp)
|
return Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, created_at: b11.timestamp)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension Block {
|
fileprivate extension Block {
|
||||||
|
|||||||
@@ -191,8 +191,7 @@ func render_blocks(blocks: borrowing NdbBlockGroup, profiles: Profiles, can_hide
|
|||||||
let url_type = classify_url(url)
|
let url_type = classify_url(url)
|
||||||
urls.append(url_type)
|
urls.append(url_type)
|
||||||
case .invoice(let invoice_block):
|
case .invoice(let invoice_block):
|
||||||
guard let invoice = invoice_block.as_invoice() else { break }
|
invoices.append(invoice_block.as_invoice())
|
||||||
invoices.append(invoice)
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -258,9 +257,9 @@ func render_blocks(blocks: borrowing NdbBlockGroup, profiles: Profiles, can_hide
|
|||||||
return .loopReturn(str + CompatibleText(stringLiteral: reduce_text_block(ind: index, hide_text_index: hide_text_index_argument, txt: txt.as_str())))
|
return .loopReturn(str + CompatibleText(stringLiteral: reduce_text_block(ind: index, hide_text_index: hide_text_index_argument, txt: txt.as_str())))
|
||||||
case .hashtag(let htag):
|
case .hashtag(let htag):
|
||||||
return .loopReturn(str + hashtag_str(htag.as_str()))
|
return .loopReturn(str + hashtag_str(htag.as_str()))
|
||||||
case .invoice(let invoice):
|
case .invoice:
|
||||||
guard let inv = invoice.as_invoice() else { return .loopContinue }
|
// Invoice already added in previewable-collection switch above
|
||||||
invoices.append(inv)
|
break
|
||||||
case .url(let url):
|
case .url(let url):
|
||||||
guard let url = URL(string: url.as_str()) else { return .loopContinue }
|
guard let url = URL(string: url.as_str()) else { return .loopContinue }
|
||||||
return .loopReturn(str + url_str(url))
|
return .loopReturn(str + url_str(url))
|
||||||
|
|||||||
@@ -33,10 +33,7 @@ final class InvoiceTests: XCTestCase {
|
|||||||
let success: Bool? = blockList.useItem(at: 0, { block in
|
let success: Bool? = blockList.useItem(at: 0, { block in
|
||||||
switch block {
|
switch block {
|
||||||
case .invoice(let invoiceData):
|
case .invoice(let invoiceData):
|
||||||
guard let invoice = invoiceData.as_invoice() else {
|
let invoice = invoiceData.as_invoice()
|
||||||
XCTFail("Cannot get invoice from invoice block")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
XCTAssertEqual(invoice.amount, .any)
|
XCTAssertEqual(invoice.amount, .any)
|
||||||
XCTAssertEqual(invoice.string, invstr)
|
XCTAssertEqual(invoice.string, invstr)
|
||||||
return true
|
return true
|
||||||
@@ -110,10 +107,7 @@ final class InvoiceTests: XCTestCase {
|
|||||||
let success: Bool? = blockList.useItem(at: 0, { block in
|
let success: Bool? = blockList.useItem(at: 0, { block in
|
||||||
switch block {
|
switch block {
|
||||||
case .invoice(let invoiceData):
|
case .invoice(let invoiceData):
|
||||||
guard let invoice = invoiceData.as_invoice() else {
|
let invoice = invoiceData.as_invoice()
|
||||||
XCTFail("Cannot get invoice from invoice block")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
XCTAssertEqual(invoice.amount, .specific(10000))
|
XCTAssertEqual(invoice.amount, .specific(10000))
|
||||||
XCTAssertEqual(invoice.expiry, 604800)
|
XCTAssertEqual(invoice.expiry, 604800)
|
||||||
XCTAssertEqual(invoice.created_at, 1666139119)
|
XCTAssertEqual(invoice.created_at, 1666139119)
|
||||||
@@ -174,6 +168,35 @@ final class InvoiceTests: XCTestCase {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test parsing the specific invoice from GitHub issue that wasn't rendering
|
||||||
|
func testParseSpecificFailingInvoice() throws {
|
||||||
|
let invstr = "lnbc130n1p5h7alnpp5f83swv5wx9h25ansxsvkw7364c65vxktthy2m9ww5zf3cjrzp0vsdq9tfpygcqzysxqzjcsp5essuf0xnfeu4rpw7nllcggr6e9635xdpnaklr2fadtkwej0vvyfs9qxpqysgqddjjzxa2dwhntx8uvppx3u6pu864ul5dxkayp6jgf7n45ql5x7u9xzrvuav5rzsaz7h8d2gq455je2ezku40a5xrshu0w00ylprk03qq6kvvjd"
|
||||||
|
|
||||||
|
guard let blockGroup: NdbBlockGroup = try? NdbBlockGroup.parse(content: invstr) else {
|
||||||
|
XCTFail("Parsing threw an error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blockGroup.withList({ blockList in
|
||||||
|
XCTAssertEqual(blockList.count, 1, "Expected 1 block, got \(blockList.count)")
|
||||||
|
let success: Bool? = blockList.useItem(at: 0, { block in
|
||||||
|
switch block {
|
||||||
|
case .invoice(let invoiceData):
|
||||||
|
let invoice = invoiceData.as_invoice()
|
||||||
|
XCTAssertEqual(invoice.amount, .specific(13000))
|
||||||
|
return true
|
||||||
|
case .text(let txt):
|
||||||
|
XCTFail("Expected invoice block, got text block")
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
XCTFail("Block is not an invoice")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
XCTAssertEqual(success, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// gh-3144: It was decided on a standup meeting that we do not need invoices to render, few people use this feature.
|
// gh-3144: It was decided on a standup meeting that we do not need invoices to render, few people use this feature.
|
||||||
func testParseInvoice() throws {
|
func testParseInvoice() throws {
|
||||||
|
|||||||
@@ -37,14 +37,11 @@ enum NdbBech32Type: UInt32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ndb_invoice_block {
|
extension ndb_invoice_block {
|
||||||
func as_invoice() -> Invoice? {
|
/// Converts to a Swift Invoice object.
|
||||||
|
func as_invoice() -> Invoice {
|
||||||
let b11 = self.invoice
|
let b11 = self.invoice
|
||||||
let invstr = self.invstr.as_str()
|
let invstr = self.invstr.as_str()
|
||||||
|
let description = convert_invoice_description(b11: b11)
|
||||||
guard let description = convert_invoice_description(b11: b11) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let amount: Amount = b11.amount == 0 ? .any : .specific(Int64(b11.amount))
|
let amount: Amount = b11.amount == 0 ? .any : .specific(Int64(b11.amount))
|
||||||
|
|
||||||
return Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, created_at: b11.timestamp)
|
return Invoice(description: description, amount: amount, string: invstr, expiry: b11.expiry, created_at: b11.timestamp)
|
||||||
@@ -125,18 +122,18 @@ struct NdbBlockGroup: ~Copyable {
|
|||||||
if event.is_content_encrypted() {
|
if event.is_content_encrypted() {
|
||||||
return try lendingFunction(parse(event: event, keypair: keypair))
|
return try lendingFunction(parse(event: event, keypair: keypair))
|
||||||
}
|
}
|
||||||
else if event.known_kind == .highlight {
|
if event.known_kind == .highlight {
|
||||||
return try lendingFunction(parse(event: event, keypair: keypair))
|
return try lendingFunction(parse(event: event, keypair: keypair))
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return try ndb.lookup_block_group_by_key(event: event, borrow: { group in
|
return try ndb.lookup_block_group_by_key(event: event, borrow: { group in
|
||||||
switch group {
|
switch group {
|
||||||
case .none: return try lendingFunction(parse(event: event, keypair: keypair))
|
case .none:
|
||||||
case .some(let group): return try lendingFunction(group)
|
return try lendingFunction(parse(event: event, keypair: keypair))
|
||||||
|
case .some(let group):
|
||||||
|
return try lendingFunction(group)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses the note contents on-demand from a specific note.
|
/// Parses the note contents on-demand from a specific note.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user