dodil/erp-brick-hr

src/brick_hr/infrastructure/xml_door.py

"""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"]