39 lines
1.6 KiB
Python
39 lines
1.6 KiB
Python
|
|
"""Canonical MAS v0.1 local-part generator.
|
||
|
|
|
||
|
|
Generators are stricter than parsers: they never emit both a facet and an
|
||
|
|
interaction, and they enforce the 64-octet SMTP limit after encoding.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
from .parser import MasSyntaxError, parse_local_part
|
||
|
|
|
||
|
|
|
||
|
|
def generate_local_part(target: str, *, facet: Optional[str] = None,
|
||
|
|
interaction: Optional[str] = None) -> str:
|
||
|
|
if facet is not None and interaction is not None:
|
||
|
|
raise MasSyntaxError("generator-facet-and-interaction",
|
||
|
|
"v0.1 generators MUST NOT emit both '+' and '!'")
|
||
|
|
s = target
|
||
|
|
if facet is not None:
|
||
|
|
s += "+" + facet
|
||
|
|
if interaction is not None:
|
||
|
|
s += "!" + interaction
|
||
|
|
parsed = parse_local_part(s) # enforces grammar, emptiness, reserved ops, length
|
||
|
|
if str(parsed.target) != target or (parsed.facet and parsed.facet.value) != facet \
|
||
|
|
or (parsed.interaction and parsed.interaction.mode) != interaction:
|
||
|
|
raise MasSyntaxError("generator-component", "a component contains a MAS operator")
|
||
|
|
return s
|
||
|
|
|
||
|
|
|
||
|
|
def resource_local_part(resource_type: str, resource_id: str, *, facet: Optional[str] = None,
|
||
|
|
interaction: Optional[str] = None) -> str:
|
||
|
|
if not resource_type or not resource_id:
|
||
|
|
raise MasSyntaxError("generator-empty", "resource type and id must be non-empty")
|
||
|
|
if "=" in resource_type:
|
||
|
|
raise MasSyntaxError("invalid-resource-type", "resource type must not contain '='")
|
||
|
|
return generate_local_part(f"{resource_type}={resource_id}", facet=facet,
|
||
|
|
interaction=interaction)
|