Skip to main content

essence/api/
map.rs

1use crate::{
2    crawler::mapper,
3    error::ScrapeError,
4    types::{MapRequest, MapResponse},
5    validation,
6};
7use axum::Json;
8use tracing::{error, info};
9
10/// Handler for POST /api/v1/map
11pub async fn map_handler(
12    Json(request): Json<MapRequest>,
13) -> Result<Json<MapResponse>, ScrapeError> {
14    info!("Map request received for URL: {}", request.url);
15
16    // Validate request (includes SSRF protection)
17    validation::validate_map_request(&request).await?;
18
19    // Discover URLs
20    let links = mapper::discover_urls(&request.url, &request)
21        .await
22        .map_err(|e| {
23            error!("Failed to discover URLs for {}: {}", request.url, e);
24            e
25        })?;
26
27    info!(
28        "Successfully discovered {} URLs for: {}",
29        links.len(),
30        request.url
31    );
32
33    Ok(Json(MapResponse::success(links)))
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[tokio::test]
41    async fn test_map_handler_invalid_url() {
42        let request = MapRequest {
43            url: "".to_string(),
44            search: None,
45            ignore_sitemap: None,
46            include_subdomains: None,
47            limit: None,
48        };
49
50        let result = map_handler(Json(request)).await;
51        assert!(result.is_err());
52    }
53
54    #[tokio::test]
55    async fn test_map_handler_limit_validation() {
56        let request = MapRequest {
57            url: "https://example.com".to_string(),
58            search: None,
59            ignore_sitemap: None,
60            include_subdomains: None,
61            limit: Some(200000), // Exceeds max
62        };
63
64        let result = map_handler(Json(request)).await;
65        assert!(result.is_err());
66    }
67}