use crate::transcript::error::{Result, TranscriptError};
use crate::transcript::source::Transcript;
pub fn render(transcript: &Transcript) -> Result<String> {
serde_json::to_string_pretty(transcript)
.map(|s| {
let mut s = s;
s.push('\n');
s
})
.map_err(|e| TranscriptError::ParseError(format!("json serialisation failed: {e}")))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::transcript::cue::Cue;
use crate::transcript::source::TrackKind;
fn fixture(cues: Vec<Cue>) -> Transcript {
Transcript {
source: "mock".into(),
locator_id: "abc".into(),
language: "en".into(),
kind: TrackKind::Manual,
cues,
}
}
#[test]
fn empty_cues_serialises() {
let out = render(&fixture(vec![])).unwrap();
let value: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(value["cues"], serde_json::json!([]));
}
#[test]
fn includes_top_level_metadata() {
let out = render(&fixture(vec![])).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["source"], "mock");
assert_eq!(v["locator_id"], "abc");
assert_eq!(v["language"], "en");
assert_eq!(v["kind"], "manual");
}
#[test]
fn cues_have_timing_fields() {
let out = render(&fixture(vec![Cue::new(123, 456, "hi")])).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
let cue = &v["cues"][0];
assert_eq!(cue["start_ms"], 123);
assert_eq!(cue["end_ms"], 456);
assert_eq!(cue["text"], "hi");
}
#[test]
fn output_is_pretty_printed() {
let out = render(&fixture(vec![Cue::new(0, 1, "x")])).unwrap();
assert!(out.contains("\n "));
}
#[test]
fn output_ends_with_trailing_newline() {
let out = render(&fixture(vec![])).unwrap();
assert!(out.ends_with('\n'));
}
#[test]
fn round_trips_through_serde() {
let original = fixture(vec![
Cue::new(0, 1_000, "hello"),
Cue::new(1_000, 2_500, "world\nlines"),
]);
let out = render(&original).unwrap();
let back: Transcript = serde_json::from_str(&out).unwrap();
assert_eq!(original, back);
}
#[test]
fn track_kind_translated_serialised_lowercase() {
let mut t = fixture(vec![]);
t.kind = TrackKind::Translated;
let out = render(&t).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["kind"], "translated");
}
#[test]
fn special_characters_in_text_are_escaped() {
let t = fixture(vec![Cue::new(0, 1, "quote \" backslash \\ newline\n")]);
let out = render(&t).unwrap();
let back: Transcript = serde_json::from_str(&out).unwrap();
assert_eq!(back.cues[0].text, "quote \" backslash \\ newline\n");
}
}