diff --git a/scripts/pyfa_sim.py b/scripts/pyfa_sim.py index 4b630848d..a15b1ca54 100644 --- a/scripts/pyfa_sim.py +++ b/scripts/pyfa_sim.py @@ -8,6 +8,25 @@ import eos.config from eos.const import FittingHardpoint from eos.utils.spoolSupport import SpoolOptions, SpoolType +# POST /simulate request and response shape; included in 400 response body +SIMULATE_SCHEMA = { + "request": { + "fit": "string (required). EFT or multi-line text export of a single fit, e.g. [ShipName, FitName] followed by modules/drones/etc.", + "projected_fits": "array (optional). Each element: { \"fit\": string, \"count\": positive integer }.", + "command_fits": "array (optional). Each element: { \"fit\": string }.", + }, + "response_200": { + "fit": {"id": "int", "name": "string", "ship_type": "string"}, + "resources": {"hardpoints": {}, "drones": {}, "fighters": {}, "calibration": {}, "powergrid": {}, "cpu": {}, "cargo": {}}, + "defense": {"hp": {}, "ehp": {}, "resonance": {}, "tank": {}, "effective_tank": {}, "sustainable_tank": {}, "effective_sustainable_tank": {}}, + "capacitor": {"capacity": {}, "recharge": {}, "use": {}, "delta": {}, "stable": {}, "state": {}, "neutralizer_resistance": {}}, + "firepower": {"weapon_dps": {}, "drone_dps": {}, "total_dps": {}, "weapon_volley": {}, "drone_volley": {}, "total_volley": {}, "weapons": []}, + "remote_reps_outgoing": {"current": {}, "pre_spool": {}, "full_spool": {}}, + "targeting_misc": {"targets_max": {}, "target_range": {}, "scan_resolution": {}, "scan_strength": {}, "scan_type": {}, "jam_chance": {}, "drone_control_range": {}, "speed": {}, "align_time": {}, "signature_radius": {}, "warp_speed": {}, "max_warp_distance": {}, "probe_size": {}, "cargo_capacity": {}, "cargo_used": {}}, + "price": {"ship": {}, "fittings": {}, "drones_and_fighters": {}, "cargo": {}, "character": {}, "total": {}}, + }, +} + def _init_pyfa(savepath: str | None) -> None: config.debug = False @@ -363,45 +382,57 @@ def compute_stats(payload: dict, savepath: str | None = None) -> dict: def _run_http_server(port: int, savepath: str | None) -> None: + def send_error_json(code: int, msg: str, traceback_str: str | None = None): + body = {"error": msg, "schema": SIMULATE_SCHEMA} + if traceback_str: + body["traceback"] = traceback_str + return json.dumps(body, indent=2) + "\n" + class SimulateHandler(BaseHTTPRequestHandler): + def send_error(self, code: int, message: str | None = None, explain: str | None = None): + msg = message or explain or "Error" + self.send_response(code) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.end_headers() + self.wfile.write(send_error_json(code, msg).encode("utf-8")) + + def _reply_error(self, code: int, msg: str, traceback_str: str | None = None): + self.send_response(code) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.end_headers() + self.wfile.write(send_error_json(code, msg, traceback_str).encode("utf-8")) + + def do_GET(self): + if self.path == "/simulate": + self._reply_error(405, "Method not allowed. Use POST.") + else: + self._reply_error(404, "Not found. POST /simulate with JSON body.") + def do_POST(self): if self.path != "/simulate": - self.send_response(404) - self.end_headers() + self._reply_error(404, "Not found. POST /simulate with JSON body.") return content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length).decode("utf-8") try: payload = json.loads(body) except json.JSONDecodeError as exc: - self.send_response(400) - self.send_header("Content-Type", "text/plain; charset=utf-8") - self.end_headers() - self.wfile.write(("Invalid JSON: %s" % exc).encode("utf-8")) + self._reply_error(400, "Invalid JSON: %s" % exc) return if not isinstance(payload, dict): - self.send_response(400) - self.send_header("Content-Type", "text/plain; charset=utf-8") - self.end_headers() - self.wfile.write(b"Top-level JSON must be an object") + self._reply_error(400, "Top-level JSON must be an object") return try: output = compute_stats(payload, savepath) except ValueError as e: - self.send_response(400) - self.send_header("Content-Type", "text/plain; charset=utf-8") - self.end_headers() - self.wfile.write(str(e).encode("utf-8")) + self._reply_error(400, str(e)) return except Exception as e: import traceback tb = traceback.format_exc() sys.stderr.write(tb) sys.stderr.flush() - self.send_response(500) - self.send_header("Content-Type", "text/plain; charset=utf-8") - self.end_headers() - self.wfile.write((str(e) + "\n\n" + tb).encode("utf-8")) + self._reply_error(500, str(e), tb) return self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8")