feat(card): WireCard + extensions — forward-compat sin romper postcard
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>
This commit is contained in:
@@ -64,7 +64,7 @@ impl Client {
|
||||
let hello = Hello {
|
||||
schema_version: CARD_SCHEMA_VERSION,
|
||||
protocol_version: brahman_card::PROTOCOL_VERSION.to_string(),
|
||||
card,
|
||||
card: card.into(), // Card → WireCard: descarta extensions
|
||||
wit,
|
||||
};
|
||||
write_frame(&mut stream, &Frame::Hello(hello)).await?;
|
||||
|
||||
@@ -3,25 +3,26 @@
|
||||
//! Todos los mensajes que cruzan el wire son variantes de [`Frame`].
|
||||
|
||||
use brahman_broker::MatchStrategy;
|
||||
use brahman_card::{TypeRef, WitInterface};
|
||||
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 completa para que el servidor
|
||||
/// la valide e indexe. Opcionalmente, una `WitInterface` ya extraída — si
|
||||
/// está presente, el módulo es "consciente" y el server lo registra como
|
||||
/// `ResolvedCard::from_conscious`; si no, como `from_agnostic`.
|
||||
/// 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.
|
||||
pub card: brahman_card::Card,
|
||||
/// 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)]
|
||||
|
||||
@@ -367,7 +367,9 @@ impl Session {
|
||||
}
|
||||
|
||||
let session_id = Ulid::new();
|
||||
self.register_session(session_id, hello.card, hello.wit).await;
|
||||
// WireCard → Card: extensiones quedan vacías post-wire (es el contrato).
|
||||
let card: Card = hello.card.into();
|
||||
self.register_session(session_id, card, hello.wit).await;
|
||||
|
||||
let ack = HelloAck {
|
||||
server_version: crate::HANDSHAKE_VERSION.to_string(),
|
||||
@@ -416,7 +418,10 @@ impl Session {
|
||||
brahman_card::PROTOCOL_VERSION
|
||||
)));
|
||||
}
|
||||
if let Err(e) = hello.card.validate() {
|
||||
// Validamos contra Card (la rica) — convertir es barato y centraliza
|
||||
// la lógica de validación en un solo lugar.
|
||||
let as_card: Card = Card::from(hello.card.clone());
|
||||
if let Err(e) = as_card.validate() {
|
||||
return Some(HandshakeError::InvalidCard(e.to_string()));
|
||||
}
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user