Описать протокол с конвертом#

Конверт не является сетевым транспортом.

Он добавляет к полезной нагрузке method, correlation id и служебные поля. Конверт принадлежит сервису, поэтому объявляется на роутере и применяется ко всем его RPC-операциям.

# examples/docs/protocols.py
class BillingService:
    protocol = JsonRpc(path="/rpc")


@dataclass(frozen=True, slots=True, kw_only=True)
class Charge(RpcOperation[Receipt]):
    __http__ = Rpc.method("billing.charge")

    account: JsonField[str]
    amount: JsonField[int]


class BillingApi(BillingService, AsyncApi):
    charge = op(Charge)


async def main() -> None:
    site = GuideSite()
    async with async_guide_client(site) as client:
        receipt = await BillingApi(client).charge(account="account-7", amount=1290)
    print(f"receipt: {receipt.reference}")
    print(site.calls[0])

JsonRpc(path="/rpc") выбирает общий HTTP-путь. Rpc.method(...) задаёт имя внутри тела, а поля операции становятся params. Публичный вызов остаётся обычным типизированным методом.

Файл целиком
"""Call a JSON-RPC service through the shared HTTP runtime."""

import asyncio
from dataclasses import dataclass

from pydantic import BaseModel

from eazy_sdk import AsyncApi, JsonField, op
from eazy_sdk.protocols import JsonRpc, Rpc, RpcOperation
from examples.docs.guide_site import GuideSite, async_guide_client


class Receipt(BaseModel):
    reference: str


# region docs: protocols
# examples/docs/protocols.py
class BillingService:
    protocol = JsonRpc(path="/rpc")


@dataclass(frozen=True, slots=True, kw_only=True)
class Charge(RpcOperation[Receipt]):
    __http__ = Rpc.method("billing.charge")

    account: JsonField[str]
    amount: JsonField[int]


class BillingApi(BillingService, AsyncApi):
    charge = op(Charge)


async def main() -> None:
    site = GuideSite()
    async with async_guide_client(site) as client:
        receipt = await BillingApi(client).charge(account="account-7", amount=1290)
    print(f"receipt: {receipt.reference}")
    print(site.calls[0])
# endregion docs: protocols


if __name__ == "__main__":
    asyncio.run(main())
receipt: receipt-42
POST api.mail.example/rpc

Место конверта в запросе#

модель → проекция → конверт → кодирование → шифрование байтов → подпись

Подпись и шифрование всего payload получают конверт целиком. Значение JSON-RPC id по умолчанию сохраняется при retry, чтобы сервер мог распознать дубликат. Для сервиса, который требует новый идентификатор, укажите id_on_retry="regenerate".

JSON-RPC обычно сообщает ошибку внутри ответа 200. rpc_result(...), rpc_error(...) и rpc_error_default(...) превращают result и error в обычные response cases, поэтому вызывающий получает тот же ApiError, что и у REST.

Пакетный JSON-RPC вызов не входит в прозрачный runtime. Частичный успех нарушает соответствие одной операции одному типизированному результату, а повтор всего batch больше нельзя считать безопасным только по свойствам отдельных элементов.

Дальше#