From 1c4cd39a90db7e598c0292a48943cbe55742bc50 Mon Sep 17 00:00:00 2001 From: Vilgot Fredenberg Date: Tue, 25 May 2021 10:22:32 +0200 Subject: [PATCH 1/3] Revamp error --- Cargo.toml | 4 +- src/endpoints.rs | 7 ++- src/error.rs | 68 +++++++++++++++++++++++++----- src/{parsing/mod.rs => parsing.rs} | 16 +++---- src/parsing/video_information.rs | 11 ++--- src/tests.rs | 1 + src/tests/endpoints.rs | 10 +++++ src/tests/endpoints_test.rs | 28 ------------ src/tests/mod.rs | 2 - 9 files changed, 86 insertions(+), 61 deletions(-) rename src/{parsing/mod.rs => parsing.rs} (59%) create mode 100644 src/tests.rs create mode 100644 src/tests/endpoints.rs delete mode 100644 src/tests/endpoints_test.rs delete mode 100644 src/tests/mod.rs diff --git a/Cargo.toml b/Cargo.toml index e575b8f..7b6f2b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["trivernis "] edition = "2018" description = "YouTube video metadata fetcher" readme = "README.md" +repository = "https://github.com/Trivernis/youtube-metadata-rs" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,8 +13,7 @@ license = "MIT" [dependencies] reqwest = "0.11.3" scraper = "0.12.0" -thiserror = "1.0.24" lazy_static = "1.4.0" [dev-dependencies] -tokio = {version = "1.5.0", features = ["macros", "rt-multi-thread"]} \ No newline at end of file +tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] } diff --git a/src/endpoints.rs b/src/endpoints.rs index 2f2e551..93f5628 100644 --- a/src/endpoints.rs +++ b/src/endpoints.rs @@ -1,10 +1,12 @@ -use crate::error::YoutubeResult; +use crate::error::Result; use crate::parsing::video_information::parse_video_information; use crate::types::VideoInformation; /// Returns information about a video /// ``` /// use youtube_metadata::get_video_information; +/// # #[tokio::test] +/// # async fn doctest() { /// let information = get_video_information("https://www.youtube.com/watch?v=dQw4w9WgXcQ") /// .await /// .unwrap(); @@ -22,8 +24,9 @@ use crate::types::VideoInformation; /// information.thumbnail, /// Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string()) /// ); +/// # } /// ``` -pub async fn get_video_information(url: &str) -> YoutubeResult { +pub async fn get_video_information(url: &str) -> Result { let response = reqwest::get(url).await?; let response_text = response.text().await?; diff --git a/src/error.rs b/src/error.rs index 5663405..332fd29 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,18 +1,64 @@ -use thiserror::Error; +use std::{ + error::Error as StdError, + fmt::{Display, Formatter, Result as FmtResult}, +}; -pub type YoutubeResult = Result; +use reqwest::Error as ReqwestError; -#[derive(Debug, Error)] -pub enum YoutubeError { - #[error(transparent)] - Reqwest(#[from] reqwest::Error), +pub type Result = std::result::Result; - #[error("Parse Error: {0}")] - ParseError(String), +#[derive(Debug)] +pub enum Error { + Reqwest(ReqwestError), + Parse(Parsing), } -impl From<&str> for YoutubeError { - fn from(s: &str) -> Self { - Self::ParseError(s.to_string()) +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + use Error::*; + match self { + Reqwest(e) => e.fmt(f), + Parse(_) => write!(f, "parse error"), + } } } + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + use Error::*; + match self { + Reqwest(e) => e.source(), + Parse(e) => Some(e), + } + } +} + +impl From for Error { + fn from(s: Parsing) -> Self { + Self::Parse(s) + } +} + +impl From for Error { + fn from(e: ReqwestError) -> Self { + Self::Reqwest(e) + } +} + +#[derive(Debug)] +pub enum Parsing { + MissingElement(String), + MissingAttribute(String), +} + +impl Display for Parsing { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + use Parsing::*; + match self { + MissingAttribute(s) => write!(f, "missing attribute: {}", s), + MissingElement(s) => write!(f, "missing element: {}", s), + } + } +} + +impl StdError for Parsing {} diff --git a/src/parsing/mod.rs b/src/parsing.rs similarity index 59% rename from src/parsing/mod.rs rename to src/parsing.rs index 6580a53..4a7526f 100644 --- a/src/parsing/mod.rs +++ b/src/parsing.rs @@ -1,17 +1,14 @@ -use crate::error::{YoutubeError, YoutubeResult}; +use crate::error::{Parsing, Result}; use scraper::{ElementRef, Html, Selector}; pub mod video_information; /// Tries selecting one element or fails if the element can't be found -fn try_select_one<'a>(document: &'a Html, selector: &Selector) -> YoutubeResult> { +fn try_select_one<'a>(document: &'a Html, selector: &Selector) -> Result> { document .select(selector) .next() - .ok_or(YoutubeError::ParseError(format!( - "Missing Element: {:?}", - selector - ))) + .ok_or_else(|| Parsing::MissingElement(format!("{:?}", selector)).into()) } /// Tries to select a given attribute @@ -19,13 +16,10 @@ fn try_select_attribute<'a>( document: &'a Html, selector: &Selector, attribute: &str, -) -> YoutubeResult<&'a str> { +) -> Result<&'a str> { let element = try_select_one(document, selector)?; element .value() .attr(attribute) - .ok_or(YoutubeError::ParseError(format!( - "Missing attribute '{}'", - attribute - ))) + .ok_or_else(|| Parsing::MissingAttribute(attribute.to_string()).into()) } diff --git a/src/parsing/video_information.rs b/src/parsing/video_information.rs index 8fa7208..1ea1447 100644 --- a/src/parsing/video_information.rs +++ b/src/parsing/video_information.rs @@ -1,9 +1,10 @@ -use crate::error::YoutubeResult; -use crate::parsing::try_select_attribute; -use crate::types::VideoInformation; +use lazy_static::lazy_static; use scraper::{Html, Selector}; -lazy_static::lazy_static! { +use super::try_select_attribute; +use crate::{error::Result, types::VideoInformation}; + +lazy_static! { static ref TITLE_SELECTOR: Selector = Selector::parse(r#"meta[property="og:title"]"#).unwrap(); static ref THUMBNAIL_SELECTOR: Selector = Selector::parse(r#"meta[property="og:image"]"#).unwrap(); static ref URL_SELECTOR: Selector = Selector::parse(r#"link[rel="canonical"]"#).unwrap(); @@ -12,7 +13,7 @@ lazy_static::lazy_static! { } /// Parses information about a video from the html -pub fn parse_video_information(html: &str) -> YoutubeResult { +pub fn parse_video_information(html: &str) -> Result { let document = Html::parse_document(html); let video_id = try_select_attribute(&document, &ID_SELECTOR, "content")?; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..b4e6f30 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1 @@ +mod endpoints; diff --git a/src/tests/endpoints.rs b/src/tests/endpoints.rs new file mode 100644 index 0000000..eca2799 --- /dev/null +++ b/src/tests/endpoints.rs @@ -0,0 +1,10 @@ +use crate::get_video_information; + +#[tokio::test] +async fn invalid_url_is_err() { + assert!( + get_video_information("https://www.youtube.com/watch?v=FFFFFFFFFFF") + .await + .is_err() + ); +} diff --git a/src/tests/endpoints_test.rs b/src/tests/endpoints_test.rs deleted file mode 100644 index d4267f5..0000000 --- a/src/tests/endpoints_test.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::endpoints::get_video_information; - -#[tokio::test] -async fn test_get_video_information() { - let information = get_video_information("https://www.youtube.com/watch?v=dQw4w9WgXcQ") - .await - .unwrap(); - assert_eq!(information.id, "dQw4w9WgXcQ".to_string()); - assert_eq!( - information.url, - "https://www.youtube.com/watch?v=dQw4w9WgXcQ".to_string() - ); - assert_eq!(information.uploader, "RickAstleyVEVO".to_string()); - assert_eq!( - information.title, - "Rick Astley - Never Gonna Give You Up (Video)".to_string() - ); - assert_eq!( - information.thumbnail, - Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string()) - ); - - assert!( - get_video_information("https://www.youtube.com/watch?v=FFFFFFFFFFF") - .await - .is_err() - ); -} diff --git a/src/tests/mod.rs b/src/tests/mod.rs deleted file mode 100644 index 7250582..0000000 --- a/src/tests/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(test)] -mod endpoints_test; From ce24a9ae91d878ab8f056ef16ec012bfb51bb551 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 12 Jun 2021 13:15:52 +0200 Subject: [PATCH 2/3] Increment version Signed-off-by: trivernis --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7b6f2b7..c64524a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "youtube-metadata" -version = "0.1.0" +version = "0.2.0" authors = ["trivernis "] edition = "2018" description = "YouTube video metadata fetcher" From f687d02f9af9f0785dfa5ccab99f74263730f32d Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 12 Jun 2021 13:30:43 +0200 Subject: [PATCH 3/3] Add GITHUB ACTIONS Signed-off-by: trivernis --- .github/workflows/build-and-test.yml | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..712e752 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,42 @@ + +name: Build and Test + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build-and-test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: rustup + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Cache build data + uses: actions/cache@v2 + with: + path: | + target + ~/.cargo/ + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Fast Fail Check + run: cargo check --verbose + + - name: Build + run: cargo build --verbose --all-features + + - name: Run tests + run: cargo test --verbose --all-features \ No newline at end of file