#!/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" def camel_to_pascal(value: str) -> str: return value[:1].upper() + value[1:] def dart_source(spec: dict) -> str: lines = [ "// Generated by scripts/generate_relay_protocol.py. Do not edit.", "", "abstract final class RelayProtocol {", ] 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( [ "", " static final RegExp _idPattern = RegExp(r'^[A-Za-z0-9_-]+$');", "", " 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: lines = [ "// Code generated by scripts/generate_relay_protocol.py. DO NOT EDIT.", "", "package main", "", "const (", ] 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_PATH.write_text(dart_source(spec), encoding="utf-8") GO_PATH.write_text(go_source(spec), encoding="utf-8") if __name__ == "__main__": main()