fix: REST paths

This commit is contained in:
Vlad Stan
2026-07-08 11:54:56 +03:00
parent 8425e7467e
commit 23b22e89c7
+82 -15
View File
@@ -46,7 +46,7 @@ def register_wasm_extension(app: FastAPI, ext_id: str) -> WasmExtension:
loaded = load_wasm_extension(ext_id)
_mount_wasm_extension_static(app, loaded)
_register_wasm_extension_routes(app, loaded)
_register_wasm_extension_invoke_route(app, loaded)
_register_wasm_extension_api_routes(app, loaded)
extensions = getattr(app.state, "lnbits_wasm_extensions", {})
extensions[ext_id] = loaded
@@ -114,23 +114,29 @@ def _register_wasm_extension_routes(app: FastAPI, extension: WasmExtension) -> N
_add_wasm_extension_page_route(app, extension, route_path, entrypoint)
def _register_wasm_extension_invoke_route(
def _register_wasm_extension_api_routes(app: FastAPI, extension: WasmExtension) -> None:
for route_config in extension.config.get("api_routes") or []:
_add_wasm_extension_api_route(app, extension, route_config)
def _add_wasm_extension_api_route(
app: FastAPI,
extension: WasmExtension,
route_config: dict[str, Any],
) -> None:
route_path = f"/api/v1/extensions/{extension.id}/invoke/{{export_name}}"
if any(getattr(route, "path", None) == route_path for route in app.routes):
method = _wasm_extension_api_method(extension, route_config.get("method"))
route_path = _wasm_extension_api_path(extension, route_config.get("path"))
export_name = _wasm_extension_api_export(extension, route_config.get("export"))
path_params = route_config.get("path_params") or {}
if _has_route(app, route_path, method):
return
async def invoke_wasm_extension_export(
export_name: str,
request: Request,
) -> dict[str, Any]:
async def invoke_wasm_extension_export(request: Request) -> dict[str, Any]:
from .wasm import invoke_wasm_extension_export as invoke_export
try:
_require_http_wasm_export(extension, export_name)
payload = await _read_json_object(request)
payload = await _read_api_payload(request, path_params)
return await invoke_export(
app,
extension.id,
@@ -147,34 +153,95 @@ def _register_wasm_extension_invoke_route(
app.add_api_route(
route_path,
invoke_wasm_extension_export,
methods=["POST"],
name=f"{extension.id}:invoke",
methods=[method],
name=f"{extension.id}:{method}:{route_path}",
include_in_schema=False,
)
async def _read_api_payload(
request: Request,
path_params: dict[str, str],
) -> dict[str, Any]:
payload = _read_api_path_params(request, path_params)
payload.update(_read_api_query_params(request))
if request.method in {"POST", "PUT", "PATCH"}:
payload.update(await _read_json_object(request))
return payload
async def _read_json_object(request: Request) -> dict[str, Any]:
body = await request.body()
if not body:
return {}
value = json.loads(body)
if not isinstance(value, dict):
raise TypeError("WASM extension invoke payload must be a JSON object.")
raise TypeError("WASM extension API payload must be a JSON object.")
return value
def _require_http_wasm_export(extension: WasmExtension, export_name: str) -> None:
def _read_api_path_params(
request: Request,
path_params: dict[str, str],
) -> dict[str, Any]:
payload: dict[str, Any] = {}
for key, value in request.path_params.items():
target = path_params.get(key) or _snake_to_camel(key)
payload[target] = value
return payload
def _read_api_query_params(request: Request) -> dict[str, Any]:
return {_snake_to_camel(key): value for key, value in request.query_params.items()}
def _wasm_extension_api_export(extension: WasmExtension, export_name: Any) -> str:
if not isinstance(export_name, str) or not export_name:
raise ValueError(f"Invalid API export for WASM extension '{extension.id}'.")
for export in extension.exports:
if export.get("name") != export_name:
continue
if export.get("visibility") in {"public", "authenticated"}:
return
return export_name
raise PermissionError(
f"WASM export '{export_name}' is not callable over HTTP."
)
raise KeyError(f"WASM extension '{extension.id}' has no export '{export_name}'.")
def _wasm_extension_api_method(extension: WasmExtension, method: Any) -> str:
if not isinstance(method, str):
raise ValueError(f"Invalid API method for WASM extension '{extension.id}'.")
method = method.upper()
if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}:
raise ValueError(f"Unsupported API method for WASM extension '{extension.id}'.")
return method
def _wasm_extension_api_path(extension: WasmExtension, path: Any) -> str:
if not isinstance(path, str) or not path.startswith("/"):
raise ValueError(f"Invalid API path for WASM extension '{extension.id}'.")
if path == "/":
return f"/api/v1/ext/{extension.id}"
return f"/api/v1/ext/{extension.id}{path}"
def _has_route(app: FastAPI, route_path: str, method: str) -> bool:
for route in app.routes:
if getattr(route, "path", None) != route_path:
continue
methods = getattr(route, "methods", set()) or set()
if method in methods:
return True
return False
def _snake_to_camel(value: str) -> str:
head, *tail = value.split("_")
return head + "".join(part.capitalize() for part in tail)
def _add_wasm_extension_page_route(
app: FastAPI,
extension: WasmExtension,