use std::panic::Location;
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::response::{IntoResponse, Response};
use futures::future::BoxFuture;
use futures::FutureExt;
use crate::servers::http::v1::query::Query;
use crate::servers::http::v1::requests::scrape::{ParseScrapeQueryError, Scrape};
use crate::servers::http::v1::responses;
pub struct ExtractRequest(pub Scrape);
impl<S> FromRequestParts<S> for ExtractRequest
where
S: Send + Sync,
{
type Rejection = Response;
#[must_use]
fn from_request_parts<'life0, 'life1, 'async_trait>(
parts: &'life0 mut Parts,
_state: &'life1 S,
) -> BoxFuture<'async_trait, Result<Self, Self::Rejection>>
where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
{
async {
match extract_scrape_from(parts.uri.query()) {
Ok(scrape_request) => Ok(ExtractRequest(scrape_request)),
Err(error) => Err(error.into_response()),
}
}
.boxed()
}
}
fn extract_scrape_from(maybe_raw_query: Option<&str>) -> Result<Scrape, responses::error::Error> {
if maybe_raw_query.is_none() {
return Err(responses::error::Error::from(ParseScrapeQueryError::MissingParams {
location: Location::caller(),
}));
}
let query = maybe_raw_query.unwrap().parse::<Query>();
if let Err(error) = query {
return Err(responses::error::Error::from(error));
}
let scrape_request = Scrape::try_from(query.unwrap());
if let Err(error) = scrape_request {
return Err(responses::error::Error::from(error));
}
Ok(scrape_request.unwrap())
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use torrust_tracker_primitives::info_hash::InfoHash;
use super::extract_scrape_from;
use crate::servers::http::v1::requests::scrape::Scrape;
use crate::servers::http::v1::responses::error::Error;
struct TestInfoHash {
pub bencoded: String,
pub value: InfoHash,
}
fn test_info_hash() -> TestInfoHash {
TestInfoHash {
bencoded: "%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0".to_owned(),
value: InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap(),
}
}
fn assert_error_response(error: &Error, error_message: &str) {
assert!(
error.failure_reason.contains(error_message),
"Error response does not contain message: '{error_message}'. Error: {error:?}"
);
}
#[test]
fn it_should_extract_the_scrape_request_from_the_url_query_params() {
let info_hash = test_info_hash();
let raw_query = format!("info_hash={}", info_hash.bencoded);
let scrape = extract_scrape_from(Some(&raw_query)).unwrap();
assert_eq!(
scrape,
Scrape {
info_hashes: vec![info_hash.value],
}
);
}
#[test]
fn it_should_extract_the_scrape_request_from_the_url_query_params_with_more_than_one_info_hash() {
let info_hash = test_info_hash();
let raw_query = format!("info_hash={}&info_hash={}", info_hash.bencoded, info_hash.bencoded);
let scrape = extract_scrape_from(Some(&raw_query)).unwrap();
assert_eq!(
scrape,
Scrape {
info_hashes: vec![info_hash.value, info_hash.value],
}
);
}
#[test]
fn it_should_reject_a_request_without_query_params() {
let response = extract_scrape_from(None).unwrap_err();
assert_error_response(
&response,
"Cannot parse query params for scrape request: missing query params for scrape request",
);
}
#[test]
fn it_should_reject_a_request_with_a_query_that_cannot_be_parsed() {
let invalid_query = "param1=value1=value2";
let response = extract_scrape_from(Some(invalid_query)).unwrap_err();
assert_error_response(&response, "Cannot parse query params");
}
#[test]
fn it_should_reject_a_request_with_a_query_that_cannot_be_parsed_into_a_scrape_request() {
let response = extract_scrape_from(Some("param1=value1")).unwrap_err();
assert_error_response(&response, "Cannot parse query params for scrape request");
}
}