import base64 import re from io import BytesIO from amqp.model.model import AMQResponse from amqp.model.snowflake_id import SnowflakeId from amqp.router.serializer import parse_map_string, map_as_string class AMQResponseFactory: MAGIC_BYTE_HTTP_REQUEST = "A" @staticmethod def create_async_response_message(_id: SnowflakeId) -> AMQResponse: return AMQResponse( str(_id), 200, "application/text", "OK".encode("utf-8"), "", "", {} ) @staticmethod def serialize(response: AMQResponse) -> bytes: id_bytes = response.id.encode('utf-8') prolog = ( f"|r={response.response_code}|c={response.content_type}|e={response.error}|f={response.error_cause}|t={{{map_as_string(response.trace_info)}}}|b=" ).encode('utf-8') header_length = 1 + len(id_bytes) + len(prolog) prolog_len = header_length.to_bytes(2, byteorder='big') msg_length = 2 + header_length + len(response.response) with BytesIO(bytearray(msg_length)) as baos: baos.write(prolog_len) baos.write(AMQResponseFactory.MAGIC_BYTE_HTTP_REQUEST.encode('utf-8')) baos.write(id_bytes) baos.write(prolog) baos.write(base64.b64encode(response.response)) return baos.getvalue() @staticmethod def from_bytes(input_bytes: bytes) -> AMQResponse: prolog_len = (input_bytes[0] << 8) + input_bytes[1] metadata = input_bytes[2:prolog_len + 2].decode() pattern = re.compile( f"{AMQResponseFactory.MAGIC_BYTE_HTTP_REQUEST}" r"([0-9A-Fa-f]+)\|" r"r=([^|]*)\|" r"c=([^|]*)\|" r"e=([^|]*)\|" r"f=([^|]*)\|" r"t=(\{[^}]*\})\|" r"b=" ) match = pattern.match(metadata) if not match: raise ValueError( f"AMQResponse format does not match expected format. " f"Input string [{metadata}] does not match pattern: {pattern.pattern}" ) id_str = match.group(1) response_code = int(match.group(2)) content_type = match.group(3) error = match.group(4) error_cause = match.group(5) trace_info_str = match.group(6) body_b64 = input_bytes[prolog_len + 2:] trace_info = parse_map_string(trace_info_str) return AMQResponse( id_str, response_code, content_type, base64.b64decode(body_b64), error, error_cause, trace_info, )