From c5faf7630802968a3e410c7368376b8e3823ce39 Mon Sep 17 00:00:00 2001 From: 4xvgal <4xvgal@protonmail.com> Date: Mon, 23 Mar 2026 00:12:41 +0900 Subject: [PATCH 1/3] feat: Add support for UnionField in msggen and related conversion logic --- .msggen.json | 56 +++- cln-grpc/proto/node.proto | 86 +++++- cln-grpc/src/convert.rs | 277 +++++++++++++++++--- cln-rpc/src/model.rs | 118 +++++++-- contrib/msggen/msggen/checks.py | 4 + contrib/msggen/msggen/gen/grpc/convert.py | 94 ++++++- contrib/msggen/msggen/gen/grpc/proto.py | 51 +++- contrib/msggen/msggen/gen/grpc/unconvert.py | 85 +++++- contrib/msggen/msggen/gen/grpc/util.py | 33 +++ contrib/msggen/msggen/gen/grpc2py.py | 9 +- contrib/msggen/msggen/gen/rpc/rust.py | 102 ++++++- contrib/msggen/msggen/model.py | 31 +-- contrib/msggen/msggen/patch.py | 4 + 13 files changed, 858 insertions(+), 92 deletions(-) diff --git a/.msggen.json b/.msggen.json index 899e574c41e4..8c55b7d695b5 100644 --- a/.msggen.json +++ b/.msggen.json @@ -1270,6 +1270,8 @@ "CreateinvoiceRequest": { "CreateInvoice.invstring": 1, "CreateInvoice.label": 2, + "CreateInvoice.label_int": 5, + "CreateInvoice.label_string": 4, "CreateInvoice.preimage": 3 }, "CreateinvoiceResponse": { @@ -1323,6 +1325,8 @@ "Datastore.hex": 2, "Datastore.key": 5, "Datastore.key[]": 1, + "Datastore.key_arr_string": 7, + "Datastore.key_string": 8, "Datastore.mode": 3, "Datastore.string": 6 }, @@ -1338,7 +1342,9 @@ "DatastoreUsage.datastoreusage.total_bytes": 2 }, "DatastoreusageRequest": { - "DatastoreUsage.key": 1 + "DatastoreUsage.key": 1, + "DatastoreUsage.key_arr_string": 2, + "DatastoreUsage.key_string": 3 }, "DatastoreusageResponse": { "DatastoreUsage.datastoreusage": 1 @@ -1524,7 +1530,9 @@ "DeldatastoreRequest": { "DelDatastore.generation": 2, "DelDatastore.key": 3, - "DelDatastore.key[]": 1 + "DelDatastore.key[]": 1, + "DelDatastore.key_arr_string": 4, + "DelDatastore.key_string": 5 }, "DeldatastoreResponse": { "DelDatastore.generation": 2, @@ -1544,6 +1552,8 @@ "DelinvoiceRequest": { "DelInvoice.desconly": 3, "DelInvoice.label": 1, + "DelInvoice.label_string": 4, + "DelInvoice.label_u64": 5, "DelInvoice.status": 2 }, "DelinvoiceResponse": { @@ -2056,8 +2066,13 @@ "Invoice.description": 2, "Invoice.expiry": 7, "Invoice.exposeprivatechannels": 8, + "Invoice.exposeprivatechannels_arr_scid": 14, + "Invoice.exposeprivatechannels_bool": 13, + "Invoice.exposeprivatechannels_scid": 15, "Invoice.fallbacks[]": 4, "Invoice.label": 3, + "Invoice.label_int": 12, + "Invoice.label_string": 11, "Invoice.msatoshi": 1, "Invoice.preimage": 5 }, @@ -2716,7 +2731,9 @@ }, "ListdatastoreRequest": { "ListDatastore.key": 2, - "ListDatastore.key[]": 1 + "ListDatastore.key[]": 1, + "ListDatastore.key_arr_string": 3, + "ListDatastore.key_string": 4 }, "ListdatastoreResponse": { "ListDatastore.datastore[]": 1 @@ -2843,6 +2860,8 @@ "ListInvoices.index": 5, "ListInvoices.invstring": 2, "ListInvoices.label": 1, + "ListInvoices.label_int": 9, + "ListInvoices.label_string": 8, "ListInvoices.limit": 7, "ListInvoices.offer_id": 4, "ListInvoices.payment_hash": 3, @@ -3364,6 +3383,8 @@ "OfferRequest": { "Offer.absolute_expiry": 6, "Offer.amount": 1, + "Offer.amount_currency": 17, + "Offer.amount_msat_or_any": 16, "Offer.description": 2, "Offer.fronting_nodes[]": 15, "Offer.issuer": 3, @@ -3758,7 +3779,10 @@ "SetconfigRequest": { "SetConfig.config": 1, "SetConfig.transient": 3, - "SetConfig.val": 2 + "SetConfig.val": 2, + "SetConfig.val_bool": 6, + "SetConfig.val_int": 5, + "SetConfig.val_string": 4 }, "SetconfigResponse": { "SetConfig.config": 1 @@ -4055,7 +4079,9 @@ "WaitInvoice.paid_outpoint.txid": 1 }, "WaitinvoiceRequest": { - "WaitInvoice.label": 1 + "WaitInvoice.label": 1, + "WaitInvoice.label_int": 3, + "WaitInvoice.label_string": 2 }, "WaitinvoiceResponse": { "WaitInvoice.amount_msat": 6, @@ -6045,6 +6071,10 @@ "added": "pre-v0.10.1", "deprecated": null }, + "Datastore.key[]": { + "added": "pre-v0.10.1", + "deprecated": null + }, "Datastore.mode": { "added": "pre-v0.10.1", "deprecated": null @@ -6073,6 +6103,10 @@ "added": "v23.11", "deprecated": null }, + "DatastoreUsage.key[]": { + "added": "v23.11", + "deprecated": null + }, "Decode": { "added": "v23.05", "deprecated": null @@ -6829,6 +6863,10 @@ "added": "pre-v0.10.1", "deprecated": null }, + "DelDatastore.key[]": { + "added": "pre-v0.10.1", + "deprecated": null + }, "DelDatastore.string": { "added": "pre-v0.10.1", "deprecated": null @@ -8357,6 +8395,10 @@ "added": "pre-v0.10.1", "deprecated": null }, + "Invoice.exposeprivatechannels[]": { + "added": "pre-v0.10.1", + "deprecated": null + }, "Invoice.fallbacks[]": { "added": "pre-v0.10.1", "deprecated": null @@ -10185,6 +10227,10 @@ "added": "pre-v0.10.1", "deprecated": null }, + "ListDatastore.key[]": { + "added": "pre-v0.10.1", + "deprecated": null + }, "ListForwards": { "added": "pre-v0.10.1", "deprecated": null diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 639c51614df8..0dabc75ed6e7 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -586,7 +586,10 @@ message ConnectAddress { message CreateinvoiceRequest { string invstring = 1; - string label = 2; + oneof label { + string label_string = 4; + sint64 label_int = 5; + } bytes preimage = 3; } @@ -620,6 +623,10 @@ message CreateinvoicePaidOutpoint { uint32 outnum = 2; } +message DatastoreRequestarr_stringWrapper { + repeated string items = 1; +} + message DatastoreRequest { // Datastore.mode enum DatastoreMode { @@ -632,19 +639,29 @@ message DatastoreRequest { optional bytes hex = 2; optional DatastoreMode mode = 3; optional uint64 generation = 4; - repeated string key = 5; + oneof key { + DatastoreRequestarr_stringWrapper key_arr_string = 7; + string key_string = 8; + } optional string string = 6; } message DatastoreResponse { + repeated string key = 1; optional uint64 generation = 2; optional bytes hex = 3; optional string string = 4; - repeated string key = 5; +} + +message DatastoreusageRequestarr_stringWrapper { + repeated string items = 1; } message DatastoreusageRequest { - repeated string key = 1; + oneof key { + DatastoreusageRequestarr_stringWrapper key_arr_string = 2; + string key_string = 3; + } } message DatastoreusageResponse { @@ -673,16 +690,23 @@ message CreateonionHops { bytes payload = 2; } +message DeldatastoreRequestarr_stringWrapper { + repeated string items = 1; +} + message DeldatastoreRequest { optional uint64 generation = 2; - repeated string key = 3; + oneof key { + DeldatastoreRequestarr_stringWrapper key_arr_string = 4; + string key_string = 5; + } } message DeldatastoreResponse { + repeated string key = 1; optional uint64 generation = 2; optional bytes hex = 3; optional string string = 4; - repeated string key = 5; } message DelinvoiceRequest { @@ -692,7 +716,10 @@ message DelinvoiceRequest { EXPIRED = 1; UNPAID = 2; } - string label = 1; + oneof label { + string label_string = 4; + uint64 label_u64 = 5; + } DelinvoiceStatus status = 2; optional bool desconly = 3; } @@ -780,14 +807,25 @@ message RecoverchannelResponse { repeated string stubs = 1; } +message InvoiceRequestarr_scidWrapper { + repeated string items = 1; +} + message InvoiceRequest { string description = 2; - string label = 3; + oneof label { + string label_string = 11; + sint64 label_int = 12; + } repeated string fallbacks = 4; optional bytes preimage = 5; optional uint32 cltv = 6; optional uint64 expiry = 7; - repeated string exposeprivatechannels = 8; + oneof exposeprivatechannels { + bool exposeprivatechannels_bool = 13; + InvoiceRequestarr_scidWrapper exposeprivatechannels_arr_scid = 14; + string exposeprivatechannels_scid = 15; + } optional bool deschashonly = 9; AmountOrAny amount_msat = 10; } @@ -854,8 +892,15 @@ message ListinvoicerequestsInvoicerequests { optional string label = 6; } +message ListdatastoreRequestarr_stringWrapper { + repeated string items = 1; +} + message ListdatastoreRequest { - repeated string key = 2; + oneof key { + ListdatastoreRequestarr_stringWrapper key_arr_string = 3; + string key_string = 4; + } } message ListdatastoreResponse { @@ -875,7 +920,10 @@ message ListinvoicesRequest { CREATED = 0; UPDATED = 1; } - optional string label = 1; + oneof label { + string label_string = 8; + sint64 label_int = 9; + } optional string invstring = 2; optional bytes payment_hash = 3; optional string offer_id = 4; @@ -1163,7 +1211,10 @@ message WaitanyinvoicePaidOutpoint { } message WaitinvoiceRequest { - string label = 1; + oneof label { + string label_string = 2; + sint64 label_int = 3; + } } message WaitinvoiceResponse { @@ -2416,7 +2467,10 @@ message MultiwithdrawResponse { } message OfferRequest { - string amount = 1; + oneof amount { + AmountOrAny amount_msat_or_any = 16; + string amount_currency = 17; + } optional string description = 2; optional string issuer = 3; optional string label = 4; @@ -2706,7 +2760,11 @@ message SetchannelChannels { message SetconfigRequest { string config = 1; - optional string val = 2; + oneof val { + string val_string = 4; + sint64 val_int = 5; + bool val_bool = 6; + } optional bool transient = 3; } diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 21775bada8da..05c4349fab08 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -526,7 +526,7 @@ impl From for pb::DatastoreResponse { Self { generation: c.generation, // Rule #2 for type u64? hex: c.hex.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? - // Field: Datastore.key + // Field: Datastore.key[] key: c.key.into_iter().map(|i| i.into()).collect(), // Rule #3 for type string string: c.string, // Rule #2 for type string? } @@ -569,7 +569,7 @@ impl From for pb::DeldatastoreResponse { Self { generation: c.generation, // Rule #2 for type u64? hex: c.hex.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? - // Field: DelDatastore.key + // Field: DelDatastore.key[] key: c.key.into_iter().map(|i| i.into()).collect(), // Rule #3 for type string string: c.string, // Rule #2 for type string? } @@ -4799,37 +4799,62 @@ impl From for pb::ConnectRequest { } } +impl From for pb::createinvoice_request::Label { + fn from(c: requests::CreateinvoiceLabel) -> Self { + match c { +requests::CreateinvoiceLabel::String(v) => pb::createinvoice_request::Label::LabelString(v), +requests::CreateinvoiceLabel::Int(v) => pb::createinvoice_request::Label::LabelInt(v), + } + } +} + #[allow(unused_variables)] impl From for pb::CreateinvoiceRequest { fn from(c: requests::CreateinvoiceRequest) -> Self { Self { invstring: c.invstring, // Rule #2 for type string - label: c.label, // Rule #2 for type string + label: Some(c.label.into()), preimage: hex::decode(&c.preimage).unwrap(), // Rule #2 for type hex } } } +impl From for pb::datastore_request::Key { + fn from(c: requests::DatastoreKey) -> Self { + match c { +requests::DatastoreKey::ArrayOfString(v) => pb::datastore_request::Key::KeyArrString(pb::DatastoreRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::DatastoreKey::String(v) => pb::datastore_request::Key::KeyString(v), + } + } +} + #[allow(unused_variables)] impl From for pb::DatastoreRequest { fn from(c: requests::DatastoreRequest) -> Self { Self { generation: c.generation, // Rule #2 for type u64? hex: c.hex.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? - // Field: Datastore.key - key: c.key.into_iter().map(|i| i.into()).collect(), // Rule #3 for type string + key: Some(c.key.into()), mode: c.mode.map(|v| v as i32), string: c.string, // Rule #2 for type string? } } } +impl From for pb::datastoreusage_request::Key { + fn from(c: requests::DatastoreusageKey) -> Self { + match c { +requests::DatastoreusageKey::ArrayOfString(v) => pb::datastoreusage_request::Key::KeyArrString(pb::DatastoreusageRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::DatastoreusageKey::String(v) => pb::datastoreusage_request::Key::KeyString(v), + } + } +} + #[allow(unused_variables)] impl From for pb::DatastoreusageRequest { fn from(c: requests::DatastoreusageRequest) -> Self { Self { - // Field: DatastoreUsage.key - key: c.key.map(|arr| arr.into_iter().map(|i| i.into()).collect()).unwrap_or(vec![]), // Rule #3 + key: c.key.map(|v| v.into()), } } } @@ -4857,13 +4882,30 @@ impl From for pb::CreateonionRequest { } } +impl From for pb::deldatastore_request::Key { + fn from(c: requests::DeldatastoreKey) -> Self { + match c { +requests::DeldatastoreKey::ArrayOfString(v) => pb::deldatastore_request::Key::KeyArrString(pb::DeldatastoreRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::DeldatastoreKey::String(v) => pb::deldatastore_request::Key::KeyString(v), + } + } +} + #[allow(unused_variables)] impl From for pb::DeldatastoreRequest { fn from(c: requests::DeldatastoreRequest) -> Self { Self { generation: c.generation, // Rule #2 for type u64? - // Field: DelDatastore.key - key: c.key.into_iter().map(|i| i.into()).collect(), // Rule #3 for type string + key: Some(c.key.into()), + } + } +} + +impl From for pb::delinvoice_request::Label { + fn from(c: requests::DelinvoiceLabel) -> Self { + match c { +requests::DelinvoiceLabel::String(v) => pb::delinvoice_request::Label::LabelString(v), +requests::DelinvoiceLabel::U64(v) => pb::delinvoice_request::Label::LabelU64(v), } } } @@ -4873,7 +4915,7 @@ impl From for pb::DelinvoiceRequest { fn from(c: requests::DelinvoiceRequest) -> Self { Self { desconly: c.desconly, // Rule #2 for type boolean? - label: c.label, // Rule #2 for type string + label: Some(c.label.into()), status: c.status as i32, } } @@ -4936,6 +4978,25 @@ impl From for pb::RecoverchannelRequest { } } +impl From for pb::invoice_request::Exposeprivatechannels { + fn from(c: requests::InvoiceExposeprivatechannels) -> Self { + match c { +requests::InvoiceExposeprivatechannels::Bool(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsBool(v), +requests::InvoiceExposeprivatechannels::ArrayOfShortChannelId(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsArrScid(pb::InvoiceRequestArrScidWrapper { items: v.into_iter().map(|i| i.to_string()).collect() }), +requests::InvoiceExposeprivatechannels::ShortChannelId(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsScid(v.to_string()), + } + } +} + +impl From for pb::invoice_request::Label { + fn from(c: requests::InvoiceLabel) -> Self { + match c { +requests::InvoiceLabel::String(v) => pb::invoice_request::Label::LabelString(v), +requests::InvoiceLabel::Int(v) => pb::invoice_request::Label::LabelInt(v), + } + } +} + #[allow(unused_variables)] impl From for pb::InvoiceRequest { fn from(c: requests::InvoiceRequest) -> Self { @@ -4945,11 +5006,10 @@ impl From for pb::InvoiceRequest { deschashonly: c.deschashonly, // Rule #2 for type boolean? description: c.description, // Rule #2 for type string expiry: c.expiry, // Rule #2 for type u64? - // Field: Invoice.exposeprivatechannels - exposeprivatechannels: c.exposeprivatechannels.map(|arr| arr.into_iter().map(|i| i.to_string()).collect()).unwrap_or(vec![]), // Rule #3 + exposeprivatechannels: c.exposeprivatechannels.map(|v| v.into()), // Field: Invoice.fallbacks[] fallbacks: c.fallbacks.map(|arr| arr.into_iter().map(|i| i.into()).collect()).unwrap_or(vec![]), // Rule #3 - label: c.label, // Rule #2 for type string + label: Some(c.label.into()), preimage: c.preimage.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? } } @@ -4988,12 +5048,29 @@ impl From for pb::ListinvoicerequestsReque } } +impl From for pb::listdatastore_request::Key { + fn from(c: requests::ListdatastoreKey) -> Self { + match c { +requests::ListdatastoreKey::ArrayOfString(v) => pb::listdatastore_request::Key::KeyArrString(pb::ListdatastoreRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::ListdatastoreKey::String(v) => pb::listdatastore_request::Key::KeyString(v), + } + } +} + #[allow(unused_variables)] impl From for pb::ListdatastoreRequest { fn from(c: requests::ListdatastoreRequest) -> Self { Self { - // Field: ListDatastore.key - key: c.key.map(|arr| arr.into_iter().map(|i| i.into()).collect()).unwrap_or(vec![]), // Rule #3 + key: c.key.map(|v| v.into()), + } + } +} + +impl From for pb::listinvoices_request::Label { + fn from(c: requests::ListinvoicesLabel) -> Self { + match c { +requests::ListinvoicesLabel::String(v) => pb::listinvoices_request::Label::LabelString(v), +requests::ListinvoicesLabel::Int(v) => pb::listinvoices_request::Label::LabelInt(v), } } } @@ -5004,7 +5081,7 @@ impl From for pb::ListinvoicesRequest { Self { index: c.index.map(|v| v as i32), invstring: c.invstring, // Rule #2 for type string? - label: c.label, // Rule #2 for type string? + label: c.label.map(|v| v.into()), limit: c.limit, // Rule #2 for type u32? offer_id: c.offer_id, // Rule #2 for type string? payment_hash: c.payment_hash.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? @@ -5119,11 +5196,20 @@ impl From for pb::WaitanyinvoiceRequest { } } +impl From for pb::waitinvoice_request::Label { + fn from(c: requests::WaitinvoiceLabel) -> Self { + match c { +requests::WaitinvoiceLabel::String(v) => pb::waitinvoice_request::Label::LabelString(v), +requests::WaitinvoiceLabel::Int(v) => pb::waitinvoice_request::Label::LabelInt(v), + } + } +} + #[allow(unused_variables)] impl From for pb::WaitinvoiceRequest { fn from(c: requests::WaitinvoiceRequest) -> Self { Self { - label: c.label, // Rule #2 for type string + label: Some(c.label.into()), } } } @@ -5620,12 +5706,21 @@ impl From for pb::MultiwithdrawRequest { } } +impl From for pb::offer_request::Amount { + fn from(c: requests::OfferAmount) -> Self { + match c { +requests::OfferAmount::MsatOrAny(v) => pb::offer_request::Amount::AmountMsatOrAny(v.into()), +requests::OfferAmount::Currency(v) => pb::offer_request::Amount::AmountCurrency(v), + } + } +} + #[allow(unused_variables)] impl From for pb::OfferRequest { fn from(c: requests::OfferRequest) -> Self { Self { absolute_expiry: c.absolute_expiry, // Rule #2 for type u64? - amount: c.amount, // Rule #2 for type string + amount: Some(c.amount.into()), description: c.description, // Rule #2 for type string? // Field: Offer.fronting_nodes[] fronting_nodes: c.fronting_nodes.map(|arr| arr.into_iter().map(|i| i.serialize().to_vec()).collect()).unwrap_or(vec![]), // Rule #3 @@ -5803,13 +5898,23 @@ impl From for pb::SetchannelRequest { } } +impl From for pb::setconfig_request::Val { + fn from(c: requests::SetconfigVal) -> Self { + match c { +requests::SetconfigVal::String(v) => pb::setconfig_request::Val::ValString(v), +requests::SetconfigVal::Int(v) => pb::setconfig_request::Val::ValInt(v), +requests::SetconfigVal::Bool(v) => pb::setconfig_request::Val::ValBool(v), + } + } +} + #[allow(unused_variables)] impl From for pb::SetconfigRequest { fn from(c: requests::SetconfigRequest) -> Self { Self { config: c.config, // Rule #2 for type string transient: c.transient, // Rule #2 for type boolean? - val: c.val, // Rule #2 for type string? + val: c.val.map(|v| v.into()), } } } @@ -6610,35 +6715,62 @@ impl From for requests::ConnectRequest { } } +impl From for requests::CreateinvoiceLabel { + fn from(c: pb::createinvoice_request::Label) -> Self { + match c { +pb::createinvoice_request::Label::LabelString(v) => requests::CreateinvoiceLabel::String(v), +pb::createinvoice_request::Label::LabelInt(v) => requests::CreateinvoiceLabel::Int(v), + } + } +} + #[allow(unused_variables)] impl From for requests::CreateinvoiceRequest { fn from(c: pb::CreateinvoiceRequest) -> Self { Self { invstring: c.invstring, // Rule #1 for type string - label: c.label, // Rule #1 for type string + label: c.label.unwrap().into(), preimage: hex::encode(&c.preimage), // Rule #1 for type hex } } } +impl From for requests::DatastoreKey { + fn from(c: pb::datastore_request::Key) -> Self { + match c { +pb::datastore_request::Key::KeyArrString(v) => requests::DatastoreKey::ArrayOfString(v.items.into_iter().map(|s| s.into()).collect()), +pb::datastore_request::Key::KeyString(v) => requests::DatastoreKey::String(v), + } + } +} + #[allow(unused_variables)] impl From for requests::DatastoreRequest { fn from(c: pb::DatastoreRequest) -> Self { Self { generation: c.generation, // Rule #1 for type u64? hex: c.hex.map(|v| hex::encode(v)), // Rule #1 for type hex? - key: c.key.into_iter().map(|s| s.into()).collect(), // Rule #4 + key: c.key.unwrap().into(), mode: c.mode.map(|v| v.try_into().unwrap()), string: c.string, // Rule #1 for type string? } } } +impl From for requests::DatastoreusageKey { + fn from(c: pb::datastoreusage_request::Key) -> Self { + match c { +pb::datastoreusage_request::Key::KeyArrString(v) => requests::DatastoreusageKey::ArrayOfString(v.items.into_iter().map(|s| s.into()).collect()), +pb::datastoreusage_request::Key::KeyString(v) => requests::DatastoreusageKey::String(v), + } + } +} + #[allow(unused_variables)] impl From for requests::DatastoreusageRequest { fn from(c: pb::DatastoreusageRequest) -> Self { Self { - key: Some(c.key.into_iter().map(|s| s.into()).collect()), // Rule #4 + key: c.key.map(|v| v.into()), } } } @@ -6665,12 +6797,30 @@ impl From for requests::CreateonionRequest { } } +impl From for requests::DeldatastoreKey { + fn from(c: pb::deldatastore_request::Key) -> Self { + match c { +pb::deldatastore_request::Key::KeyArrString(v) => requests::DeldatastoreKey::ArrayOfString(v.items.into_iter().map(|s| s.into()).collect()), +pb::deldatastore_request::Key::KeyString(v) => requests::DeldatastoreKey::String(v), + } + } +} + #[allow(unused_variables)] impl From for requests::DeldatastoreRequest { fn from(c: pb::DeldatastoreRequest) -> Self { Self { generation: c.generation, // Rule #1 for type u64? - key: c.key.into_iter().map(|s| s.into()).collect(), // Rule #4 + key: c.key.unwrap().into(), + } + } +} + +impl From for requests::DelinvoiceLabel { + fn from(c: pb::delinvoice_request::Label) -> Self { + match c { +pb::delinvoice_request::Label::LabelString(v) => requests::DelinvoiceLabel::String(v), +pb::delinvoice_request::Label::LabelU64(v) => requests::DelinvoiceLabel::U64(v), } } } @@ -6680,7 +6830,7 @@ impl From for requests::DelinvoiceRequest { fn from(c: pb::DelinvoiceRequest) -> Self { Self { desconly: c.desconly, // Rule #1 for type boolean? - label: c.label, // Rule #1 for type string + label: c.label.unwrap().into(), status: c.status.try_into().unwrap(), } } @@ -6742,6 +6892,25 @@ impl From for requests::RecoverchannelRequest { } } +impl From for requests::InvoiceExposeprivatechannels { + fn from(c: pb::invoice_request::Exposeprivatechannels) -> Self { + match c { +pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsBool(v) => requests::InvoiceExposeprivatechannels::Bool(v), +pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsArrScid(v) => requests::InvoiceExposeprivatechannels::ArrayOfShortChannelId(v.items.into_iter().map(|s| cln_rpc::primitives::ShortChannelId::from_str(&s).unwrap()).collect()), +pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsScid(v) => requests::InvoiceExposeprivatechannels::ShortChannelId(cln_rpc::primitives::ShortChannelId::from_str(&v).unwrap()), + } + } +} + +impl From for requests::InvoiceLabel { + fn from(c: pb::invoice_request::Label) -> Self { + match c { +pb::invoice_request::Label::LabelString(v) => requests::InvoiceLabel::String(v), +pb::invoice_request::Label::LabelInt(v) => requests::InvoiceLabel::Int(v), + } + } +} + #[allow(unused_variables)] impl From for requests::InvoiceRequest { fn from(c: pb::InvoiceRequest) -> Self { @@ -6751,9 +6920,9 @@ impl From for requests::InvoiceRequest { deschashonly: c.deschashonly, // Rule #1 for type boolean? description: c.description, // Rule #1 for type string expiry: c.expiry, // Rule #1 for type u64? - exposeprivatechannels: Some(c.exposeprivatechannels.into_iter().map(|s| cln_rpc::primitives::ShortChannelId::from_str(&s).unwrap()).collect()), // Rule #4 + exposeprivatechannels: c.exposeprivatechannels.map(|v| v.into()), fallbacks: Some(c.fallbacks.into_iter().map(|s| s.into()).collect()), // Rule #4 - label: c.label, // Rule #1 for type string + label: c.label.unwrap().into(), preimage: c.preimage.map(|v| hex::encode(v)), // Rule #1 for type hex? } } @@ -6792,11 +6961,29 @@ impl From for requests::ListinvoicerequestsReque } } +impl From for requests::ListdatastoreKey { + fn from(c: pb::listdatastore_request::Key) -> Self { + match c { +pb::listdatastore_request::Key::KeyArrString(v) => requests::ListdatastoreKey::ArrayOfString(v.items.into_iter().map(|s| s.into()).collect()), +pb::listdatastore_request::Key::KeyString(v) => requests::ListdatastoreKey::String(v), + } + } +} + #[allow(unused_variables)] impl From for requests::ListdatastoreRequest { fn from(c: pb::ListdatastoreRequest) -> Self { Self { - key: Some(c.key.into_iter().map(|s| s.into()).collect()), // Rule #4 + key: c.key.map(|v| v.into()), + } + } +} + +impl From for requests::ListinvoicesLabel { + fn from(c: pb::listinvoices_request::Label) -> Self { + match c { +pb::listinvoices_request::Label::LabelString(v) => requests::ListinvoicesLabel::String(v), +pb::listinvoices_request::Label::LabelInt(v) => requests::ListinvoicesLabel::Int(v), } } } @@ -6807,7 +6994,7 @@ impl From for requests::ListinvoicesRequest { Self { index: c.index.map(|v| v.try_into().unwrap()), invstring: c.invstring, // Rule #1 for type string? - label: c.label, // Rule #1 for type string? + label: c.label.map(|v| v.into()), limit: c.limit, // Rule #1 for type u32? offer_id: c.offer_id, // Rule #1 for type string? payment_hash: c.payment_hash.map(|v| hex::encode(v)), // Rule #1 for type hex? @@ -6920,11 +7107,20 @@ impl From for requests::WaitanyinvoiceRequest { } } +impl From for requests::WaitinvoiceLabel { + fn from(c: pb::waitinvoice_request::Label) -> Self { + match c { +pb::waitinvoice_request::Label::LabelString(v) => requests::WaitinvoiceLabel::String(v), +pb::waitinvoice_request::Label::LabelInt(v) => requests::WaitinvoiceLabel::Int(v), + } + } +} + #[allow(unused_variables)] impl From for requests::WaitinvoiceRequest { fn from(c: pb::WaitinvoiceRequest) -> Self { Self { - label: c.label, // Rule #1 for type string + label: c.label.unwrap().into(), } } } @@ -7408,12 +7604,21 @@ impl From for requests::MultiwithdrawRequest { } } +impl From for requests::OfferAmount { + fn from(c: pb::offer_request::Amount) -> Self { + match c { +pb::offer_request::Amount::AmountMsatOrAny(v) => requests::OfferAmount::MsatOrAny(v.into()), +pb::offer_request::Amount::AmountCurrency(v) => requests::OfferAmount::Currency(v), + } + } +} + #[allow(unused_variables)] impl From for requests::OfferRequest { fn from(c: pb::OfferRequest) -> Self { Self { absolute_expiry: c.absolute_expiry, // Rule #1 for type u64? - amount: c.amount, // Rule #1 for type string + amount: c.amount.unwrap().into(), description: c.description, // Rule #1 for type string? fronting_nodes: Some(c.fronting_nodes.into_iter().map(|s| PublicKey::from_slice(&s).unwrap()).collect()), // Rule #4 issuer: c.issuer, // Rule #1 for type string? @@ -7587,13 +7792,23 @@ impl From for requests::SetchannelRequest { } } +impl From for requests::SetconfigVal { + fn from(c: pb::setconfig_request::Val) -> Self { + match c { +pb::setconfig_request::Val::ValString(v) => requests::SetconfigVal::String(v), +pb::setconfig_request::Val::ValInt(v) => requests::SetconfigVal::Int(v), +pb::setconfig_request::Val::ValBool(v) => requests::SetconfigVal::Bool(v), + } + } +} + #[allow(unused_variables)] impl From for requests::SetconfigRequest { fn from(c: pb::SetconfigRequest) -> Self { Self { config: c.config, // Rule #1 for type string transient: c.transient, // Rule #1 for type boolean? - val: c.val, // Rule #1 for type string? + val: c.val.map(|v| v.into()), } } } diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index 6e666cdf47bf..ea061624c0f0 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -2,7 +2,7 @@ // This file was automatically generated using the following command: // // ```bash -// contrib/msggen/msggen/__main__.py -s generate +// -c // ``` // // Do not edit this file, it'll be overwritten. Rather edit the schema that @@ -773,10 +773,17 @@ pub mod requests { "connect" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum CreateinvoiceLabel { + String(String), + Int(i64), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateinvoiceRequest { pub invstring: String, - pub label: String, + pub label: CreateinvoiceLabel, pub preimage: String, } @@ -839,6 +846,13 @@ pub mod requests { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum DatastoreKey { + ArrayOfString(Vec), + String(String), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DatastoreRequest { #[serde(skip_serializing_if = "Option::is_none")] @@ -849,7 +863,7 @@ pub mod requests { pub mode: Option, #[serde(skip_serializing_if = "Option::is_none")] pub string: Option, - pub key: Vec, + pub key: DatastoreKey, } impl From for Request { @@ -869,10 +883,17 @@ pub mod requests { "datastore" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum DatastoreusageKey { + ArrayOfString(Vec), + String(String), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DatastoreusageRequest { - #[serde(skip_serializing_if = "crate::is_none_or_empty")] - pub key: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub key: Option, } impl From for Request { @@ -925,11 +946,18 @@ pub mod requests { "createonion" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum DeldatastoreKey { + ArrayOfString(Vec), + String(String), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DeldatastoreRequest { #[serde(skip_serializing_if = "Option::is_none")] pub generation: Option, - pub key: Vec, + pub key: DeldatastoreKey, } impl From for Request { @@ -983,13 +1011,20 @@ pub mod requests { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum DelinvoiceLabel { + String(String), + U64(u64), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DelinvoiceRequest { #[serde(skip_serializing_if = "Option::is_none")] pub desconly: Option, // Path `DelInvoice.status` pub status: DelinvoiceStatus, - pub label: String, + pub label: DelinvoiceLabel, } impl From for Request { @@ -1147,6 +1182,21 @@ pub mod requests { "recoverchannel" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum InvoiceExposeprivatechannels { + Bool(bool), + ArrayOfShortChannelId(Vec), + ShortChannelId(ShortChannelId), + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum InvoiceLabel { + String(String), + Int(i64), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct InvoiceRequest { #[serde(skip_serializing_if = "Option::is_none")] @@ -1156,14 +1206,14 @@ pub mod requests { #[serde(skip_serializing_if = "Option::is_none")] pub expiry: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub exposeprivatechannels: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub preimage: Option, #[serde(skip_serializing_if = "crate::is_none_or_empty")] - pub exposeprivatechannels: Option>, - #[serde(skip_serializing_if = "crate::is_none_or_empty")] pub fallbacks: Option>, pub amount_msat: AmountOrAny, pub description: String, - pub label: String, + pub label: InvoiceLabel, } impl From for Request { @@ -1261,10 +1311,17 @@ pub mod requests { "listinvoicerequests" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum ListdatastoreKey { + ArrayOfString(Vec), + String(String), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ListdatastoreRequest { - #[serde(skip_serializing_if = "crate::is_none_or_empty")] - pub key: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub key: Option, } impl From for Request { @@ -1314,6 +1371,13 @@ pub mod requests { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum ListinvoicesLabel { + String(String), + Int(i64), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ListinvoicesRequest { #[serde(skip_serializing_if = "Option::is_none")] @@ -1321,7 +1385,7 @@ pub mod requests { #[serde(skip_serializing_if = "Option::is_none")] pub invstring: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, + pub label: Option, #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -1637,9 +1701,16 @@ pub mod requests { "waitanyinvoice" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum WaitinvoiceLabel { + String(String), + Int(i64), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct WaitinvoiceRequest { - pub label: String, + pub label: WaitinvoiceLabel, } impl From for Request { @@ -3117,6 +3188,13 @@ pub mod requests { "multiwithdraw" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum OfferAmount { + MsatOrAny(AmountOrAny), + Currency(String), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct OfferRequest { #[serde(skip_serializing_if = "Option::is_none")] @@ -3145,7 +3223,7 @@ pub mod requests { pub single_use: Option, #[serde(skip_serializing_if = "crate::is_none_or_empty")] pub fronting_nodes: Option>, - pub amount: String, + pub amount: OfferAmount, } impl From for Request { @@ -3525,12 +3603,20 @@ pub mod requests { "setchannel" } } + #[derive(Clone, Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum SetconfigVal { + String(String), + Int(i64), + Bool(bool), + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SetconfigRequest { #[serde(skip_serializing_if = "Option::is_none")] pub transient: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub val: Option, + pub val: Option, pub config: String, } diff --git a/contrib/msggen/msggen/checks.py b/contrib/msggen/msggen/checks.py index 26818370bd30..934ba776ed13 100644 --- a/contrib/msggen/msggen/checks.py +++ b/contrib/msggen/msggen/checks.py @@ -15,6 +15,10 @@ def recurse(f: model.Field): if isinstance(f, model.ArrayField): self.visit(f.itemtype) recurse(f.itemtype) + elif isinstance(f, model.UnionField): + for v in f.variants: + self.visit(v) + recurse(v) elif isinstance(f, model.CompositeField): for c in f.fields: self.visit(c) diff --git a/contrib/msggen/msggen/gen/grpc/convert.py b/contrib/msggen/msggen/gen/grpc/convert.py index a65c447db4fc..49c8eb1ba06b 100644 --- a/contrib/msggen/msggen/gen/grpc/convert.py +++ b/contrib/msggen/msggen/gen/grpc/convert.py @@ -1,6 +1,7 @@ # A grpc model -from msggen.model import ArrayField, CompositeField, EnumField, PrimitiveField, Service -from msggen.gen.grpc.util import notification_typename_overrides +from msggen.model import ArrayField, CompositeField, EnumField, PrimitiveField, UnionField, Service +from msggen.gen.grpc.util import notification_typename_overrides, camel_to_snake, union_variant_suffix +from msggen.gen.rpc.rust import union_variant_name from msggen.gen import IGenerator from typing import TextIO from textwrap import indent, dedent @@ -17,6 +18,86 @@ def generate_array(self, prefix, field: ArrayField, override): if isinstance(field.itemtype, CompositeField): self.generate_composite(prefix, field.itemtype, override) + def union_variant_conversion(self, f, val="v"): + """Generate the conversion expression for a single union variant value.""" + if isinstance(f, PrimitiveField): + mapping = { + "short_channel_id": f"{val}.to_string()", + "short_channel_id_dir": f"{val}.to_string()", + "pubkey": f"{val}.serialize().to_vec()", + "hex": f"hex::decode({val}).unwrap()", + "txid": f"hex::decode({val}).unwrap()", + "hash": f">::as_ref(&{val}).to_vec()", + "secret": f"{val}.to_vec()", + "msat": f"{val}.into()", + "msat_or_all": f"{val}.into()", + "msat_or_any": f"{val}.into()", + "sat": f"{val}.into()", + "sat_or_all": f"{val}.into()", + "feerate": f"{val}.into()", + "outpoint": f"{val}.into()", + }.get(f.typename, val) + return mapping + elif isinstance(f, ArrayField): + inner_mapping = { + "short_channel_id": "i.to_string()", + "short_channel_id_dir": "i.to_string()", + "pubkey": "i.serialize().to_vec()", + "hex": "hex::decode(i).unwrap()", + "txid": "hex::decode(i).unwrap()", + }.get(f.itemtype.typename, "i.into()") + return f"{val}.into_iter().map(|i| {inner_mapping}).collect()" + elif isinstance(f, EnumField): + return f"{val} as i32" + elif isinstance(f, CompositeField): + return f"{val}.into()" + return val + + def generate_union(self, prefix, field: UnionField, parent_typename, override=None): + """Generate From impl for a union type (cln-rpc enum -> pb oneof).""" + if override is None: + override = lambda x: x + + typename = str(field.typename) + pbname = override(self.to_camel_case(str(override(parent_typename)))) + pb_mod = camel_to_snake(pbname) + oneof_name = field.normalized() + # The prost enum name is CamelCase of the oneof field name + pb_oneof_enum = self.to_camel_case(oneof_name[0].upper() + oneof_name[1:]) + + self.write( + f"""\ + impl From<{prefix}::{typename}> for pb::{pb_mod}::{pb_oneof_enum} {{ + fn from(c: {prefix}::{typename}) -> Self {{ + match c {{ + """ + ) + + for v in field.variants: + vname = union_variant_name(v) + suffix = union_variant_suffix(v) + pb_variant = self.to_camel_case(f"{oneof_name}_{suffix}") + pb_variant = pb_variant[0].upper() + pb_variant[1:] + if isinstance(v, ArrayField): + wrapper_name = override(f"{parent_typename}{suffix.capitalize()}Wrapper") + wrapper_pb = self.to_camel_case(str(wrapper_name)) + self.write( + f" {prefix}::{typename}::{vname}(v) => pb::{pb_mod}::{pb_oneof_enum}::{pb_variant}(pb::{wrapper_pb} {{ items: v.into_iter().map(|i| {self.union_variant_conversion(v.itemtype, 'i')}).collect() }}),\n" + ) + else: + self.write( + f" {prefix}::{typename}::{vname}(v) => pb::{pb_mod}::{pb_oneof_enum}::{pb_variant}({self.union_variant_conversion(v)}),\n" + ) + + self.write( + f"""\ + }} + }} + }} + + """ + ) + def generate_composite(self, prefix, field: CompositeField, override=None): """Generates the conversions from JSON-RPC to GRPC.""" if field.omit(): @@ -34,6 +115,8 @@ def generate_composite(self, prefix, field: CompositeField, override=None): self.generate_array(prefix, f, override) elif isinstance(f, CompositeField): self.generate_composite(prefix, f, override) + elif isinstance(f, UnionField): + self.generate_union(prefix, f, str(field.typename), override) pbname = override(self.to_camel_case(str(override(field.typename)))) @@ -148,6 +231,13 @@ def generate_composite(self, prefix, field: CompositeField, override=None): else: rhs = f"c.{name}.map(|v| v.into())" self.write(f"{name}: {rhs},\n", numindent=3) + + elif isinstance(f, UnionField): + if not f.optional: + self.write(f"{name}: Some(c.{name}.into()),\n", numindent=3) + else: + self.write(f"{name}: c.{name}.map(|v| v.into()),\n", numindent=3) + self.write( f"""\ }} diff --git a/contrib/msggen/msggen/gen/grpc/proto.py b/contrib/msggen/msggen/gen/grpc/proto.py index 3f2b981ab644..77ca1955928a 100644 --- a/contrib/msggen/msggen/gen/grpc/proto.py +++ b/contrib/msggen/msggen/gen/grpc/proto.py @@ -8,6 +8,7 @@ typemap, method_name_overrides, notification_typename_overrides, + union_variant_suffix, ) from msggen.model import ( ArrayField, @@ -15,6 +16,7 @@ CompositeField, EnumField, PrimitiveField, + UnionField, Service, MethodName, TypeName, @@ -177,6 +179,32 @@ def generate_enum(self, e: EnumField, indent=0, typename_override=None): self.write(f"""{prefix}}}\n""", False) + def union_variant_proto_type(self, f, parent_typename): + """Return the protobuf type name for a union variant field.""" + if isinstance(f, PrimitiveField): + return typemap.get(f.typename, f.typename) + elif isinstance(f, EnumField): + return str(f.typename) + elif isinstance(f, CompositeField): + return str(f.typename) + elif isinstance(f, ArrayField): + # oneof cannot contain repeated, so we need a wrapper message + return f"{parent_typename}{union_variant_suffix(f)}Wrapper" + return "bytes" + + def generate_union_wrapper_messages(self, u: UnionField, parent_typename, typename_override=None): + """Generate wrapper messages for array variants inside a union (oneof can't contain repeated).""" + if typename_override is None: + typename_override = lambda x: x + + for v in u.variants: + if isinstance(v, ArrayField): + wrapper_name = f"{parent_typename}{union_variant_suffix(v)}Wrapper" + item_type = typemap.get(v.itemtype.typename, v.itemtype.typename) + self.write(f"\nmessage {typename_override(wrapper_name)} {{\n", False) + self.write(f"\trepeated {item_type} items = 1;\n", False) + self.write(f"}}\n", False) + def generate_message(self, message: CompositeField, typename_override=None): if message.omit(): return @@ -186,6 +214,11 @@ def generate_message(self, message: CompositeField, typename_override=None): if typename_override is None: typename_override = lambda x: x + # Generate wrapper messages for any union fields first + for f in message.fields: + if isinstance(f, UnionField): + self.generate_union_wrapper_messages(f, str(message.typename), typename_override) + self.write( f""" message {typename_override(message.typename)} {{ @@ -203,7 +236,20 @@ def generate_message(self, message: CompositeField, typename_override=None): opt = "optional " if f.optional and not (isinstance(f, PrimitiveField) and f.typename == "string_map") else "" - if isinstance(f, ArrayField): + if isinstance(f, UnionField): + self.write(f"\toneof {f.normalized()} {{\n", False) + for v in f.variants: + suffix = union_variant_suffix(v) + vname = f"{f.normalized()}_{suffix}" + vtype = self.union_variant_proto_type(v, str(message.typename)) + vtype = typename_override(vtype) + # Allocate variant numbers in the parent message's namespace + parent = ".".join(f.path.split(".")[:-1]) + vfield = PrimitiveField(suffix, f"{parent}.{f.normalized()}_{suffix}", "", added=f.added, deprecated=f.deprecated) + vnum = self.field2number(message.typename, vfield) + self.write(f"\t\t{vtype} {vname} = {vnum};\n", False) + self.write(f"\t}}\n", False) + elif isinstance(f, ArrayField): typename = f.override( typemap.get(f.itemtype.typename, f.itemtype.typename) ) @@ -264,5 +310,8 @@ def gather_subfields(field: Field) -> List[Field]: elif isinstance(field, ArrayField): fields = [] fields.extend(gather_subfields(field.itemtype)) + elif isinstance(field, UnionField): + for v in field.variants: + fields.extend(gather_subfields(v)) return fields diff --git a/contrib/msggen/msggen/gen/grpc/unconvert.py b/contrib/msggen/msggen/gen/grpc/unconvert.py index 73984f75a47a..4c7a34abda23 100644 --- a/contrib/msggen/msggen/gen/grpc/unconvert.py +++ b/contrib/msggen/msggen/gen/grpc/unconvert.py @@ -1,6 +1,8 @@ # A grpc model -from msggen.model import ArrayField, CompositeField, EnumField, PrimitiveField, Service +from msggen.model import ArrayField, CompositeField, EnumField, PrimitiveField, UnionField, Service from msggen.gen.grpc.convert import GrpcConverterGenerator +from msggen.gen.grpc.util import camel_to_snake, union_variant_suffix +from msggen.gen.rpc.rust import union_variant_name class GrpcUnconverterGenerator(GrpcConverterGenerator): @@ -12,6 +14,79 @@ def generate(self, service: Service): # TODO Temporarily disabled since the use of overrides is lossy # self.generate_responses(service) + def unconvert_variant_value(self, f, val="v"): + """Generate the reverse conversion expression for a union variant value (pb -> cln-rpc).""" + if isinstance(f, PrimitiveField): + return { + "short_channel_id": f"cln_rpc::primitives::ShortChannelId::from_str(&{val}).unwrap()", + "short_channel_id_dir": f"cln_rpc::primitives::ShortChannelIdDir::from_str(&{val}).unwrap()", + "pubkey": f"PublicKey::from_slice(&{val}).unwrap()", + "hex": f"hex::encode({val})", + "txid": f"hex::encode({val})", + "hash": f"Sha256::from_slice(&{val}).unwrap()", + "secret": f"{val}.try_into().unwrap()", + "msat": f"{val}.into()", + "msat_or_all": f"{val}.into()", + "msat_or_any": f"{val}.into()", + "sat": f"{val}.into()", + "sat_or_all": f"{val}.into()", + "feerate": f"{val}.into()", + "outpoint": f"{val}.into()", + }.get(f.typename, val) + elif isinstance(f, ArrayField): + inner_mapping = { + "short_channel_id": "cln_rpc::primitives::ShortChannelId::from_str(&s).unwrap()", + "short_channel_id_dir": "cln_rpc::primitives::ShortChannelIdDir::from_str(&s).unwrap()", + "pubkey": "PublicKey::from_slice(&s).unwrap()", + "hex": "hex::encode(s)", + "txid": "hex::encode(s)", + }.get(f.itemtype.typename, "s.into()") + return f"{val}.items.into_iter().map(|s| {inner_mapping}).collect()" + elif isinstance(f, EnumField): + return f"{val}.try_into().unwrap()" + elif isinstance(f, CompositeField): + return f"{val}.into()" + return val + + def generate_union_unconvert(self, prefix, field: UnionField, parent_typename, override=None): + """Generate From impl for pb oneof -> cln-rpc union type.""" + if override is None: + override = lambda x: x + + typename = str(field.typename) + pbname = self.to_camel_case(str(parent_typename)) + pb_mod = camel_to_snake(pbname) + oneof_name = field.normalized() + pb_oneof_enum = self.to_camel_case(oneof_name[0].upper() + oneof_name[1:]) + + self.write( + f"""\ + impl From for {prefix}::{typename} {{ + fn from(c: pb::{pb_mod}::{pb_oneof_enum}) -> Self {{ + match c {{ + """ + ) + + for v in field.variants: + vname = union_variant_name(v) + suffix = union_variant_suffix(v) + pb_variant = self.to_camel_case(f"{oneof_name}_{suffix}") + pb_variant = pb_variant[0].upper() + pb_variant[1:] + conv = self.unconvert_variant_value(v) + + self.write( + f" pb::{pb_mod}::{pb_oneof_enum}::{pb_variant}(v) => {prefix}::{typename}::{vname}({conv}),\n" + ) + + self.write( + f"""\ + }} + }} + }} + + """ + ) + def generate_composite(self, prefix, field: CompositeField, override=None) -> None: # First pass: generate any sub-fields before we generate the # top-level field itself. @@ -26,6 +101,8 @@ def generate_composite(self, prefix, field: CompositeField, override=None) -> No self.generate_array(prefix, f, override) elif isinstance(f, CompositeField): self.generate_composite(prefix, f, override) + elif isinstance(f, UnionField): + self.generate_union_unconvert(prefix, f, str(field.typename), override) has_deprecated = any([f.deprecated for f in field.fields]) deprecated = ",deprecated" if has_deprecated else "" @@ -146,6 +223,12 @@ def generate_composite(self, prefix, field: CompositeField, override=None) -> No rhs = f"c.{name}.map(|v| v.into())" self.write(f"{name}: {rhs},\n", numindent=3) + elif isinstance(f, UnionField): + if not f.optional: + self.write(f"{name}: c.{name}.unwrap().into(),\n", numindent=3) + else: + self.write(f"{name}: c.{name}.map(|v| v.into()),\n", numindent=3) + self.write( f"""\ }} diff --git a/contrib/msggen/msggen/gen/grpc/util.py b/contrib/msggen/msggen/gen/grpc/util.py index 267691d59be6..0334dee5534c 100644 --- a/contrib/msggen/msggen/gen/grpc/util.py +++ b/contrib/msggen/msggen/gen/grpc/util.py @@ -78,3 +78,36 @@ def camel_to_snake(camel_case: str): snake = re.sub(r"(?'*field.dims}" + elif isinstance(field, EnumField): + return str(field.typename) + elif isinstance(field, CompositeField): + return str(field.typename) + else: + return "serde_json::Value" + + +def gen_union(u, meta): + defi, decl = "", "" + if u.omit(): + return "", "" + + typename = str(u.typename) + + # Generate sub-type declarations for composite/enum variants + for v in u.variants: + if isinstance(v, CompositeField): + _, vdecl = gen_composite(v, meta) + decl += vdecl + elif isinstance(v, EnumField): + _, vdecl = gen_enum(v, meta, None) + decl += vdecl + + decl += f"#[derive(Clone, Debug, Deserialize, Serialize)]\n" + decl += f"#[serde(untagged)]\n" + decl += f"pub enum {typename} {{\n" + + for v in u.variants: + vname = union_variant_name(v) + vtype = union_variant_type(v) + decl += f" {vname}({vtype}),\n" + + decl += "}\n\n" + + name = u.name.normalized() + org = str(u.name) + if u.deprecated: + defi += " #[deprecated]\n" + defi += rename_if_necessary(org, name) + if not u.optional: + defi += f" pub {name}: {typename},\n" + else: + defi += f' #[serde(skip_serializing_if = "Option::is_none")]\n' + defi += f" pub {name}: Option<{typename}>,\n" + + return defi, decl + + def gen_field(field, meta, override=None): if field.omit(): return ("", "") - if isinstance(field, CompositeField): + if isinstance(field, UnionField): + return gen_union(field, meta) + elif isinstance(field, CompositeField): return gen_composite(field, meta, override) elif isinstance(field, EnumField): return gen_enum(field, meta, override) @@ -240,6 +338,8 @@ def gen_array(a, meta, override=None): itemtype = a.itemtype.typename elif isinstance(a.itemtype, EnumField): itemtype = a.itemtype.typename + elif isinstance(a.itemtype, UnionField): + itemtype = a.itemtype.typename if itemtype is None: return ("", "") # Override said not to include diff --git a/contrib/msggen/msggen/model.py b/contrib/msggen/msggen/model.py index a48336a97145..d5df7e5eb1a5 100644 --- a/contrib/msggen/msggen/model.py +++ b/contrib/msggen/msggen/model.py @@ -157,6 +157,9 @@ def gather_subfields(field: Field) -> List[Field]: elif isinstance(field, ArrayField): fields = [] fields.extend(gather_subfields(field.itemtype)) + elif isinstance(field, UnionField): + for v in field.variants: + fields.extend(gather_subfields(v)) return fields @@ -282,6 +285,9 @@ def merge_dicts(dict1, dict2): if isinstance(field, ArrayField): field.itemtype.path = fpath + elif "oneOf" in ftype: + field = UnionField.from_js(ftype, fpath) + elif "type" not in ftype: logger.warning(f"Unmanaged {fpath}, it doesn't have a type") continue @@ -391,13 +397,15 @@ def from_js(cls, js, path): elif child_js["type"] in PrimitiveField.types: itemtype = PrimitiveField( - child_js["type"], path, child_js.get("description", "") + child_js["type"], path, child_js.get("description", ""), + added=child_js.get("added", None), deprecated=child_js.get("deprecated", None), ) elif child_js["type"] == "array": itemtype = ArrayField.from_js(path, child_js) variants.append(itemtype) - return UnionField(path, js.get('description', None), variants) + return UnionField(path, js.get('description', None), variants, + added=js.get('added', None), deprecated=js.get('deprecated', None)) class PrimitiveField(Field): @@ -495,11 +503,6 @@ def __str__(self): return f"Command[name={self.name}, fields=[{fieldnames}]]" -OfferStringField = PrimitiveField("string", None, None, added=None, deprecated=None) -InvoiceLabelField = PrimitiveField("string", None, None, added=None, deprecated=None) -DatastoreKeyField = ArrayField(itemtype=PrimitiveField("string", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added=None, deprecated=None) -DatastoreUsageKeyField = ArrayField(itemtype=PrimitiveField("string", None, None, added="v23.11", deprecated=None), dims=1, path=None, description=None, added="v23.11", deprecated=None) -InvoiceExposeprivatechannelsField = ArrayField(itemtype=PrimitiveField("short_channel_id", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added=None, deprecated=None) PayExclude = ArrayField(itemtype=PrimitiveField("string", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added=None, deprecated=None) RenePayExclude = ArrayField(itemtype=PrimitiveField("string", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added="v24.08", deprecated=None) RoutehintListField = PrimitiveField( @@ -509,7 +512,6 @@ def __str__(self): added=None, deprecated=None ) -SetConfigValField = PrimitiveField("string", None, None, added=None, deprecated=None) DecodeRoutehintListField = PrimitiveField( "DecodeRoutehintList", None, @@ -517,6 +519,7 @@ def __str__(self): added=None, deprecated=None ) +OfferStringField = PrimitiveField("string", None, None, added=None, deprecated=None) CreateRuneRestrictionsField = ArrayField(itemtype=PrimitiveField("string", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added=None, deprecated=None) CheckRuneParamsField = ArrayField(itemtype=PrimitiveField("string", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added=None, deprecated=None) ChainMovesExtraTagsField = ArrayField(itemtype=PrimitiveField("string", None, None, added=None, deprecated=None), dims=1, path=None, description=None, added=None, deprecated=None) @@ -535,24 +538,12 @@ def __str__(self): # Override fields with manually managed types, fieldpath -> field mapping overrides = { - 'Invoice.label': InvoiceLabelField, - 'DelInvoice.label': InvoiceLabelField, - 'ListInvoices.label': InvoiceLabelField, - 'Datastore.key': DatastoreKeyField, - 'DelDatastore.key': DatastoreKeyField, - 'ListDatastore.key': DatastoreKeyField, - 'Invoice.exposeprivatechannels': InvoiceExposeprivatechannelsField, 'Pay.exclude': PayExclude, 'RenePay.exclude': RenePayExclude, 'KeySend.routehints': RoutehintListField, 'KeySend.extratlvs': TlvStreamField, 'Decode.routes': DecodeRoutehintListField, - 'CreateInvoice.label': InvoiceLabelField, - 'DatastoreUsage.key': DatastoreUsageKeyField, - 'WaitInvoice.label': InvoiceLabelField, 'Offer.recurrence_base': OfferStringField, - 'Offer.amount': OfferStringField, - 'SetConfig.val': SetConfigValField, 'CreateRune.restrictions': CreateRuneRestrictionsField, 'CheckRune.params': CheckRuneParamsField, "ListChainMoves.chainmoves[].extra_tags": ChainMovesExtraTagsField, diff --git a/contrib/msggen/msggen/patch.py b/contrib/msggen/msggen/patch.py index 98c80050ed1c..48bf4805414a 100644 --- a/contrib/msggen/msggen/patch.py +++ b/contrib/msggen/msggen/patch.py @@ -28,6 +28,10 @@ def recurse(f: model.Field, inherited_added: Optional[str] = None, inherited_dep if isinstance(f, model.ArrayField): self.visit(f.itemtype, f, inherited_added=f.added or inherited_added, inherited_deprecated=f.deprecated or inherited_deprecated) recurse(f.itemtype, inherited_added=f.added or inherited_added, inherited_deprecated=f.deprecated or inherited_deprecated) + elif isinstance(f, model.UnionField): + for v in f.variants: + self.visit(v, f, inherited_added=f.added or inherited_added, inherited_deprecated=f.deprecated or inherited_deprecated) + recurse(v, inherited_added=f.added or inherited_added, inherited_deprecated=f.deprecated or inherited_deprecated) elif isinstance(f, model.CompositeField): for c in f.fields: self.visit(c, f, inherited_added=inherited_added, inherited_deprecated=inherited_deprecated) From f7406e673fd17d707d602bbc382100ae40706a65 Mon Sep 17 00:00:00 2001 From: 4xvgal <4xvgal@protonmail.com> Date: Mon, 23 Mar 2026 00:23:39 +0900 Subject: [PATCH 2/3] fix: Match proto wrapper message casing in convert generator The convert generator was capitalizing the union variant suffix (e.g. ArrString) when constructing wrapper type names, while the proto generator used the raw suffix (arr_string). This caused prost-generated struct names to mismatch (DatastoreRequestarrStringWrapper vs DatastoreRequestArrStringWrapper). Co-Authored-By: Claude Opus 4.6 (1M context) --- cln-grpc/src/convert.rs | 10 +++++----- contrib/msggen/msggen/gen/grpc/convert.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 05c4349fab08..6b32bcfbb5e6 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -4822,7 +4822,7 @@ impl From for pb::CreateinvoiceRequest { impl From for pb::datastore_request::Key { fn from(c: requests::DatastoreKey) -> Self { match c { -requests::DatastoreKey::ArrayOfString(v) => pb::datastore_request::Key::KeyArrString(pb::DatastoreRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::DatastoreKey::ArrayOfString(v) => pb::datastore_request::Key::KeyArrString(pb::DatastoreRequestarrStringWrapper { items: v.into_iter().map(|i| i).collect() }), requests::DatastoreKey::String(v) => pb::datastore_request::Key::KeyString(v), } } @@ -4844,7 +4844,7 @@ impl From for pb::DatastoreRequest { impl From for pb::datastoreusage_request::Key { fn from(c: requests::DatastoreusageKey) -> Self { match c { -requests::DatastoreusageKey::ArrayOfString(v) => pb::datastoreusage_request::Key::KeyArrString(pb::DatastoreusageRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::DatastoreusageKey::ArrayOfString(v) => pb::datastoreusage_request::Key::KeyArrString(pb::DatastoreusageRequestarrStringWrapper { items: v.into_iter().map(|i| i).collect() }), requests::DatastoreusageKey::String(v) => pb::datastoreusage_request::Key::KeyString(v), } } @@ -4885,7 +4885,7 @@ impl From for pb::CreateonionRequest { impl From for pb::deldatastore_request::Key { fn from(c: requests::DeldatastoreKey) -> Self { match c { -requests::DeldatastoreKey::ArrayOfString(v) => pb::deldatastore_request::Key::KeyArrString(pb::DeldatastoreRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::DeldatastoreKey::ArrayOfString(v) => pb::deldatastore_request::Key::KeyArrString(pb::DeldatastoreRequestarrStringWrapper { items: v.into_iter().map(|i| i).collect() }), requests::DeldatastoreKey::String(v) => pb::deldatastore_request::Key::KeyString(v), } } @@ -4982,7 +4982,7 @@ impl From for pb::invoice_request::Expos fn from(c: requests::InvoiceExposeprivatechannels) -> Self { match c { requests::InvoiceExposeprivatechannels::Bool(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsBool(v), -requests::InvoiceExposeprivatechannels::ArrayOfShortChannelId(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsArrScid(pb::InvoiceRequestArrScidWrapper { items: v.into_iter().map(|i| i.to_string()).collect() }), +requests::InvoiceExposeprivatechannels::ArrayOfShortChannelId(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsArrScid(pb::InvoiceRequestarrScidWrapper { items: v.into_iter().map(|i| i.to_string()).collect() }), requests::InvoiceExposeprivatechannels::ShortChannelId(v) => pb::invoice_request::Exposeprivatechannels::ExposeprivatechannelsScid(v.to_string()), } } @@ -5051,7 +5051,7 @@ impl From for pb::ListinvoicerequestsReque impl From for pb::listdatastore_request::Key { fn from(c: requests::ListdatastoreKey) -> Self { match c { -requests::ListdatastoreKey::ArrayOfString(v) => pb::listdatastore_request::Key::KeyArrString(pb::ListdatastoreRequestArrStringWrapper { items: v.into_iter().map(|i| i).collect() }), +requests::ListdatastoreKey::ArrayOfString(v) => pb::listdatastore_request::Key::KeyArrString(pb::ListdatastoreRequestarrStringWrapper { items: v.into_iter().map(|i| i).collect() }), requests::ListdatastoreKey::String(v) => pb::listdatastore_request::Key::KeyString(v), } } diff --git a/contrib/msggen/msggen/gen/grpc/convert.py b/contrib/msggen/msggen/gen/grpc/convert.py index 49c8eb1ba06b..e61f64d104f0 100644 --- a/contrib/msggen/msggen/gen/grpc/convert.py +++ b/contrib/msggen/msggen/gen/grpc/convert.py @@ -79,7 +79,7 @@ def generate_union(self, prefix, field: UnionField, parent_typename, override=No pb_variant = self.to_camel_case(f"{oneof_name}_{suffix}") pb_variant = pb_variant[0].upper() + pb_variant[1:] if isinstance(v, ArrayField): - wrapper_name = override(f"{parent_typename}{suffix.capitalize()}Wrapper") + wrapper_name = override(f"{parent_typename}{suffix}Wrapper") wrapper_pb = self.to_camel_case(str(wrapper_name)) self.write( f" {prefix}::{typename}::{vname}(v) => pb::{pb_mod}::{pb_oneof_enum}::{pb_variant}(pb::{wrapper_pb} {{ items: v.into_iter().map(|i| {self.union_variant_conversion(v.itemtype, 'i')}).collect() }}),\n" From 0ef8a8ae17335f2ec39222caadb1b1ca5b9581c2 Mon Sep 17 00:00:00 2001 From: 4xvgal <4xvgal@protonmail.com> Date: Mon, 23 Mar 2026 00:54:44 +0900 Subject: [PATCH 3/3] fix: Preserve existing protobuf field numbers for oneOf variants Reuse the original field number for the first variant of each oneOf, instead of allocating new numbers for all variants. This maintains backward compatibility with existing gRPC clients. For example, Invoice.label was previously field 3 (string). Now the oneof reuses 3 for label_string and only allocates a new number for label_int. Co-Authored-By: Claude Opus 4.6 (1M context) --- .msggen.json | 40 +++++++------------ cln-grpc/proto/node.proto | 52 ++++++++++++------------- contrib/msggen/msggen/gen/grpc/proto.py | 14 +++++-- 3 files changed, 50 insertions(+), 56 deletions(-) diff --git a/.msggen.json b/.msggen.json index 8c55b7d695b5..c83b74a8665f 100644 --- a/.msggen.json +++ b/.msggen.json @@ -1270,8 +1270,7 @@ "CreateinvoiceRequest": { "CreateInvoice.invstring": 1, "CreateInvoice.label": 2, - "CreateInvoice.label_int": 5, - "CreateInvoice.label_string": 4, + "CreateInvoice.label_int": 4, "CreateInvoice.preimage": 3 }, "CreateinvoiceResponse": { @@ -1325,8 +1324,7 @@ "Datastore.hex": 2, "Datastore.key": 5, "Datastore.key[]": 1, - "Datastore.key_arr_string": 7, - "Datastore.key_string": 8, + "Datastore.key_string": 7, "Datastore.mode": 3, "Datastore.string": 6 }, @@ -1343,8 +1341,7 @@ }, "DatastoreusageRequest": { "DatastoreUsage.key": 1, - "DatastoreUsage.key_arr_string": 2, - "DatastoreUsage.key_string": 3 + "DatastoreUsage.key_string": 2 }, "DatastoreusageResponse": { "DatastoreUsage.datastoreusage": 1 @@ -1531,8 +1528,7 @@ "DelDatastore.generation": 2, "DelDatastore.key": 3, "DelDatastore.key[]": 1, - "DelDatastore.key_arr_string": 4, - "DelDatastore.key_string": 5 + "DelDatastore.key_string": 4 }, "DeldatastoreResponse": { "DelDatastore.generation": 2, @@ -1552,8 +1548,7 @@ "DelinvoiceRequest": { "DelInvoice.desconly": 3, "DelInvoice.label": 1, - "DelInvoice.label_string": 4, - "DelInvoice.label_u64": 5, + "DelInvoice.label_u64": 4, "DelInvoice.status": 2 }, "DelinvoiceResponse": { @@ -2066,13 +2061,11 @@ "Invoice.description": 2, "Invoice.expiry": 7, "Invoice.exposeprivatechannels": 8, - "Invoice.exposeprivatechannels_arr_scid": 14, - "Invoice.exposeprivatechannels_bool": 13, - "Invoice.exposeprivatechannels_scid": 15, + "Invoice.exposeprivatechannels_arr_scid": 12, + "Invoice.exposeprivatechannels_scid": 13, "Invoice.fallbacks[]": 4, "Invoice.label": 3, - "Invoice.label_int": 12, - "Invoice.label_string": 11, + "Invoice.label_int": 11, "Invoice.msatoshi": 1, "Invoice.preimage": 5 }, @@ -2732,8 +2725,7 @@ "ListdatastoreRequest": { "ListDatastore.key": 2, "ListDatastore.key[]": 1, - "ListDatastore.key_arr_string": 3, - "ListDatastore.key_string": 4 + "ListDatastore.key_string": 3 }, "ListdatastoreResponse": { "ListDatastore.datastore[]": 1 @@ -2860,8 +2852,7 @@ "ListInvoices.index": 5, "ListInvoices.invstring": 2, "ListInvoices.label": 1, - "ListInvoices.label_int": 9, - "ListInvoices.label_string": 8, + "ListInvoices.label_int": 8, "ListInvoices.limit": 7, "ListInvoices.offer_id": 4, "ListInvoices.payment_hash": 3, @@ -3383,8 +3374,7 @@ "OfferRequest": { "Offer.absolute_expiry": 6, "Offer.amount": 1, - "Offer.amount_currency": 17, - "Offer.amount_msat_or_any": 16, + "Offer.amount_currency": 16, "Offer.description": 2, "Offer.fronting_nodes[]": 15, "Offer.issuer": 3, @@ -3780,9 +3770,8 @@ "SetConfig.config": 1, "SetConfig.transient": 3, "SetConfig.val": 2, - "SetConfig.val_bool": 6, - "SetConfig.val_int": 5, - "SetConfig.val_string": 4 + "SetConfig.val_bool": 5, + "SetConfig.val_int": 4 }, "SetconfigResponse": { "SetConfig.config": 1 @@ -4080,8 +4069,7 @@ }, "WaitinvoiceRequest": { "WaitInvoice.label": 1, - "WaitInvoice.label_int": 3, - "WaitInvoice.label_string": 2 + "WaitInvoice.label_int": 2 }, "WaitinvoiceResponse": { "WaitInvoice.amount_msat": 6, diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 0dabc75ed6e7..a13c8b656cc0 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -587,8 +587,8 @@ message ConnectAddress { message CreateinvoiceRequest { string invstring = 1; oneof label { - string label_string = 4; - sint64 label_int = 5; + string label_string = 2; + sint64 label_int = 4; } bytes preimage = 3; } @@ -640,8 +640,8 @@ message DatastoreRequest { optional DatastoreMode mode = 3; optional uint64 generation = 4; oneof key { - DatastoreRequestarr_stringWrapper key_arr_string = 7; - string key_string = 8; + DatastoreRequestarr_stringWrapper key_arr_string = 5; + string key_string = 7; } optional string string = 6; } @@ -659,8 +659,8 @@ message DatastoreusageRequestarr_stringWrapper { message DatastoreusageRequest { oneof key { - DatastoreusageRequestarr_stringWrapper key_arr_string = 2; - string key_string = 3; + DatastoreusageRequestarr_stringWrapper key_arr_string = 1; + string key_string = 2; } } @@ -697,8 +697,8 @@ message DeldatastoreRequestarr_stringWrapper { message DeldatastoreRequest { optional uint64 generation = 2; oneof key { - DeldatastoreRequestarr_stringWrapper key_arr_string = 4; - string key_string = 5; + DeldatastoreRequestarr_stringWrapper key_arr_string = 3; + string key_string = 4; } } @@ -717,8 +717,8 @@ message DelinvoiceRequest { UNPAID = 2; } oneof label { - string label_string = 4; - uint64 label_u64 = 5; + string label_string = 1; + uint64 label_u64 = 4; } DelinvoiceStatus status = 2; optional bool desconly = 3; @@ -814,17 +814,17 @@ message InvoiceRequestarr_scidWrapper { message InvoiceRequest { string description = 2; oneof label { - string label_string = 11; - sint64 label_int = 12; + string label_string = 3; + sint64 label_int = 11; } repeated string fallbacks = 4; optional bytes preimage = 5; optional uint32 cltv = 6; optional uint64 expiry = 7; oneof exposeprivatechannels { - bool exposeprivatechannels_bool = 13; - InvoiceRequestarr_scidWrapper exposeprivatechannels_arr_scid = 14; - string exposeprivatechannels_scid = 15; + bool exposeprivatechannels_bool = 8; + InvoiceRequestarr_scidWrapper exposeprivatechannels_arr_scid = 12; + string exposeprivatechannels_scid = 13; } optional bool deschashonly = 9; AmountOrAny amount_msat = 10; @@ -898,8 +898,8 @@ message ListdatastoreRequestarr_stringWrapper { message ListdatastoreRequest { oneof key { - ListdatastoreRequestarr_stringWrapper key_arr_string = 3; - string key_string = 4; + ListdatastoreRequestarr_stringWrapper key_arr_string = 2; + string key_string = 3; } } @@ -921,8 +921,8 @@ message ListinvoicesRequest { UPDATED = 1; } oneof label { - string label_string = 8; - sint64 label_int = 9; + string label_string = 1; + sint64 label_int = 8; } optional string invstring = 2; optional bytes payment_hash = 3; @@ -1212,8 +1212,8 @@ message WaitanyinvoicePaidOutpoint { message WaitinvoiceRequest { oneof label { - string label_string = 2; - sint64 label_int = 3; + string label_string = 1; + sint64 label_int = 2; } } @@ -2468,8 +2468,8 @@ message MultiwithdrawResponse { message OfferRequest { oneof amount { - AmountOrAny amount_msat_or_any = 16; - string amount_currency = 17; + AmountOrAny amount_msat_or_any = 1; + string amount_currency = 16; } optional string description = 2; optional string issuer = 3; @@ -2761,9 +2761,9 @@ message SetchannelChannels { message SetconfigRequest { string config = 1; oneof val { - string val_string = 4; - sint64 val_int = 5; - bool val_bool = 6; + string val_string = 2; + sint64 val_int = 4; + bool val_bool = 5; } optional bool transient = 3; } diff --git a/contrib/msggen/msggen/gen/grpc/proto.py b/contrib/msggen/msggen/gen/grpc/proto.py index 77ca1955928a..730295feff43 100644 --- a/contrib/msggen/msggen/gen/grpc/proto.py +++ b/contrib/msggen/msggen/gen/grpc/proto.py @@ -238,15 +238,21 @@ def generate_message(self, message: CompositeField, typename_override=None): if isinstance(f, UnionField): self.write(f"\toneof {f.normalized()} {{\n", False) + first_variant = True for v in f.variants: suffix = union_variant_suffix(v) vname = f"{f.normalized()}_{suffix}" vtype = self.union_variant_proto_type(v, str(message.typename)) vtype = typename_override(vtype) - # Allocate variant numbers in the parent message's namespace - parent = ".".join(f.path.split(".")[:-1]) - vfield = PrimitiveField(suffix, f"{parent}.{f.normalized()}_{suffix}", "", added=f.added, deprecated=f.deprecated) - vnum = self.field2number(message.typename, vfield) + if first_variant: + # Reuse the original field number for backward compat + vnum = i + first_variant = False + else: + # Allocate new numbers for additional variants + parent = ".".join(f.path.split(".")[:-1]) + vfield = PrimitiveField(suffix, f"{parent}.{f.normalized()}_{suffix}", "", added=f.added, deprecated=f.deprecated) + vnum = self.field2number(message.typename, vfield) self.write(f"\t\t{vtype} {vname} = {vnum};\n", False) self.write(f"\t}}\n", False) elif isinstance(f, ArrayField):