| import base64 |
| from datetime import datetime |
| from typing import List |
| from uuid import UUID |
|
|
| from app.api.topics.schemas import Topic, TopicMessage |
| from app.core.config import ( |
| ENCODING_ASCII, |
| ENCODING_UTF8, |
| TABLE_PARTICIPANTS, |
| TOPIC_EXPECTED_PARTS, |
| TOPIC_INNER_SEPARATOR, |
| TOPIC_LIST_SEPARATOR, |
| TOPIC_MESSAGE_PART_SEPARATOR, |
| TOPIC_PARSE_EMPTY_ERROR, |
| TOPIC_PARSE_INVALID_ERROR, |
| TOPIC_SEPARATOR, |
| ) |
| from app.core.link import Link, ParticipantLink |
|
|
|
|
| class TopicLineParser: |
| SEPARATOR = TOPIC_SEPARATOR |
| EXPECTED_PARTS = TOPIC_EXPECTED_PARTS |
| LIST_SEPARATOR = TOPIC_LIST_SEPARATOR |
| INNER_SEPARATOR = TOPIC_INNER_SEPARATOR |
| MESSAGE_PART_SEPARATOR = TOPIC_MESSAGE_PART_SEPARATOR |
|
|
| @staticmethod |
| def _decode_message(encoded: str) -> str: |
| return base64.b64decode(encoded.encode(ENCODING_ASCII)).decode(ENCODING_UTF8) |
|
|
| @staticmethod |
| def _encode_message(value: str) -> str: |
| return base64.b64encode(value.encode(ENCODING_UTF8)).decode(ENCODING_ASCII) |
|
|
| @classmethod |
| def _parse_participants(cls, raw: str) -> List[ParticipantLink]: |
| if not raw: |
| return [] |
| links: List[ParticipantLink] = [] |
| for value in raw.split(cls.INNER_SEPARATOR): |
| if not value: |
| continue |
| links.append(ParticipantLink.from_raw(value)) |
| return links |
|
|
| @classmethod |
| def _parse_messages(cls, raw: str) -> List[TopicMessage]: |
| if not raw: |
| return [] |
| messages: List[TopicMessage] = [] |
| for chunk in raw.split(cls.LIST_SEPARATOR): |
| if not chunk: |
| continue |
| try: |
| participant_raw, encoded_content = chunk.split(cls.MESSAGE_PART_SEPARATOR, 1) |
| except ValueError: |
| continue |
| messages.append( |
| TopicMessage( |
| participant_id=ParticipantLink.from_raw(participant_raw), |
| content=cls._decode_message(encoded_content), |
| ) |
| ) |
| return messages |
|
|
| @classmethod |
| def _serialize_participants(cls, participants: List[ParticipantLink]) -> str: |
| return cls.INNER_SEPARATOR.join(participant.as_path() for participant in participants) |
|
|
| @classmethod |
| def _serialize_messages(cls, messages: List[TopicMessage]) -> str: |
| return cls.LIST_SEPARATOR.join( |
| cls.MESSAGE_PART_SEPARATOR.join( |
| [message.participant_id.as_path(), cls._encode_message(message.content)] |
| ) |
| for message in messages |
| ) |
|
|
| @classmethod |
| def parse(cls, line: str) -> Topic: |
| raw = line.strip() |
| if not raw: |
| raise ValueError(TOPIC_PARSE_EMPTY_ERROR) |
|
|
| parts = raw.split(cls.SEPARATOR) |
| if len(parts) != cls.EXPECTED_PARTS: |
| raise ValueError(TOPIC_PARSE_INVALID_ERROR) |
|
|
| ( |
| topic_id, |
| title, |
| description, |
| created_at_value, |
| participants_raw, |
| messages_raw, |
| ) = parts |
|
|
| participants = cls._parse_participants(participants_raw) |
| messages = cls._parse_messages(messages_raw) |
|
|
| return Topic( |
| id=UUID(topic_id), |
| title=title, |
| description=description, |
| created_at=datetime.fromisoformat(created_at_value), |
| participants=participants, |
| messages=messages, |
| ) |
|
|
| @classmethod |
| def serialize(cls, topic: Topic) -> str: |
| return cls.SEPARATOR.join( |
| [ |
| str(topic.id), |
| topic.title, |
| topic.description, |
| topic.created_at.isoformat(), |
| cls._serialize_participants(topic.participants), |
| cls._serialize_messages(topic.messages), |
| ] |
| ) |
|
|
|
|
|
|
|
|