feat(card): Card::new(label) — alternativa segura a Default::default()
Cierra la trap documentada de Card::default() que devuelve id =
Ulid::nil(). Usar Card::default() viva colisionaba con cualquier otra
Card default-construida bajo el mismo id 00000...
La fix no es romper Default (sigue siendo determinista, requerido por
callers que lo usan como template para deserializacion y patterns de
busqueda) sino agregar un constructor explicito Card::new(label) que
asigna id = Ulid::new() + label provisto, manteniendo defaults seguros
en todo lo demas.
Pensado para struct-literals con override parcial:
let card = Card {
kind: CardKind::Data,
payload: Payload::Embedded(json),
..Card::new("mi-modulo.algo")
};
Refactor de call sites en codigo de produccion:
- brahman_sidecar::discovery::build_consumer_card
- nouser daemon::build_engine_card
Default queda con docstring expandida que apunta a Card::new para uso
"vivo". to_brahman_card en nouser-card NO se modifica porque asigna
el id estable de la Monada, no uno fresco.
Tests: 3 unitarios nuevos en brahman-card. 15 tests verdes (era 12).
This commit is contained in:
@@ -172,9 +172,16 @@ pub struct Card {
|
||||
}
|
||||
|
||||
impl Default for Card {
|
||||
/// Default razonable para `..Default::default()` en struct-literals.
|
||||
/// `id` queda en `Ulid::nil()` y `label` vacío — el consumidor debe
|
||||
/// sobreescribirlos antes de validar.
|
||||
/// Default determinista pensado para el patrón `..Default::default()`
|
||||
/// en struct-literals donde el caller sobreescribe `id` y `label`.
|
||||
///
|
||||
/// **Trap conocida**: `id` queda en `Ulid::nil()`. Si construís una
|
||||
/// Card "viva" para registrar en el broker, NUNCA dejes el `id`
|
||||
/// derivado de `Default` — todas las Cards default-construidas
|
||||
/// colisionarían bajo el mismo `00000000000000000000000000`. Para
|
||||
/// Cards frescas usá [`Card::new`], que asigna `Ulid::new()`.
|
||||
/// `Ulid::nil()` queda reservado para patterns de búsqueda y
|
||||
/// sentinel values en serialización.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
schema_version: CARD_SCHEMA_VERSION,
|
||||
@@ -591,6 +598,32 @@ pub enum TypeRef {
|
||||
// =====================================================================
|
||||
|
||||
impl Card {
|
||||
/// Construye una Card "viva" lista para registrarse en el broker:
|
||||
/// `id = Ulid::new()` (único), `label` provisto, todo lo demás en
|
||||
/// los defaults seguros (Payload::Virtual, Supervision::OneShot,
|
||||
/// CardKind::Ente, etc.).
|
||||
///
|
||||
/// Diseñada para usarse en struct-literals con override parcial,
|
||||
/// igual que `Default` pero sin la trap de `Ulid::nil()`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let card = Card {
|
||||
/// kind: CardKind::Data,
|
||||
/// payload: Payload::Embedded(serde_json::json!({"foo": 1})),
|
||||
/// ..Card::new("mi-modulo.algo")
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Para Cards de búsqueda/sentinel donde `nil` es semánticamente
|
||||
/// significativo, usá `Card::default()` directamente.
|
||||
pub fn new(label: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: Ulid::new(),
|
||||
label: label.into(),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializa una Card desde JSON y valida.
|
||||
pub fn from_json(src: &str) -> Result<Self, CardError> {
|
||||
let c: Self = serde_json::from_str(src)?;
|
||||
@@ -1230,4 +1263,28 @@ mod tests {
|
||||
let decoded: WireCard = postcard::from_bytes(&bytes).expect("WireCard debe decodear");
|
||||
assert_eq!(decoded.label, "x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_assigns_real_ulid_and_label() {
|
||||
let c = Card::new("nouser.engine");
|
||||
assert_eq!(c.label, "nouser.engine");
|
||||
assert_ne!(c.id, Ulid::nil(), "Card::new no debe dejar id en nil");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_yields_distinct_ids_per_call() {
|
||||
let a = Card::new("x");
|
||||
let b = Card::new("x");
|
||||
assert_ne!(a.id, b.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_keeps_nil_id_for_struct_update_pattern() {
|
||||
// Mantener este invariante explícito: Default::default() es
|
||||
// determinista y devuelve nil. Cualquier cambio aquí rompería
|
||||
// el patrón `..Default::default()` en patterns de búsqueda.
|
||||
let d = Card::default();
|
||||
assert_eq!(d.id, Ulid::nil());
|
||||
assert!(d.label.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,16 +727,13 @@ fn embed_via(
|
||||
/// Card del propio engine (kind=Ente). Es el "ser" que produce y
|
||||
/// administra Mónadas; aparece en brahman-status junto a sus Mónadas.
|
||||
fn build_engine_card() -> brahman_card::Card {
|
||||
use brahman_card::{ulid::Ulid, Card, CardKind, Lifecycle, Payload, Priority, Supervision};
|
||||
use brahman_card::{Card, CardKind, Lifecycle, Payload, Priority, Supervision};
|
||||
Card {
|
||||
schema_version: brahman_card::CARD_SCHEMA_VERSION,
|
||||
id: Ulid::new(),
|
||||
label: "brahman.nouser_engine".into(),
|
||||
payload: Payload::Virtual,
|
||||
supervision: Supervision::Delegate,
|
||||
lifecycle: Lifecycle::Daemon,
|
||||
priority: Priority::Normal,
|
||||
kind: CardKind::Ente,
|
||||
..Default::default()
|
||||
..Card::new("brahman.nouser_engine")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use brahman_card::{
|
||||
ulid::Ulid, Card, CardKind, Flow, Flows, Lifecycle, Payload, Priority, Supervision, TypeRef,
|
||||
CARD_SCHEMA_VERSION,
|
||||
};
|
||||
use brahman_handshake::client::{Client, ClientError};
|
||||
use brahman_handshake::messages::MatchEventKind;
|
||||
@@ -63,9 +62,6 @@ pub fn build_consumer_card(
|
||||
type_name: impl Into<String>,
|
||||
) -> Card {
|
||||
Card {
|
||||
schema_version: CARD_SCHEMA_VERSION,
|
||||
id: Ulid::new(),
|
||||
label: consumer_label.into(),
|
||||
payload: Payload::Virtual,
|
||||
supervision: Supervision::OneShot,
|
||||
lifecycle: Lifecycle::Oneshot,
|
||||
@@ -81,7 +77,7 @@ pub fn build_consumer_card(
|
||||
}],
|
||||
output: vec![],
|
||||
},
|
||||
..Default::default()
|
||||
..Card::new(consumer_label)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user