from __future__ import annotations
import argparse
import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / "codegen" / "endpoints.json"
OUTPUT = ROOT / "src" / "generated" / "endpoints.rs"
def const_name(endpoint_id: str) -> str:
return endpoint_id.replace(".", "_").replace("/", "_").upper()
def render_module(module_name: str, endpoints: dict[str, dict[str, str]]) -> str:
lines = [
f"pub(crate) mod {module_name} {{",
" use super::PathTemplateEndpoint;",
"",
]
endpoint_consts: list[str] = []
for endpoint_id, spec in endpoints.items():
const = const_name(endpoint_id)
endpoint_consts.append(const)
lines.append(
f" pub(crate) const {const}: PathTemplateEndpoint = "
f'PathTemplateEndpoint::new("{endpoint_id}", "{spec["method"]}", "{spec["template"]}");'
)
if endpoint_consts:
lines.extend(
[
"",
" #[allow(dead_code)]",
" pub(crate) const ALL: &[PathTemplateEndpoint] = &[",
*[f" {const}," for const in endpoint_consts],
" ];",
]
)
lines.extend(["}", ""])
return "\n".join(lines)
def render() -> str:
manifest = json.loads(MANIFEST.read_text())
parts = [
"// @generated by scripts/generate_endpoints.py. Do not edit by hand.",
"",
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]",
"pub(crate) struct PathTemplateEndpoint {",
" pub(crate) id: &'static str,",
" pub(crate) method: &'static str,",
" pub(crate) template: &'static str,",
"}",
"",
"impl PathTemplateEndpoint {",
" pub(crate) const fn new(",
" id: &'static str,",
" method: &'static str,",
" template: &'static str,",
" ) -> Self {",
" Self { id, method, template }",
" }",
"",
" pub(crate) fn render(self, params: &[(&str, &str)]) -> String {",
" let mut path = self.template.to_owned();",
" for (name, value) in params {",
' let placeholder = format!("{{{name}}}");',
" path = path.replace(&placeholder, value);",
" }",
" path",
" }",
"}",
"",
]
for module_name, endpoints in manifest.items():
parts.append(render_module(module_name, endpoints))
parts.extend(
[
"#[allow(dead_code)]",
"pub(crate) const ALL_ENDPOINTS: &[PathTemplateEndpoint] = &[",
*[
f" {module_name}::{const_name(endpoint_id)},"
for module_name, endpoints in manifest.items()
for endpoint_id in endpoints
],
"];",
"",
]
)
return "\n".join(parts).rstrip() + "\n"
def rustfmt(contents: str) -> str:
result = subprocess.run(
["rustfmt", "--emit", "stdout"],
input=contents,
text=True,
capture_output=True,
check=True,
)
return result.stdout
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
rendered = rustfmt(render())
if args.check:
current = OUTPUT.read_text()
if current != rendered:
raise SystemExit("src/generated/endpoints.rs is out of date")
return
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_text(rendered)
if __name__ == "__main__":
main()