url_preview/
lib.rs

1//! # url-preview
2//!
3//! A high-performance Rust library for generating rich URL previews with specialized support
4//! for Twitter/X and GitHub.
5//!
6//! ## Features
7//!
8//! - **High Performance**: Optimized with concurrent processing and smart caching
9//! - **Platform Support**: Specialized handlers for Twitter/X and GitHub
10//! - **Rich Metadata**: Extract titles, descriptions, images, and more
11//! - **Security**: SSRF protection, content limits, and URL validation
12//! - **Error Handling**: Detailed error types for better debugging
13//! - **Flexible Configuration**: Customize timeouts, user agents, caching, and security policies
14//!
15//! ## Quick Start
16//!
17//! ```rust,no_run
18//! use url_preview::{PreviewService, PreviewError};
19//!
20//! #[tokio::main]
21//! async fn main() -> Result<(), PreviewError> {
22//!     let service = PreviewService::new();
23//!     let preview = service.generate_preview("https://www.rust-lang.org").await?;
24//!
25//!     println!("Title: {:?}", preview.title);
26//!     println!("Description: {:?}", preview.description);
27//!     Ok(())
28//! }
29//! ```
30//!
31//! ## Security (New in v0.5.0)
32//!
33//! The library includes comprehensive security features by default:
34//!
35//! ```rust,no_run
36//! use url_preview::{PreviewService, PreviewError};
37//!
38//! # async fn example() {
39//! let service = PreviewService::new();
40//! 
41//! // These will be blocked by default
42//! match service.generate_preview("http://localhost:8080").await {
43//!     Err(PreviewError::LocalhostBlocked) => println!("Localhost blocked"),
44//!     _ => {}
45//! }
46//!
47//! match service.generate_preview("http://192.168.1.1").await {
48//!     Err(PreviewError::PrivateIpBlocked(ip)) => println!("Private IP {} blocked", ip),
49//!     _ => {}
50//! }
51//! # }
52//! ```
53//!
54//! ## Error Handling
55//!
56//! The library provides specific error types:
57//!
58//! ```rust,no_run
59//! use url_preview::{PreviewService, PreviewError};
60//!
61//! # async fn example() {
62//! let service = PreviewService::new();
63//! match service.generate_preview("https://invalid.url").await {
64//!     Ok(preview) => { /* handle preview */ },
65//!     Err(PreviewError::NotFound(msg)) => println!("404: {}", msg),
66//!     Err(PreviewError::DnsError(msg)) => println!("DNS failed: {}", msg),
67//!     Err(PreviewError::TimeoutError(msg)) => println!("Timeout: {}", msg),
68//!     Err(PreviewError::ServerError { status, message }) => {
69//!         println!("Server error {}: {}", status, message)
70//!     },
71//!     Err(e) => println!("Other error: {}", e),
72//! }
73//! # }
74//! ```
75
76use async_trait::async_trait;
77
78#[cfg(feature = "cache")]
79mod cache;
80mod error;
81mod extractor;
82mod fetcher;
83#[cfg(feature = "github")]
84mod github_types;
85#[cfg(feature = "logging")]
86mod logging;
87mod preview_generator;
88mod preview_service;
89mod security;
90mod utils;
91
92#[cfg(feature = "browser")]
93mod mcp_client;
94#[cfg(feature = "browser")]
95mod browser_fetcher;
96
97#[cfg(feature = "llm")]
98mod llm_extractor;
99#[cfg(feature = "llm")]
100mod llm_providers;
101#[cfg(feature = "llm")]
102mod llm_config;
103
104#[cfg(feature = "cache")]
105pub use cache::Cache;
106pub use error::PreviewError;
107pub use extractor::MetadataExtractor;
108pub use fetcher::{FetchResult, Fetcher, FetcherConfig};
109#[cfg(feature = "github")]
110pub use github_types::{is_github_url, GitHubBasicPreview, GitHubDetailedInfo, GitHubRepository};
111#[cfg(feature = "logging")]
112pub use logging::{log_error_card, log_preview_card, setup_logging, LogConfig, LogLevelGuard};
113pub use preview_generator::{CacheStrategy, UrlPreviewGenerator};
114pub use preview_service::{PreviewService, PreviewServiceConfig, MAX_CONCURRENT_REQUESTS};
115pub use security::{ContentLimits, UrlValidationConfig, UrlValidator};
116
117#[cfg(feature = "browser")]
118pub use mcp_client::{McpClient, McpConfig, McpTransport, BrowserUsagePolicy};
119#[cfg(feature = "browser")]
120pub use browser_fetcher::{BrowserFetcher, BrowserPreviewService};
121
122#[cfg(feature = "llm")]
123pub use llm_extractor::{LLMExtractor, LLMExtractorConfig, ContentFormat, ProcessedContent, ExtractionResult, LLMProvider, TokenUsage};
124#[cfg(feature = "llm")]
125pub use llm_providers::MockProvider;
126#[cfg(feature = "llm")]
127pub use llm_providers::openai::OpenAIProvider;
128#[cfg(feature = "llm")]
129pub use llm_providers::anthropic::AnthropicProvider;
130#[cfg(feature = "llm")]
131pub use llm_providers::claude_compat::ClaudeCompatProvider;
132#[cfg(all(feature = "llm", feature = "cc-sdk"))]
133pub use llm_providers::claude_code::ClaudeCodeProvider;
134#[cfg(feature = "llm")]
135pub use llm_providers::LocalProvider;
136#[cfg(feature = "llm")]
137pub use llm_config::{ApiKeyValidator, LLMConfig};
138
139#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
140pub struct Preview {
141    pub url: String,
142    pub title: Option<String>,
143    pub description: Option<String>,
144    pub image_url: Option<String>,
145    pub favicon: Option<String>,
146    pub site_name: Option<String>,
147}
148
149#[async_trait]
150pub trait PreviewGenerator {
151    async fn generate_preview(&self, url: &str) -> Result<Preview, PreviewError>;
152}
153
154#[cfg(feature = "twitter")]
155pub fn is_twitter_url(url: &str) -> bool {
156    url.contains("twitter.com") || url.contains("x.com")
157}
158
159#[cfg(not(feature = "twitter"))]
160pub fn is_twitter_url(_url: &str) -> bool {
161    false
162}