Por qué el payload y no el enum
La generación de bindings da un argumento nuevo, distinto del de la fase 18, para no describir el enum de error: describiría su forma de Rust y no la que sale por el puente.
Qué describe la derivación
La derivación que hace que un tipo sepa contarse describe su forma serializada, y para eso lee los atributos de serde: el renombrado, el etiquetado, la omisión. Eso funciona muy bien para los tipos de datos.
Con un enum de error hay una diferencia que rompe la correspondencia:
#[derive(thiserror::Error, Debug, serde::Serialize, specta::Type)]pub enum ProyectoError { #[error("el proyecto {0} no existe")] NoExiste(ProyectoId), #[error("error de entrada/salida")] Io(#[from] std::io::Error), // ← aquí empieza el problema}Dos cosas fallan a la vez:
El error envuelto no se puede describir. El error de entrada y salida de la biblioteca estándar no implementa ni la serialización ni la descripción, y no puedes implementárselas tú porque no es tuyo. Así que o quitas la variante, o envuelves el error en una cadena, o el proyecto no compila.
Lo que se describe no es lo que cruza. Si resuelves lo anterior convirtiendo cada variante a texto —que es lo que hace thiserror con su mensaje— entonces el tipo generado describe una unión con las variantes de Rust, mientras que lo que el frontend recibe de verdad depende de cómo lo serialices. Los dos pueden divergir sin que nada avise, que es exactamente el problema que la generación venía a resolver.
El desajuste, reproducido
Por qué el payload no tiene el problema
Porque no tiene otra vida. El enum de error existe para el código de Rust: se propaga, se inspecciona, envuelve errores ajenos, alimenta el registro. Tiene una forma interna rica que sirve para todo eso.
El payload existe solo para cruzar. Sus dos campos son cadenas, se serializan de una única forma posible, y nadie los usa para otra cosa. Describirlo produce un tipo que no puede desajustarse con la realidad porque no hay dos realidades.
// El único tipo de error que se describe, porque es el único que cruza.#[derive(Debug, serde::Serialize, specta::Type)]#[serde(rename_all = "camelCase")]pub struct ErrorPayload { pub code: String, pub message: String,}Y en el fichero generado sale un tipo de dos campos, que es exactamente lo que el frontend recibe.