A Python port of the Invisible Internet Project (I2P)
at main 94 lines 2.5 kB view raw
1"""JSON-RPC 2.0 server primitives for I2PControl.""" 2from __future__ import annotations 3 4import json 5from dataclasses import dataclass, field 6from typing import Any 7 8# Standard JSON-RPC 2.0 error codes 9PARSE_ERROR = -32700 10INVALID_REQUEST = -32600 11METHOD_NOT_FOUND = -32601 12INTERNAL_ERROR = -32603 13 14 15@dataclass 16class JSONRPCRequest: 17 """Parsed JSON-RPC 2.0 request.""" 18 19 method: str 20 params: dict = field(default_factory=dict) 21 id: int | str | None = None 22 jsonrpc: str = "2.0" 23 24 25@dataclass 26class JSONRPCResponse: 27 """JSON-RPC 2.0 response.""" 28 29 result: dict | None = None 30 error: dict | None = None 31 id: int | str | None = None 32 jsonrpc: str = "2.0" 33 34 def to_dict(self) -> dict[str, Any]: 35 d: dict[str, Any] = {"jsonrpc": self.jsonrpc, "id": self.id} 36 if self.error is not None: 37 d["error"] = self.error 38 else: 39 d["result"] = self.result 40 return d 41 42 43def parse_jsonrpc_request(data: str) -> JSONRPCRequest: 44 """Parse a JSON-RPC 2.0 request string. 45 46 Raises ``ValueError`` on invalid JSON, missing ``jsonrpc`` field, 47 or missing ``method`` field. 48 """ 49 try: 50 obj = json.loads(data) 51 except (json.JSONDecodeError, TypeError) as exc: 52 raise ValueError("Parse error: invalid JSON") from exc 53 54 if not isinstance(obj, dict): 55 raise ValueError("Parse error: request must be a JSON object") 56 57 if "jsonrpc" not in obj or obj["jsonrpc"] != "2.0": 58 raise ValueError("Invalid request: missing or wrong jsonrpc field") 59 60 if "method" not in obj: 61 raise ValueError("Invalid request: missing method field") 62 63 return JSONRPCRequest( 64 method=obj["method"], 65 params=obj.get("params", {}), 66 id=obj.get("id"), 67 jsonrpc=obj["jsonrpc"], 68 ) 69 70 71def build_jsonrpc_response( 72 result: dict | None = None, 73 error: dict | None = None, 74 request_id: int | str | None = None, 75) -> dict[str, Any]: 76 """Build a JSON-RPC 2.0 response dict.""" 77 resp: dict[str, Any] = {"jsonrpc": "2.0", "id": request_id} 78 if error is not None: 79 resp["error"] = error 80 else: 81 resp["result"] = result 82 return resp 83 84 85def build_jsonrpc_error( 86 code: int, 87 message: str, 88 request_id: int | str | None = None, 89) -> dict[str, Any]: 90 """Build a JSON-RPC 2.0 error response.""" 91 return build_jsonrpc_response( 92 error={"code": code, "message": message}, 93 request_id=request_id, 94 )