f19ca723b6
Restaura el campo extensions de Card que había caído al adoptar postcard (serde_json::Value usa secuencias/maps de longitud dinámica). La solución es separar dos formas: - Card (la rica): para JSON/TOML. Tiene extensions: BTreeMap<String, serde_json::Value> con #[serde(flatten, skip_serializing_if = is_empty)]. Los campos desconocidos del archivo sobreviven el roundtrip. - WireCard (la slim): para postcard. Mismo schema sin extensions y con genesis: Vec<WireCard> recursivo. Postcard-friendly por construcción. Conversiones From<Card> for WireCard (descarta extensions) y From<WireCard> for Card (extensiones quedan vacías post-wire). El contrato es explícito: extensions son anotaciones locales que sobreviven file I/O pero NO cruzan al Init. brahman-handshake::Hello.card cambia de Card a WireCard. Client hace card.into() al enviar; Server hace hello.card.into() para volver a Card antes de validar/registrar. Tests: - 3 nuevos en brahman-card: extensions_preserved_in_json_roundtrip, wire_card_roundtrip_strips_extensions, wire_card_postcard_friendly (verifica que postcard::to_allocvec(&wire) NO falla — caso que rompía con Card.extensions populadas). - 1 ajuste en handshake/tests/handshake.rs (struct-literal de Hello ahora con card: sample_card(...).into()). - brahman-card: postcard como dev-dep. Tests acumulados: 35 (card 11, broker 11, handshake codec+transport 2 + integ 7, card-wit 4, admin 0). 0 errores, 0 warnings (vienen del commit anterior9420eae). CHANGELOG.md actualizado con esta entrada y con el commit9420eae("probando" del usuario, limpieza de 17 warnings dead-code). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
130 lines
4.4 KiB
Rust
130 lines
4.4 KiB
Rust
//! Mensajes del protocolo de handshake.
|
|
//!
|
|
//! Todos los mensajes que cruzan el wire son variantes de [`Frame`].
|
|
|
|
use brahman_broker::MatchStrategy;
|
|
use brahman_card::{TypeRef, WireCard, WitInterface};
|
|
use serde::{Deserialize, Serialize};
|
|
use ulid::Ulid;
|
|
|
|
/// Identificador de sesión emitido por el servidor en `HelloAck`.
|
|
pub type SessionId = Ulid;
|
|
|
|
/// Saludo inicial del módulo. Lleva la Card en forma `WireCard`
|
|
/// (postcard-friendly: sin extensiones JSON arbitrarias). El servidor
|
|
/// la convierte a `Card` para uso interno. Opcionalmente, una
|
|
/// `WitInterface` ya extraída — si está presente, el módulo es
|
|
/// "consciente" y el server lo registra como `ResolvedCard::from_conscious`.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Hello {
|
|
/// Versión del schema de Card que el cliente sigue.
|
|
pub schema_version: u16,
|
|
/// Versión del protocolo handshake del cliente.
|
|
pub protocol_version: String,
|
|
/// Tarjeta de Presentación, proyectada al wire.
|
|
pub card: WireCard,
|
|
/// Interfaz WIT extraída por el cliente (típicamente con
|
|
/// `brahman-card-wit`). `None` si el módulo es agnóstico.
|
|
#[serde(default)]
|
|
pub wit: Option<WitInterface>,
|
|
}
|
|
|
|
/// Respuesta del servidor a un `Hello` aceptado.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HelloAck {
|
|
/// Versión del crate del servidor.
|
|
pub server_version: String,
|
|
/// Versión del protocolo soportada por el servidor.
|
|
pub protocol_version: String,
|
|
/// Identificador de sesión asignado.
|
|
pub session: SessionId,
|
|
/// `true` si el Init está vinculado al servidor; `false` si el servidor
|
|
/// corre standalone (modo degradado).
|
|
pub init_attached: bool,
|
|
}
|
|
|
|
/// Latido del cliente. El servidor responde con [`Pong`] llevando su reloj.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Ping {
|
|
pub session: SessionId,
|
|
}
|
|
|
|
/// Respuesta a un `Ping` con timestamp del servidor (ms desde UNIX_EPOCH).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Pong {
|
|
pub timestamp_ms: u64,
|
|
}
|
|
|
|
/// Cierre cooperativo de la sesión por parte del cliente.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Farewell {
|
|
pub session: SessionId,
|
|
}
|
|
|
|
/// Errores del protocolo emitidos por el servidor.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
|
pub enum HandshakeError {
|
|
#[error("protocolo incompatible: {0}")]
|
|
ProtocolMismatch(String),
|
|
#[error("card inválida: {0}")]
|
|
InvalidCard(String),
|
|
#[error("schema de card incompatible: cliente={client}, servidor={server}")]
|
|
SchemaMismatch { client: u16, server: u16 },
|
|
#[error("sin autorización: {0}")]
|
|
Unauthorized(String),
|
|
#[error("capacidad requerida no satisfecha: {0}")]
|
|
CapabilityUnmet(String),
|
|
#[error("rechazado: {0}")]
|
|
Rejected(String),
|
|
#[error("error interno: {0}")]
|
|
Internal(String),
|
|
}
|
|
|
|
/// Notificación push del server al consumer: un match disponible o perdido.
|
|
///
|
|
/// El server emite `Available` cuando un productor empieza a satisfacer un
|
|
/// `flow.input` del consumer (ya sea porque el productor acaba de
|
|
/// registrarse, o porque cambió el match anterior). Emite `Lost` cuando
|
|
/// el productor previo dejó de satisfacer el input (desregistro o
|
|
/// cambio de match).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MatchEvent {
|
|
pub kind: MatchEventKind,
|
|
/// Nombre del input del consumer al que aplica el evento.
|
|
pub consumer_flow: String,
|
|
/// Sesión y label del productor (en `Lost` puede ser nil/vacío).
|
|
pub producer_session: SessionId,
|
|
pub producer_label: String,
|
|
pub producer_flow: String,
|
|
/// Tipo del flujo matcheado.
|
|
pub ty: TypeRef,
|
|
/// Estrategia que ganó (relevante en `Available`).
|
|
pub via: MatchStrategy,
|
|
/// `true` si fue resuelto por `pin_to`.
|
|
pub pinned: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum MatchEventKind {
|
|
Available,
|
|
Lost,
|
|
}
|
|
|
|
/// Frame único de wire — discriminada por variante. Cada conexión es un
|
|
/// stream de frames.
|
|
///
|
|
/// Direcciones:
|
|
/// - Cliente → Server: `Hello`, `Ping`, `Farewell`.
|
|
/// - Server → Cliente: `HelloAck`, `Pong`, `Error`, `MatchEvent`.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum Frame {
|
|
Hello(Hello),
|
|
HelloAck(HelloAck),
|
|
Ping(Ping),
|
|
Pong(Pong),
|
|
Farewell(Farewell),
|
|
Error(HandshakeError),
|
|
MatchEvent(MatchEvent),
|
|
}
|