mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 15:41:41 +02:00
208 lines
6.9 KiB
Python
208 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .addresses import AddressRecord, effective_address
|
|
from .config import GatewayConfig, ProtocolConfig, RoutePolicyConfig, SiteConfig
|
|
from .route_policy import fixture_path, route_exposed
|
|
|
|
|
|
SCHEMA = "ampg.fixture-manifest.v1"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ManifestWriteResult:
|
|
site_id: str
|
|
path: Path
|
|
fixture_count: int
|
|
|
|
|
|
def fixture_manifest(config: GatewayConfig, site: SiteConfig) -> dict[str, Any]:
|
|
fixtures = []
|
|
for protocol in site.protocols.values():
|
|
if not protocol.enabled:
|
|
continue
|
|
address = effective_address(config, site, protocol)
|
|
fixtures.append(_fixture_entry(site, protocol, None, address))
|
|
fixtures.extend(
|
|
_fixture_entry(site, protocol, route_policy, address)
|
|
for route_policy in site.interactions.routes
|
|
if route_exposed(site, protocol, route_policy)
|
|
)
|
|
return {
|
|
"schema": SCHEMA,
|
|
"site": {
|
|
"id": site.id,
|
|
"domain": site.domain,
|
|
"canonical_url": site.source.canonical_url or "",
|
|
},
|
|
"fixtures": fixtures,
|
|
}
|
|
|
|
|
|
def write_fixture_manifests(config: GatewayConfig) -> list[ManifestWriteResult]:
|
|
results: list[ManifestWriteResult] = []
|
|
for site in config.sites:
|
|
manifest = fixture_manifest(config, site)
|
|
path = manifest_path(site)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
results.append(
|
|
ManifestWriteResult(
|
|
site_id=site.id,
|
|
path=path,
|
|
fixture_count=len(manifest["fixtures"]),
|
|
)
|
|
)
|
|
return results
|
|
|
|
|
|
def manifest_path(site: SiteConfig) -> Path:
|
|
return site.outputs.root / "ampg-fixture-manifest.json"
|
|
|
|
|
|
def _fixture_entry(
|
|
site: SiteConfig,
|
|
protocol: ProtocolConfig,
|
|
route_policy: RoutePolicyConfig | None,
|
|
address: AddressRecord | None,
|
|
) -> dict[str, Any]:
|
|
url, address_status = _fixture_url(site, protocol, address)
|
|
transport = _transport_for(protocol.name)
|
|
entry = {
|
|
"protocol": protocol.name,
|
|
"renderer": protocol.renderer,
|
|
"url": _url_for_route(url, route_policy),
|
|
"address_status": address_status,
|
|
"output_path": _output_path_for_route(protocol.name, route_policy),
|
|
"checks": {
|
|
"transport": transport,
|
|
"profile": transport,
|
|
"isolation": "transport-profile",
|
|
},
|
|
"interaction": _interaction_policy(protocol, route_policy),
|
|
}
|
|
if route_policy:
|
|
entry["route"] = {
|
|
"match": route_policy.match,
|
|
"fixture_path": fixture_path(route_policy.match),
|
|
}
|
|
return entry
|
|
|
|
|
|
def _transport_for(protocol_name: str) -> str:
|
|
if protocol_name in {"clearnet", "tor", "i2p", "gemini", "reticulum", "ipfs"}:
|
|
return protocol_name
|
|
return protocol_name
|
|
|
|
|
|
def _interaction_policy(
|
|
protocol: ProtocolConfig,
|
|
route_policy: RoutePolicyConfig | None,
|
|
) -> dict[str, Any]:
|
|
if route_policy:
|
|
return {
|
|
"tier": route_policy.tier,
|
|
"identity": route_policy.identity,
|
|
"payments": route_policy.payments,
|
|
"realtime": route_policy.realtime,
|
|
"public_allowed": route_policy.public_allowed,
|
|
}
|
|
return {
|
|
"tier": str(protocol.options.get("tier", protocol.options.get("max_tier", "static"))),
|
|
"identity": str(protocol.options.get("identity", "none")),
|
|
"payments": str(protocol.options.get("payments", "none")),
|
|
"realtime": bool(protocol.options.get("realtime", False)),
|
|
"public_allowed": bool(protocol.options.get("public_allowed", True)),
|
|
}
|
|
|
|
|
|
def _url_for_route(base_url: str, route_policy: RoutePolicyConfig | None) -> str:
|
|
if not route_policy:
|
|
return base_url
|
|
route_fixture_path = fixture_path(route_policy.match)
|
|
if base_url.startswith(("rns://", "lxmf://", "nomad://", "ipfs://", "ipns://")):
|
|
return base_url
|
|
return base_url.rstrip("/") + route_fixture_path
|
|
|
|
|
|
def _output_path_for_route(protocol_name: str, route_policy: RoutePolicyConfig | None) -> str:
|
|
if not route_policy:
|
|
return protocol_name
|
|
return protocol_name + fixture_path(route_policy.match)
|
|
|
|
|
|
def _fixture_url(
|
|
site: SiteConfig,
|
|
protocol: ProtocolConfig,
|
|
address: AddressRecord | None,
|
|
) -> tuple[str, str]:
|
|
configured_url = protocol.options.get("fixture_url") or protocol.options.get("browser_url")
|
|
if configured_url:
|
|
return _with_trailing_slash(str(configured_url)), "configured"
|
|
if address:
|
|
return address.url, address.address_status
|
|
|
|
if protocol.name == "clearnet":
|
|
if site.source.canonical_url:
|
|
return _with_trailing_slash(site.source.canonical_url), "configured"
|
|
return f"https://{site.domain}/", "derived"
|
|
|
|
if protocol.name == "tor":
|
|
onion_url = protocol.options.get("onion_url")
|
|
if onion_url:
|
|
return _with_trailing_slash(str(onion_url)), "configured"
|
|
onion_location = str(protocol.options.get("onion_location", "auto"))
|
|
if onion_location and onion_location != "auto":
|
|
return _with_scheme(onion_location, "http"), "configured"
|
|
return f"http://{site.id}.onion/", "placeholder"
|
|
|
|
if protocol.name == "i2p":
|
|
i2p_url = protocol.options.get("i2p_url") or protocol.options.get("i2p_hostname")
|
|
if i2p_url:
|
|
return _with_scheme(str(i2p_url), "http"), "configured"
|
|
return f"http://{site.id}.i2p/", "placeholder"
|
|
|
|
if protocol.name == "gemini":
|
|
gemini_url = protocol.options.get("gemini_url")
|
|
if gemini_url:
|
|
return _with_scheme(str(gemini_url), "gemini"), "configured"
|
|
return f"gemini://{site.domain}/", "derived"
|
|
|
|
if protocol.name == "reticulum":
|
|
rns_url = protocol.options.get("rns_url")
|
|
if rns_url:
|
|
return _with_scheme(str(rns_url), "rns"), "configured"
|
|
return f"rns://{site.id}", "placeholder"
|
|
|
|
if protocol.name == "ipfs":
|
|
ipfs_url = protocol.options.get("ipfs_url")
|
|
if ipfs_url:
|
|
return _with_scheme(str(ipfs_url), "ipfs"), "configured"
|
|
cid = protocol.options.get("cid")
|
|
if cid:
|
|
return f"ipfs://{cid}", "configured"
|
|
return f"/ipfs/{site.id}", "placeholder"
|
|
|
|
return f"https://{site.domain}/", "derived"
|
|
|
|
|
|
def _with_scheme(value: str, scheme: str) -> str:
|
|
if "://" in value:
|
|
return _with_trailing_slash(value)
|
|
return _with_trailing_slash(f"{scheme}://{value}")
|
|
|
|
|
|
def _with_trailing_slash(value: str) -> str:
|
|
if value.startswith(("rns://", "lxmf://", "nomad://", "ipfs://", "ipns://")):
|
|
return value
|
|
if value.endswith("/"):
|
|
return value
|
|
return value + "/"
|