cln-grpc: Add result conversion generator to msggen

This takes the Rust bindings and converts them into the generated
protobuf bindings:

> JSON-RPC -> Rust bindings -> grpc bindings -> protobuf
This commit is contained in:
Christian Decker 2022-01-15 13:56:49 +01:00
parent 4c105d2424
commit 8d3871d791
4 changed files with 106 additions and 2 deletions

View File

@ -2,7 +2,9 @@ cln-grpc-wrongdir:
$(MAKE) -C .. cln-grpc-all
CLN_GRPC_EXAMPLES :=
CLN_GRPC_GENALL = cln-grpc/proto/node.proto
CLN_GRPC_GENALL = cln-grpc/proto/node.proto \
cln-grpc/src/convert.rs
DEFAULT_TARGETS += $(CLN_GRPC_EXAMPLES) $(CLN_GRPC_GENALL)
$(CLN_GRPC_GENALL): $(JSON_SCHEMA)

BIN
cln-grpc/src/convert.rs Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
from msggen.model import Method, CompositeField, Service
from msggen.grpc import GrpcGenerator
from msggen.grpc import GrpcGenerator, GrpcConverterGenerator
from msggen.rust import RustGenerator
from pathlib import Path
import subprocess
@ -132,6 +132,10 @@ def gengrpc(service):
fname = repo_root() / "cln-grpc" / "proto" / "node.proto"
dest = open(fname, "w")
GrpcGenerator(dest).generate(service)
fname = repo_root() / "cln-grpc" / "src" / "convert.rs"
dest = open(fname, "w")
GrpcConverterGenerator(dest).generate(service)
def genrustjsonrpc(service):
fname = repo_root() / "cln-rpc" / "src" / "model.rs"
dest = open(fname, "w")

View File

@ -151,3 +151,101 @@ class GrpcGenerator:
for message in [f for f in fields if isinstance(f, CompositeField)]:
self.generate_message(message)
class GrpcConverterGenerator:
def __init__(self, dest: TextIO):
self.dest = dest
self.logger = logging.getLogger("msggen.grpc.GrpcConversionGenerator")
def generate_array(self, prefix, field: ArrayField):
if isinstance(field.itemtype, CompositeField):
self.generate_composite(prefix, field.itemtype)
def generate_composite(self, prefix, field: CompositeField):
"""Generates the conversions from JSON-RPC to GRPC.
"""
# First pass: generate any sub-fields before we generate the
# top-level field itself.
for f in field.fields:
if isinstance(f, ArrayField):
self.generate_array(prefix, f)
# And now we can convert the current field:
self.write(f"""\
#[allow(unused_variables)]
impl From<&{prefix}::{field.typename}> for pb::{field.typename} {{
fn from(c: &{prefix}::{field.typename}) -> Self {{
Self {{
""")
for f in field.fields:
name = f.normalized()
if isinstance(f, ArrayField):
self.write(f"{name}: c.{name}.iter().map(|s| s.into()).collect(),\n", numindent=3)
elif isinstance(f, EnumField):
self.write(f"{name}: c.{name} as i32,\n", numindent=3)
elif isinstance(f, PrimitiveField):
typ = f.typename + ("?" if not f.required else "")
# We may need to reduce or increase the size of some
# types, or have some conversion such as
# hex-decoding. Also includes the `Some()` that grpc
# requires for non-native types.
rhs = {
'u8': f'c.{name}.into()',
'u16': f'c.{name}.into()',
'u16?': f'c.{name}.map(|v| v.into())',
'msat': f'Some(c.{name}.into())',
'msat?': f'c.{name}.map(|f| f.into())',
'pubkey': f'hex::decode(&c.{name}).unwrap()',
'pubkey?': f'c.{name}.as_ref().map(|v| hex::decode(&v).unwrap())',
'hex': f'hex::decode(&c.{name}).unwrap()',
'hex?': f'c.{name}.as_ref().map(|v| hex::decode(&v).unwrap())',
'txid': f'hex::decode(&c.{name}).unwrap()',
'txid?': f'c.{name}.as_ref().map(|v| hex::decode(&v).unwrap())',
}.get(
typ,
f'c.{name}.clone()' # default to just assignment
)
self.write(f"{name}: {rhs},\n", numindent=3)
self.write(f"""\
}}
}}
}}
""")
def generate_requests(self, service):
for meth in service.methods:
req = meth.request
self.generate_composite("requests", req)
def generate_responses(self, service):
for meth in service.methods:
res = meth.response
self.generate_composite("responses", res)
def generate(self, service: Service) -> None:
self.write("""
// This file was automatically derived from the JSON-RPC schemas in
// `doc/schemas`. Do not edit this file manually as it would get
// overwritten.
use std::convert::From;
#[allow(unused_imports)]
use cln_rpc::model::{responses,requests};
use crate::pb;
""")
self.generate_responses(service)
def write(self, text: str, numindent: int = 0) -> None:
raw = dedent(text)
if numindent > 0:
raw = indent(text, " " * numindent)
self.dest.write(raw)