oembed_rs/
lib.rs

1//! # Documentation
2//!
3//! This crate provides a simple interface for fetching oEmbed data from known providers.
4//!
5//! ## Example
6//! ```rust
7//! use oembed_rs::{find_provider, fetch, ConsumerRequest};
8//!
9//! async fn example() {
10//!     let url = "https://twitter.com/user/status/1000000000000000000";
11//!     let (_, endpoint) = find_provider(url).expect("unknown provider");
12//!
13//!     let response = fetch(
14//!        &endpoint.url,
15//!        ConsumerRequest {
16//!            url,
17//!            max_width: Some(1000),
18//!            max_height: Some(500),
19//!            ..ConsumerRequest::default()
20//!        },
21//!     )
22//!     .await
23//!     .expect("failed to fetch oembed data");
24//! }
25//! ```
26
27use lazy_static::lazy_static;
28
29mod error;
30mod request;
31mod spec;
32
33pub use error::Error;
34pub use request::{fetch, ConsumerRequest};
35pub use spec::*;
36
37lazy_static! {
38    static ref PROVIDERS: Vec<Provider> =
39        serde_json::from_slice(include_bytes!("../providers.json"))
40            .expect("failed to load providers");
41}
42
43/// Find the oEmbed provider and endpoint based on the URL
44pub fn find_provider(url: &str) -> Option<(&Provider, &Endpoint)> {
45    PROVIDERS.iter().find_map(|p| {
46        p.endpoints
47            .iter()
48            .find(|e| e.schemes.iter().any(|s| matches_scheme(s, url)))
49            .map(|e| (p, e))
50    })
51}
52
53/// Checks if the URL matches the scheme
54pub fn matches_scheme(scheme: &str, mut url: &str) -> bool {
55    let mut fragments = scheme.split('*').filter(|f| !f.is_empty()).peekable();
56
57    while let Some(fragment) = fragments.next() {
58        if !url.starts_with(fragment) {
59            return false;
60        }
61
62        url = &url[fragment.len()..];
63
64        if let Some(fragment) = fragments.peek() {
65            let Some(idx) = url.find(fragment) else {
66                return false;
67            };
68
69            url = &url[idx..];
70        }
71    }
72
73    scheme.ends_with('*') || url.is_empty()
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_twitter_provider() {
82        let url = "https://twitter.com/user/status/1640004220000000000?s=20";
83        let (provider, endpoint) = find_provider(url).unwrap();
84
85        assert_eq!(provider.provider_name, "Twitter");
86        assert_eq!(endpoint.url, "https://publish.twitter.com/oembed");
87    }
88
89    #[test]
90    fn test_youtube_provider() {
91        let url = "https://youtu.be/rAn0MId";
92        let (provider, endpoint) = find_provider(url).unwrap();
93
94        assert_eq!(provider.provider_name, "YouTube");
95        assert_eq!(endpoint.url, "https://www.youtube.com/oembed");
96    }
97
98    #[test]
99    fn test_youtube_provider_with_query() {
100        let url = "https://www.youtube.com/watch?v=rAn0MId";
101        let (provider, endpoint) = find_provider(url).unwrap();
102
103        assert_eq!(provider.provider_name, "YouTube");
104        assert_eq!(endpoint.url, "https://www.youtube.com/oembed");
105    }
106
107    #[test]
108    fn test_spotify() {
109        let url = "https://open.spotify.com/playlist/5R3Pzqu8SLyH9cw9WSXw91";
110        let (provider, endpoint) = find_provider(url).unwrap();
111
112        assert_eq!(provider.provider_name, "Spotify");
113        assert_eq!(endpoint.url, "https://open.spotify.com/oembed");
114    }
115
116    #[test]
117    fn test_invalid() {
118        let url = "https://twitter.nl/user/status/1640004220000000000?s=20";
119        assert!(find_provider(url).is_none());
120
121        let url = "https://www.youtube.com/watcx?v=test";
122        assert!(find_provider(url).is_none());
123    }
124}