omni_dev/transcript/detect.rs
1//! Source auto-detection: map a locator to the source that recognises it.
2//!
3//! Backs the provider-less `omni-dev transcript fetch <url>` path (#1187).
4//! Each registered source's static [`TranscriptSource::matches`] is probed in
5//! registration order; the first match is constructed and returned behind a
6//! `Box<dyn TranscriptSource>` for runtime dispatch. Detection is pure string
7//! inspection — no network — so an unrecognised locator fails fast before any
8//! HTTP.
9
10use crate::transcript::error::{Result, TranscriptError};
11use crate::transcript::source::TranscriptSource;
12use crate::transcript::sources::youtube::Youtube;
13
14/// Probe every registered source in priority order and construct the first
15/// whose [`TranscriptSource::matches`] recognises `url`.
16///
17/// Returns [`TranscriptError::InvalidLocator`] when no registered source claims
18/// the locator.
19///
20/// Registering a new source is one arm here plus a `pub mod` in
21/// [`crate::transcript::sources`] — see the "Adding a new source" recipe in
22/// `docs/transcript.md`.
23pub fn detect(url: &str) -> Result<Box<dyn TranscriptSource>> {
24 if Youtube::matches(url) {
25 return Ok(Box::new(Youtube::new()?));
26 }
27
28 Err(TranscriptError::InvalidLocator(format!(
29 "no transcript source recognises `{url}`"
30 )))
31}
32
33#[cfg(test)]
34#[allow(clippy::unwrap_used, clippy::expect_used)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn detects_youtube_watch_url() {
40 let source = detect("https://www.youtube.com/watch?v=dQw4w9WgXcQ").unwrap();
41 assert_eq!(source.name(), "youtube");
42 }
43
44 #[test]
45 fn detects_youtube_short_url() {
46 let source = detect("https://youtu.be/dQw4w9WgXcQ").unwrap();
47 assert_eq!(source.name(), "youtube");
48 }
49
50 #[test]
51 fn detects_bare_youtube_id() {
52 let source = detect("dQw4w9WgXcQ").unwrap();
53 assert_eq!(source.name(), "youtube");
54 }
55
56 #[test]
57 fn unrecognised_locator_is_invalid_locator() {
58 // `Box<dyn TranscriptSource>` is not `Debug`, so use `matches!` rather
59 // than `unwrap_err` (which needs the `Ok` type to be `Debug`).
60 assert!(matches!(
61 detect("https://vimeo.com/76979871"),
62 Err(TranscriptError::InvalidLocator(msg)) if msg.contains("vimeo.com")
63 ));
64 }
65}