Skip to main content

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}