kb-core: hotfix Inline serde schema (struct variants)

`#[serde(tag = "kind")]` rejects newtype variants whose payload is not a
struct, so 4 of 5 `Inline` variants (`Text(String)`, `Code(String)`,
`Strong(Vec<…>)`, `Emph(Vec<…>)`) failed to serialize at runtime — only
`Link { text, href }` worked. Convert every variant to struct form so the
internally-tagged shape is well-formed and round-trips through JSON.

Add `inline_serde_round_trip` covering all five variants. Per design §9,
this is a wire-schema migration; no `docs/wire-schema/v1/*.json` change
required since `Inline` is not directly referenced there. Callers in
kb-parse-md follow in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 15:10:40 +00:00
parent 8ce44af95a
commit 606ce1cf66

View File

@@ -100,11 +100,11 @@ pub struct AudioRefBlock {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", tag = "kind")]
pub enum Inline {
Text(String),
Code(String),
Text { text: String },
Code { code: String },
Link { text: String, href: String },
Strong(Vec<Inline>),
Emph(Vec<Inline>),
Strong { children: Vec<Inline> },
Emph { children: Vec<Inline> },
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -175,3 +175,37 @@ pub struct TranscriptSegment {
pub speaker: Option<String>,
pub confidence: Option<f32>,
}
#[cfg(test)]
mod tests {
use super::*;
/// Each `Inline` variant must serialize and deserialize cleanly under
/// the internally-tagged representation. Newtype-with-primitive variants
/// (`Text(String)`, `Code(String)`, `Strong(Vec<…>)`, `Emph(Vec<…>)`)
/// previously failed at serde runtime because `tag = "kind"` cannot
/// describe a newtype carrying a non-struct value. The struct-variant
/// shape used here is the §9 schema migration.
#[test]
fn inline_serde_round_trip() {
let cases = vec![
Inline::Text { text: "hi".into() },
Inline::Code { code: "x".into() },
Inline::Link {
text: "t".into(),
href: "h".into(),
},
Inline::Strong {
children: vec![Inline::Text { text: "bold".into() }],
},
Inline::Emph {
children: vec![Inline::Text { text: "em".into() }],
},
];
for c in cases {
let s = serde_json::to_string(&c).expect("serialize");
let back: Inline = serde_json::from_str(&s).expect("deserialize");
assert_eq!(c, back);
}
}
}