msggen: Add an optional patch

This patch annotates the fields with a new `optional` attribute which
determines whether the field should be considered an inferred optional
due to being added or deprecated.
This commit is contained in:
Christian Decker 2023-03-28 13:05:17 +02:00 committed by Rusty Russell
parent 5df469cfca
commit 392cacac81
4 changed files with 1219 additions and 25 deletions

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,9 @@ from msggen.gen.rust import RustGenerator
from msggen.gen.generator import GeneratorChain
from msggen.utils import load_jsonrpc_service
import logging
from msggen.patch import VersionAnnotationPatch, OptionalPatch
from msggen.checks import VersioningCheck
logging.basicConfig(
level=logging.DEBUG,
@ -51,7 +54,6 @@ def load_msggen_meta():
meta = json.load(open('.msggen.json', 'r'))
return meta
from msggen.patch import VersionAnnotationPatch
def write_msggen_meta(meta):
pid = os.getpid()
@ -69,6 +71,7 @@ def run(rootdir: Path):
p = VersionAnnotationPatch(meta=meta)
p.apply(service)
OptionalPatch().apply(service)
generator_chain = GeneratorChain()

View file

@ -27,11 +27,17 @@ class FieldName:
class Field:
def __init__(self, path, description):
def __init__(
self,
path,
description,
added=None,
deprecated=None
):
self.path = path
self.description = description
self.deprecated = None
self.added = None
self.added = added
self.deprecated = deprecated
self.required = False
@property
@ -93,8 +99,22 @@ class Method:
class CompositeField(Field):
def __init__(self, typename, fields, path, description):
Field.__init__(self, path, description)
def __init__(
self,
typename,
fields,
path,
description,
added,
deprecated
):
Field.__init__(
self,
path,
description,
added=added,
deprecated=deprecated
)
self.typename = typename
self.fields = fields
@ -131,6 +151,8 @@ class CompositeField(Field):
field = None
desc = ftype["description"] if "description" in ftype else ""
fpath = f"{path}.{fname}"
added = ftype.get('added', None)
deprecated = ftype.get('deprecated', None)
if fpath in overrides:
field = copy(overrides[fpath])
@ -160,7 +182,7 @@ class CompositeField(Field):
field = ArrayField.from_js(fpath, ftype)
elif ftype["type"] in PrimitiveField.types:
field = PrimitiveField(ftype["type"], fpath, desc)
field = PrimitiveField(ftype["type"], fpath, desc, added=added, deprecated=deprecated)
else:
logger.warning(
@ -174,7 +196,7 @@ class CompositeField(Field):
logger.debug(field)
return CompositeField(
typename, fields, path, js["description"] if "description" in js else ""
typename, fields, path, js["description"] if "description" in js else "", added=js.get('added', None), deprecated=js.get('deprecated', None)
)
def __str__(self):
@ -196,8 +218,8 @@ class EnumVariant(Field):
class EnumField(Field):
def __init__(self, typename, values, path, description):
Field.__init__(self, path, description)
def __init__(self, typename, values, path, description, added, deprecated):
Field.__init__(self, path, description, added=added, deprecated=deprecated)
self.typename = typename
self.values = values
self.variants = [EnumVariant(v) for v in self.values]
@ -211,6 +233,8 @@ class EnumField(Field):
values=filter(lambda i: i is not None, js["enum"]),
path=path,
description=js["description"] if "description" in js else "",
added=js.get('added', None),
deprecated=js.get('deprecated', None),
)
def __str__(self):
@ -225,8 +249,8 @@ class UnionField(Field):
and a `oneof` in protobuf.
"""
def __init__(self, path, description, variants):
Field.__init__(self, path, description)
def __init__(self, path, description, variants, added, deprecated):
Field.__init__(self, path, description, added=added, deprecated=deprecated)
self.variants = variants
self.typename = path2type(path)
@ -282,8 +306,8 @@ class PrimitiveField(Field):
"hash",
]
def __init__(self, typename, path, description):
Field.__init__(self, path, description)
def __init__(self, typename, path, description, added, deprecated):
Field.__init__(self, path, description, added=added, deprecated=deprecated)
self.typename = typename
def __str__(self):
@ -291,8 +315,8 @@ class PrimitiveField(Field):
class ArrayField(Field):
def __init__(self, itemtype, dims, path, description):
Field.__init__(self, path, description)
def __init__(self, itemtype, dims, path, description, added, deprecated):
Field.__init__(self, path, description, added=added, deprecated=deprecated)
self.itemtype = itemtype
self.dims = dims
self.path = path
@ -322,11 +346,13 @@ class ArrayField(Field):
child_js["type"],
path,
child_js.get("description", ""),
added=child_js.get("added", None),
deprecated=child_js.get("deprecated", None),
)
logger.debug(f"Array path={path} dims={dims}, type={itemtype}")
return ArrayField(
itemtype, dims=dims, path=path, description=js.get("description", "")
itemtype, dims=dims, path=path, description=js.get("description", ""), added=js.get('added', None), deprecated=js.get('deprecated', None)
)
@ -340,14 +366,16 @@ class Command:
return f"Command[name={self.name}, fields=[{fieldnames}]]"
InvoiceLabelField = PrimitiveField("string", None, None)
DatastoreKeyField = ArrayField(itemtype=PrimitiveField("string", None, None), dims=1, path=None, description=None)
InvoiceExposeprivatechannelsField = PrimitiveField("boolean", None, None)
PayExclude = ArrayField(itemtype=PrimitiveField("string", None, None), dims=1, path=None, description=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)
InvoiceExposeprivatechannelsField = PrimitiveField("boolean", None, 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)
RoutehintListField = PrimitiveField(
"RoutehintList",
None,
None
None,
added=None,
deprecated=None
)
# TlvStreams are special, they don't have preset dict-keys, rather
@ -356,7 +384,9 @@ RoutehintListField = PrimitiveField(
TlvStreamField = PrimitiveField(
"TlvStream",
None,
None
None,
added=None,
deprecated=None
)
# Override fields with manually managed types, fieldpath -> field mapping

View file

@ -82,3 +82,48 @@ class VersionAnnotationPatch(Patch):
'deprecated': f.deprecated,
}
class OptionalPatch(Patch):
"""Annotates fields with `.optional`
Optional fields are either non-required fields, or fields that
were not required in prior versions. This latter case covers the
deprecation and addition for schema evolution
"""
versions = [
'pre-v0.10.1', # Dummy versions collecting all fields that predate the versioning.
'v0.10.1',
'v0.10.2',
'v0.11.0',
'v0.12.0',
'v0.12.1',
'v22.11',
'v23.02',
'v23.05',
]
# Oldest supported versions. Bump this if you no longer want to
# support older versions, and you want to make required fields
# more stringent.
supported = 'v0.12.0'
def visit(self, f: model.Field) -> None:
if f.added not in self.versions:
raise ValueError(f"Version {f.added} in unknown, please add it to {__file__}")
if f.deprecated and f.deprecated not in self.versions:
raise ValueError(f"Version {f.deprecated} in unknown, please add it to {__file__}")
idx = (
self.versions.index(self.supported),
len(self.versions) - 1,
)
# Default to false, and then overwrite it if required.
f.optional = False
if not f.required:
f.optional = True
if self.versions.index(f.added) > idx[0]:
f.optional = True
if f.deprecated and self.versions.index(f.deprecated) < idx[1]:
f.optional = True