From cc50862408e5ebcd4d0bd0b9b240ac0f8987c68c Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 23 Jan 2023 23:06:56 +0100 Subject: [PATCH] feat: handle error 400 for invalid ctokens --- src/client/mod.rs | 29 ++++++++++++++++++++--------- src/client/response/mod.rs | 14 ++++++++++++++ src/error.rs | 3 +++ tests/youtube.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 41ce447..93ab2ed 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -29,7 +29,7 @@ use log::{debug, error, warn}; use once_cell::sync::Lazy; use rand::Rng; use regex::Regex; -use reqwest::{header, Client, ClientBuilder, Request, RequestBuilder, Response}; +use reqwest::{header, Client, ClientBuilder, Request, RequestBuilder, Response, StatusCode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use time::{Duration, OffsetDateTime}; use tokio::sync::RwLock; @@ -1087,14 +1087,25 @@ impl RustyPipeQuery { if status.is_client_error() || status.is_server_error() { let status_code = status.as_u16(); - return if status_code == 404 { - Err(Error::Extraction(ExtractionError::ContentUnavailable( - "Not found".into(), - ))) - } else { - let e = Error::HttpStatus(status_code); - create_report(Level::ERR, Some(e.to_string()), vec![]); - Err(e) + + return match status { + StatusCode::NOT_FOUND => Err(Error::Extraction( + ExtractionError::ContentUnavailable("404 Not found".into()), + )), + StatusCode::BAD_REQUEST => { + let error_res = serde_json::from_str::(&resp_str); + Err(Error::Extraction(ExtractionError::BadRequest( + error_res + .map(|r| r.error.message) + .unwrap_or_default() + .into(), + ))) + } + _ => { + let e = Error::HttpStatus(status_code); + create_report(Level::ERR, Some(e.to_string()), vec![]); + Err(e) + } }; } diff --git a/src/client/response/mod.rs b/src/client/response/mod.rs index 82d7d6d..f334062 100644 --- a/src/client/response/mod.rs +++ b/src/client/response/mod.rs @@ -233,6 +233,20 @@ pub(crate) struct MusicContinuationDataInner { pub continuation: String, } +// ERROR + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ErrorResponse { + pub error: ErrorResponseContent, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ErrorResponseContent { + pub message: String, +} + /* #MAPPING */ diff --git a/src/error.rs b/src/error.rs index 57dc990..abd7989 100644 --- a/src/error.rs +++ b/src/error.rs @@ -74,6 +74,9 @@ pub enum ExtractionError { /// Content is not available / does not exist #[error("Content is not available. Reason: {0}")] ContentUnavailable(Cow<'static, str>), + /// Bad request (Error 400 from YouTube), probably invalid input parameters + #[error("Bad request. Reason: {0}")] + BadRequest(Cow<'static, str>), /// Error deserializing YouTube's response JSON #[error("deserialization error: {0}")] Deserialization(#[from] serde_json::Error), diff --git a/tests/youtube.rs b/tests/youtube.rs index 0bf7260..90f2122 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::fmt::Display; use rstest::rstest; +use rustypipe::model::paginator::ContinuationEndpoint; use rustypipe::validate; use time::macros::date; use time::OffsetDateTime; @@ -2219,6 +2220,32 @@ async fn ab3_search_channel_handles() { .unwrap(); } +//#MISCELLANEOUS + +#[rstest] +#[case::desktop(ContinuationEndpoint::Browse)] +#[case::music(ContinuationEndpoint::MusicBrowse)] +#[tokio::test] +async fn invalid_ctoken(#[case] ep: ContinuationEndpoint) { + let rp = RustyPipe::builder().strict().build(); + + let e = rp + .query() + .continuation::("Abcd", ep, None) + .await + .unwrap_err(); + + match e { + Error::Extraction(e) => match e { + ExtractionError::BadRequest(msg) => { + assert_eq!(msg, "Request contains an invalid argument.") + } + _ => panic!("invalid error: {}", e), + }, + _ => panic!("invalid error: {}", e), + } +} + //#TESTUTIL /// Assert equality within 10% margin