Skip to content

Commit 7ef414e

Browse files
msggen: introduce chain of responsibility pattern to make msggen extensible
Changelog-Added: msggen: introduce chain of responsibility pattern to make msggen extensible Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
1 parent be2523e commit 7ef414e

7 files changed

Lines changed: 192 additions & 141 deletions

File tree

contrib/msggen/msggen/__main__.py

Lines changed: 18 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,31 @@
1-
from msggen.model import Method, CompositeField, Service
2-
from msggen.grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator
3-
from msggen.rust import RustGenerator
4-
from pathlib import Path
5-
import subprocess
61
import json
2+
from msggen.gen.grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator
3+
from msggen.gen.rust import RustGenerator
4+
from msggen.gen.generator import GeneratorChain
5+
from msggen.utils import repo_root, load_jsonrpc_service
76

87

9-
# Sometimes we want to rename a method, due to a name clash
10-
method_name_override = {
11-
"Connect": "ConnectPeer",
12-
}
13-
14-
15-
def repo_root():
16-
path = subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
17-
return Path(path.strip().decode('UTF-8'))
18-
19-
20-
def load_jsonrpc_method(name):
21-
"""Load a method based on the file naming conventions for the JSON-RPC.
22-
"""
23-
base_path = (repo_root() / "doc" / "schemas").resolve()
24-
req_file = base_path / f"{name.lower()}.request.json"
25-
resp_file = base_path / f"{name.lower()}.schema.json"
26-
request = CompositeField.from_js(json.load(open(req_file)), path=name)
27-
response = CompositeField.from_js(json.load(open(resp_file)), path=name)
28-
29-
# Normalize the method request and response typename so they no
30-
# longer conflict.
31-
request.typename += "Request"
32-
response.typename += "Response"
33-
34-
return Method(
35-
name=method_name_override.get(name, name),
36-
request=request,
37-
response=response,
38-
)
39-
40-
41-
def load_jsonrpc_service():
42-
method_names = [
43-
"Getinfo",
44-
"ListPeers",
45-
"ListFunds",
46-
"SendPay",
47-
"ListChannels",
48-
"AddGossip",
49-
"AutoCleanInvoice",
50-
"CheckMessage",
51-
"Close",
52-
"Connect",
53-
"CreateInvoice",
54-
"Datastore",
55-
"CreateOnion",
56-
"DelDatastore",
57-
"DelExpiredInvoice",
58-
"DelInvoice",
59-
"Invoice",
60-
"ListDatastore",
61-
"ListInvoices",
62-
"SendOnion",
63-
"ListSendPays",
64-
"ListTransactions",
65-
"Pay",
66-
"ListNodes",
67-
"WaitAnyInvoice",
68-
"WaitInvoice",
69-
"WaitSendPay",
70-
"NewAddr",
71-
"Withdraw",
72-
"KeySend",
73-
"FundPsbt",
74-
"SendPsbt",
75-
"SignPsbt",
76-
"UtxoPsbt",
77-
"TxDiscard",
78-
"TxPrepare",
79-
"TxSend",
80-
# "decodepay",
81-
# "decode",
82-
# "delpay",
83-
# "disableoffer",
84-
"Disconnect",
85-
"Feerates",
86-
# "fetchinvoice",
87-
# "fundchannel_cancel",
88-
# "fundchannel_complete",
89-
# "fundchannel",
90-
# "fundchannel_start",
91-
# "funderupdate",
92-
# "getlog",
93-
"GetRoute",
94-
# "getsharedsecret",
95-
"ListForwards",
96-
# "listoffers",
97-
"ListPays",
98-
# "multifundchannel",
99-
# "multiwithdraw",
100-
# "offerout",
101-
# "offer",
102-
# "openchannel_abort",
103-
# "openchannel_bump",
104-
# "openchannel_init",
105-
# "openchannel_signed",
106-
# "openchannel_update",
107-
# "parsefeerate",
108-
"Ping",
109-
# "plugin",
110-
# "reserveinputs",
111-
# "sendcustommsg",
112-
# "sendinvoice",
113-
# "sendonionmessage",
114-
# "setchannelfee",
115-
"SignMessage",
116-
# "unreserveinputs",
117-
# "waitblockheight",
118-
# "ListConfigs",
119-
# "check", # No point in mapping this one
120-
# "Stop", # Breaks a core assumption (root is an object) can't map unless we change this
121-
# "notifications", # No point in mapping this
122-
# "help",
123-
]
124-
methods = [load_jsonrpc_method(name) for name in method_names]
125-
service = Service(name="Node", methods=methods)
126-
service.includes = ['primitives.proto'] # Make sure we have the primitives included.
127-
return service
128-
129-
130-
def gengrpc(service, meta):
8+
def gengrpc(generator_chain: GeneratorChain, meta):
1319
"""Load all mapped RPC methods, wrap them in a Service, and split them into messages.
13210
"""
13311
fname = repo_root() / "cln-grpc" / "proto" / "node.proto"
13412
dest = open(fname, "w")
135-
GrpcGenerator(dest, meta).generate(service)
13+
generator_chain.add_generator(GrpcGenerator(dest, meta))
13614

13715
fname = repo_root() / "cln-grpc" / "src" / "convert.rs"
13816
dest = open(fname, "w")
139-
GrpcConverterGenerator(dest).generate(service)
140-
GrpcUnconverterGenerator(dest).generate(service)
17+
generator_chain.add_generator(GrpcConverterGenerator(dest))
18+
generator_chain.add_generator(GrpcUnconverterGenerator(dest))
14119

14220
fname = repo_root() / "cln-grpc" / "src" / "server.rs"
14321
dest = open(fname, "w")
144-
GrpcServerGenerator(dest).generate(service)
22+
generator_chain.add_generator(GrpcServerGenerator(dest))
14523

14624

147-
def genrustjsonrpc(service):
25+
def genrustjsonrpc(generator_chain: GeneratorChain):
14826
fname = repo_root() / "cln-rpc" / "src" / "model.rs"
14927
dest = open(fname, "w")
150-
RustGenerator(dest).generate(service)
28+
generator_chain.add_generator(RustGenerator(dest))
15129

15230

15331
def load_msggen_meta():
@@ -163,8 +41,13 @@ def write_msggen_meta(meta):
16341
def run():
16442
service = load_jsonrpc_service()
16543
meta = load_msggen_meta()
166-
gengrpc(service, meta)
167-
genrustjsonrpc(service)
44+
generator_chain = GeneratorChain()
45+
46+
gengrpc(generator_chain, meta)
47+
genrustjsonrpc(generator_chain)
48+
49+
generator_chain.generate(service)
50+
16851
write_msggen_meta(meta)
16952

17053

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .generator import IGenerator, GeneratorChain
2+
from .grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator
3+
from .rust import RustGenerator
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Generator interface!
3+
4+
author: https://github.com/vincenzopalazzo
5+
"""
6+
from abc import ABC, abstractmethod
7+
8+
from msggen.model import Service
9+
10+
11+
class IGenerator(ABC):
12+
"""
13+
Change of responsibility handler that need to be
14+
implemented by all the generators.
15+
"""
16+
17+
@abstractmethod
18+
def generate(self, service: Service):
19+
pass
20+
21+
22+
class GeneratorChain:
23+
"""
24+
Chain responsibility patter implementation to generalize
25+
the generation method.
26+
"""
27+
28+
def __init__(self):
29+
self.generators = []
30+
31+
def add_generator(self, generator: IGenerator) -> None:
32+
self.generators.append(generator)
33+
34+
def generate(self, service: Service) -> None:
35+
for _, generator in enumerate(self.generators):
36+
generator.generate(service)
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# A grpc model
2-
from .model import ArrayField, Field, CompositeField, EnumField, PrimitiveField, Service
2+
from msggen.model import ArrayField, Field, CompositeField, EnumField, PrimitiveField, Service
3+
from msggen.gen import IGenerator
34
from typing import TextIO, List, Dict, Any
45
from textwrap import indent, dedent
56
import re
@@ -51,7 +52,7 @@
5152
}
5253

5354

54-
class GrpcGenerator:
55+
class GrpcGenerator(IGenerator):
5556
"""A generator that generates protobuf files.
5657
"""
5758

@@ -235,7 +236,7 @@ def generate(self, service: Service) -> None:
235236
self.generate_message(message)
236237

237238

238-
class GrpcConverterGenerator:
239+
class GrpcConverterGenerator(IGenerator):
239240
def __init__(self, dest: TextIO):
240241
self.dest = dest
241242
self.logger = logging.getLogger("msggen.grpc.GrpcConversionGenerator")
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import sys
66
import re
77

8-
from .model import (ArrayField, CompositeField, EnumField,
9-
PrimitiveField, Service)
8+
from msggen.model import (ArrayField, CompositeField, EnumField,
9+
PrimitiveField, Service)
10+
from msggen.gen.generator import IGenerator
1011

1112
logger = logging.getLogger(__name__)
1213

@@ -196,7 +197,7 @@ def gen_composite(c) -> Tuple[str, str]:
196197
return ("", r)
197198

198199

199-
class RustGenerator:
200+
class RustGenerator(IGenerator):
200201
def __init__(self, dest: TextIO):
201202
self.dest = dest
202203

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .utils import load_jsonrpc_method, load_jsonrpc_service, repo_root
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import subprocess
2+
import json
3+
from pathlib import Path
4+
5+
from msggen.model import Method, CompositeField, Service
6+
7+
# Sometimes we want to rename a method, due to a name clash
8+
# FIXME: need to be generalized?
9+
method_name_override = {
10+
"Connect": "ConnectPeer",
11+
}
12+
13+
14+
def repo_root():
15+
path = subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
16+
return Path(path.strip().decode('UTF-8'))
17+
18+
19+
def load_jsonrpc_method(name):
20+
"""Load a method based on the file naming conventions for the JSON-RPC.
21+
"""
22+
base_path = (repo_root() / "doc" / "schemas").resolve()
23+
req_file = base_path / f"{name.lower()}.request.json"
24+
resp_file = base_path / f"{name.lower()}.schema.json"
25+
request = CompositeField.from_js(json.load(open(req_file)), path=name)
26+
response = CompositeField.from_js(json.load(open(resp_file)), path=name)
27+
28+
# Normalize the method request and response typename so they no
29+
# longer conflict.
30+
request.typename += "Request"
31+
response.typename += "Response"
32+
33+
return Method(
34+
name=method_name_override.get(name, name),
35+
request=request,
36+
response=response,
37+
)
38+
39+
40+
def load_jsonrpc_service():
41+
method_names = [
42+
"Getinfo",
43+
"ListPeers",
44+
"ListFunds",
45+
"SendPay",
46+
"ListChannels",
47+
"AddGossip",
48+
"AutoCleanInvoice",
49+
"CheckMessage",
50+
"Close",
51+
"Connect",
52+
"CreateInvoice",
53+
"Datastore",
54+
"CreateOnion",
55+
"DelDatastore",
56+
"DelExpiredInvoice",
57+
"DelInvoice",
58+
"Invoice",
59+
"ListDatastore",
60+
"ListInvoices",
61+
"SendOnion",
62+
"ListSendPays",
63+
"ListTransactions",
64+
"Pay",
65+
"ListNodes",
66+
"WaitAnyInvoice",
67+
"WaitInvoice",
68+
"WaitSendPay",
69+
"NewAddr",
70+
"Withdraw",
71+
"KeySend",
72+
"FundPsbt",
73+
"SendPsbt",
74+
"SignPsbt",
75+
"UtxoPsbt",
76+
"TxDiscard",
77+
"TxPrepare",
78+
"TxSend",
79+
# "decodepay",
80+
# "decode",
81+
# "delpay",
82+
# "disableoffer",
83+
"Disconnect",
84+
"Feerates",
85+
# "fetchinvoice",
86+
# "fundchannel_cancel",
87+
# "fundchannel_complete",
88+
# "fundchannel",
89+
# "fundchannel_start",
90+
# "funderupdate",
91+
# "getlog",
92+
"GetRoute",
93+
# "getsharedsecret",
94+
"ListForwards",
95+
# "listoffers",
96+
"ListPays",
97+
# "multifundchannel",
98+
# "multiwithdraw",
99+
# "offerout",
100+
# "offer",
101+
# "openchannel_abort",
102+
# "openchannel_bump",
103+
# "openchannel_init",
104+
# "openchannel_signed",
105+
# "openchannel_update",
106+
# "parsefeerate",
107+
"Ping",
108+
# "plugin",
109+
# "reserveinputs",
110+
# "sendcustommsg",
111+
# "sendinvoice",
112+
# "sendonionmessage",
113+
# "setchannelfee",
114+
"SignMessage",
115+
# "unreserveinputs",
116+
# "waitblockheight",
117+
# "ListConfigs",
118+
# "check", # No point in mapping this one
119+
# "Stop", # Breaks a core assumption (root is an object) can't map unless we change this
120+
# "notifications", # No point in mapping this
121+
# "help",
122+
]
123+
methods = [load_jsonrpc_method(name) for name in method_names]
124+
service = Service(name="Node", methods=methods)
125+
service.includes = ['primitives.proto'] # Make sure we have the primitives included.
126+
return service

0 commit comments

Comments
 (0)