diff --git a/src/lean_spec/node/api/routes.py b/src/lean_spec/node/api/routes.py index 1f03bd3d..b7199cb3 100644 --- a/src/lean_spec/node/api/routes.py +++ b/src/lean_spec/node/api/routes.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Awaitable, Callable +from typing import NamedTuple from aiohttp import web @@ -18,17 +19,30 @@ Handler = Callable[[web.Request], Awaitable[web.Response]] """Type alias for aiohttp request handlers.""" -ROUTES: dict[str, Handler] = { - "/lean/v0/health": health.handle, - "/lean/v0/states/finalized": states.handle_finalized, - "/lean/v0/checkpoints/justified": checkpoints.handle_justified, - "/lean/v0/fork_choice": fork_choice.handle, - "/metrics": metrics.handle, -} -"""Read-only API routes registered as GET.""" - -ADMIN_ROUTES: list[tuple[str, str, Handler]] = [ - ("GET", "/lean/v0/admin/aggregator", aggregator.handle_status), - ("POST", "/lean/v0/admin/aggregator", aggregator.handle_toggle), + +class Route(NamedTuple): + """One API route: its verb, path, handler, and access tier.""" + + method: str + """HTTP verb the route responds to.""" + + path: str + """URL path the route is registered under.""" + + handler: Handler + """Coroutine that serves requests to this route.""" + + is_admin: bool + """True for privileged admin routes, False for public read-only routes.""" + + +ROUTES: list[Route] = [ + Route("GET", "/lean/v0/health", health.handle, is_admin=False), + Route("GET", "/lean/v0/states/finalized", states.handle_finalized, is_admin=False), + Route("GET", "/lean/v0/checkpoints/justified", checkpoints.handle_justified, is_admin=False), + Route("GET", "/lean/v0/fork_choice", fork_choice.handle, is_admin=False), + Route("GET", "/metrics", metrics.handle, is_admin=False), + Route("GET", "/lean/v0/admin/aggregator", aggregator.handle_status, is_admin=True), + Route("POST", "/lean/v0/admin/aggregator", aggregator.handle_toggle, is_admin=True), ] -"""Admin routes as (method, path, handler) triples for non-GET verbs.""" +"""Every API route, public and admin alike, tagged by access tier.""" diff --git a/src/lean_spec/node/api/server.py b/src/lean_spec/node/api/server.py index 26032f7d..5a3324e7 100644 --- a/src/lean_spec/node/api/server.py +++ b/src/lean_spec/node/api/server.py @@ -15,7 +15,7 @@ from aiohttp import web from lean_spec.node.api.aggregator_controller import AggregatorController -from lean_spec.node.api.routes import ADMIN_ROUTES, ROUTES +from lean_spec.node.api.routes import ROUTES from lean_spec.spec.forks import LstarSpec, Store logger = logging.getLogger(__name__) @@ -99,9 +99,8 @@ async def start(self) -> None: # Absence is fine; endpoints return 503 when unset. app["aggregator_controller"] = self.aggregator_controller - # Add all routes, generated from ROUTES and ADMIN_ROUTES. - routes = [web.get(path, handler) for path, handler in ROUTES.items()] - routes += [web.route(method, path, handler) for method, path, handler in ADMIN_ROUTES] + # Register every route from the unified table, keyed by its verb. + routes = [web.route(route.method, route.path, route.handler) for route in ROUTES] app.add_routes(routes) self._runner = web.AppRunner(app)