"""The HTTP door's representation of a document use case: GET …/playbooks/{id}/bpmn answers the BPMN XML itself
(application/xml), so a browser, curl or a BPMN tool can open the URL as is.
The use case is declared once (show_playbook_bpmn) and answers, through both doors, the same envelope
{playbook_id, version, media_type, filename, bpmn}. erp-platform serves every route as JSON, so this middleware
unwraps that envelope on the way out — unless the caller asks for JSON (Accept: application/json without xml). It
changes no status, no refusal and no other route. Where the platform gains a per-use-case media type, this goes.
"""
import json
import re
PATH = re.compile(r"(?:/[^/]+)?/playbooks/[^/]+/bpmn")
def _wants_json(scope) -> bool:
accept = next((v.decode("latin-1") for k, v in scope.get("headers") or [] if k == b"accept"), "")
return "application/json" in accept and "xml" not in accept
class BpmnAsXml:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if (scope["type"] != "http" or scope.get("method") != "GET" or not PATH.fullmatch(scope.get("path") or "")
or _wants_json(scope)):
await self.app(scope, receive, send)
return
start, body = {}, []
async def hold(message):
if message["type"] == "http.response.start":
start.update(message)
elif message["type"] == "http.response.body":
body.append(message.get("body", b""))
if not message.get("more_body"):
await _answer(start, b"".join(body), send)
else:
await send(message)
await self.app(scope, receive, hold)
async def _answer(start: dict, raw: bytes, send) -> None:
headers = list(start.get("headers") or [])
payload = None
if start.get("status") == 200:
try:
payload = json.loads(raw)
except ValueError:
payload = None
if not isinstance(payload, dict) or not isinstance(payload.get("bpmn"), str):
await send(start)
await send({"type": "http.response.body", "body": raw})
return
xml = payload["bpmn"].encode("utf-8")
name = re.sub(r"[^A-Za-z0-9._-]", "_", str(payload.get("filename") or "playbook.bpmn"))
keep = [(k, v) for k, v in headers if k.lower() not in (b"content-type", b"content-length")]
keep += [(b"content-type", b"application/xml; charset=utf-8"), (b"content-length", str(len(xml)).encode()),
(b"content-disposition", f'inline; filename="{name}"'.encode())]
await send({**start, "headers": keep})
await send({"type": "http.response.body", "body": xml})
__all__ = ["BpmnAsXml"]