Files
plezy/scripts/generate_relay_protocol.py
T

152 lines
4.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Generate Dart and Go relay protocol constants from relay_protocol.json."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SPEC_PATH = ROOT / "relay_protocol.json"
DART_PATH = ROOT / "lib/watch_together/services/relay_protocol.g.dart"
GO_PATH = ROOT / "server/relay_protocol_gen.go"
SUPPORTED_ID_PATTERN = r"^[A-Za-z0-9_-]+$"
def camel_to_pascal(value: str) -> str:
return value[:1].upper() + value[1:]
def validated_id_pattern(spec: dict) -> str:
try:
pattern = spec["idPattern"]
except KeyError:
raise ValueError("idPattern is required") from None
if not isinstance(pattern, str):
raise ValueError("idPattern must be a string")
if pattern != SUPPORTED_ID_PATTERN:
raise ValueError(
f"unsupported idPattern {pattern!r}; expected {SUPPORTED_ID_PATTERN!r}"
)
return pattern
def dart_source(spec: dict) -> str:
id_pattern = validated_id_pattern(spec)
lines = [
"// Generated by scripts/generate_relay_protocol.py. Do not edit.",
"",
"abstract final class RelayProtocol {",
]
lines.extend(
[
f" static const int protocolVersion = {spec['protocolVersion']};",
f" static const int legacyProtocolVersion = {spec['legacyProtocolVersion']};",
"",
]
)
for group in ("clientMessageTypes", "serverMessageTypes"):
for name, value in spec[group].items():
lines.append(f" static const String {name} = {value!r};")
for name, value in spec["errorCodes"].items():
lines.append(f" static const String {name}Code = {value!r};")
lines.append("")
for name, value in spec["limits"].items():
lines.append(f" static const int {name} = {value};")
lines.extend(
[
"",
f" static final RegExp _idPattern = RegExp(r{id_pattern!r});",
"",
" static bool isValidSessionId(String value) =>",
" value.isNotEmpty && value.length <= maxSessionIdLength && _idPattern.hasMatch(value);",
"",
" static bool isValidPeerId(String value) =>",
" value.isNotEmpty && value.length <= maxPeerIdLength && _idPattern.hasMatch(value);",
"}",
"",
]
)
return "\n".join(lines)
def go_source(spec: dict) -> str:
validated_id_pattern(spec)
lines = [
"// Code generated by scripts/generate_relay_protocol.py. DO NOT EDIT.",
"",
"package main",
"",
"const (",
]
lines.extend(
[
f"\trelayProtocolVersion = {spec['protocolVersion']}",
f"\tlegacyRelayProtocolVersion = {spec['legacyProtocolVersion']}",
"",
]
)
protocol_constants = []
for group in ("clientMessageTypes", "serverMessageTypes"):
protocol_constants.extend(
(f"relayType{camel_to_pascal(name)}", f'"{value}"')
for name, value in spec[group].items()
)
protocol_constants.extend(
(f"relayError{camel_to_pascal(name)}", f'"{value}"')
for name, value in spec["errorCodes"].items()
)
protocol_name_width = max(len(name) for name, _ in protocol_constants)
lines.extend(
f"\t{name:<{protocol_name_width}} = {value}"
for name, value in protocol_constants
)
lines.append("")
go_limit_names = {
"maxRoomSize": "maxRoomSize",
"maxMessageSize": "maxMessageSize",
"maxSessionIdLength": "maxSessionIDLength",
"maxPeerIdLength": "maxPeerIDLength",
}
limit_constants = [
(go_limit_names[name], str(value)) for name, value in spec["limits"].items()
]
limit_name_width = max(len(name) for name, _ in limit_constants)
lines.extend(
f"\t{name:<{limit_name_width}} = {value}"
for name, value in limit_constants
)
lines.extend(
[
")",
"",
"func validRelayID(value string, maxLength int) bool {",
"\tif len(value) == 0 || len(value) > maxLength {",
"\t\treturn false",
"\t}",
"\tfor _, ch := range value {",
"\t\tif (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') &&",
"\t\t\t(ch < '0' || ch > '9') && ch != '_' && ch != '-' {",
"\t\t\treturn false",
"\t\t}",
"\t}",
"\treturn true",
"}",
"",
]
)
return "\n".join(lines)
def main() -> None:
spec = json.loads(SPEC_PATH.read_text(encoding="utf-8"))
dart_output = dart_source(spec)
go_output = go_source(spec)
DART_PATH.write_text(dart_output, encoding="utf-8", newline="\n")
GO_PATH.write_text(go_output, encoding="utf-8")
if __name__ == "__main__":
main()