Compare commits

..

1 Commits

Author SHA1 Message Date
6b9c51db04 Dump schema on failing requests 2026-02-28 21:50:12 +01:00

View File

@@ -8,6 +8,25 @@ import eos.config
from eos.const import FittingHardpoint from eos.const import FittingHardpoint
from eos.utils.spoolSupport import SpoolOptions, SpoolType 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: def _init_pyfa(savepath: str | None) -> None:
config.debug = False 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 _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): 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): def do_POST(self):
if self.path != "/simulate": if self.path != "/simulate":
self.send_response(404) self._reply_error(404, "Not found. POST /simulate with JSON body.")
self.end_headers()
return return
content_length = int(self.headers.get("Content-Length", 0)) content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length).decode("utf-8") body = self.rfile.read(content_length).decode("utf-8")
try: try:
payload = json.loads(body) payload = json.loads(body)
except json.JSONDecodeError as exc: except json.JSONDecodeError as exc:
self.send_response(400) self._reply_error(400, "Invalid JSON: %s" % exc)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(("Invalid JSON: %s" % exc).encode("utf-8"))
return return
if not isinstance(payload, dict): if not isinstance(payload, dict):
self.send_response(400) self._reply_error(400, "Top-level JSON must be an object")
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"Top-level JSON must be an object")
return return
try: try:
output = compute_stats(payload, savepath) output = compute_stats(payload, savepath)
except ValueError as e: except ValueError as e:
self.send_response(400) self._reply_error(400, str(e))
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(str(e).encode("utf-8"))
return return
except Exception as e: except Exception as e:
import traceback import traceback
tb = traceback.format_exc() tb = traceback.format_exc()
sys.stderr.write(tb) sys.stderr.write(tb)
sys.stderr.flush() sys.stderr.flush()
self.send_response(500) self._reply_error(500, str(e), tb)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write((str(e) + "\n\n" + tb).encode("utf-8"))
return return
self.send_response(200) self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Type", "application/json; charset=utf-8")