From 831f816447022b757f86f8cc5edeba5dca1e297c Mon Sep 17 00:00:00 2001 From: MedzikUser Date: Sun, 12 Jun 2022 17:28:25 +0200 Subject: [PATCH] refactor code --- Cargo.lock | 5 ++- Cargo.toml | 28 +++---------- README.md | 16 ++++++-- src/api/client.rs | 41 +++++++++---------- src/api/error.rs | 38 +++++++++++++++++ src/api/get_image.rs | 29 ------------- src/api/image_type.rs | 2 + src/api/mod.rs | 56 ++++---------------------- src/api/{ => requests}/delete_image.rs | 19 ++------- src/api/requests/get_image.rs | 24 +++++++++++ src/api/requests/mod.rs | 9 +++++ src/api/{ => requests}/rate_limit.rs | 19 +++------ src/api/{ => requests}/upload_image.rs | 26 +++++------- src/api/send_api_request.rs | 38 +++++++++++++++++ src/cli/parse.rs | 6 +-- src/lib.rs | 12 +++++- src/main.rs | 5 +-- 17 files changed, 197 insertions(+), 176 deletions(-) create mode 100644 src/api/error.rs delete mode 100644 src/api/get_image.rs rename src/api/{ => requests}/delete_image.rs (50%) create mode 100644 src/api/requests/get_image.rs create mode 100644 src/api/requests/mod.rs rename src/api/{ => requests}/rate_limit.rs (68%) rename src/api/{ => requests}/upload_image.rs (50%) create mode 100644 src/api/send_api_request.rs diff --git a/Cargo.lock b/Cargo.lock index 557989e..8aa1e84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -864,6 +864,7 @@ dependencies = [ "serde_derive", "serde_json", "simple_logger", + "thiserror", "tokio", "toml", "validator", @@ -1852,9 +1853,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 76cbeda..5850464 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,28 +34,12 @@ validator = "0.15.0" colored = "2.0.0" clap_mangen = "0.1.6" discord-webhook = "0.1.0" +thiserror = "1.0.31" +clap = { version = "3.1.18", features = ["derive"] } +log = { version = "0.4.17", features = ["release_max_level_info", "max_level_debug"] } +simple_logger = { version = "2.1.0", default-features = false, features = ["colors"] } +reqwest = { version = "0.11.10", default-features = false, features = ["json", "rustls-tls"] } +tokio = { version = "1.19.2", features = ["macros", "rt-multi-thread"] } [target.'cfg(not(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten")))))'.dependencies] arboard = "2.1.1" - -[dependencies.clap] -version = "3.1.18" -features = ["derive", "cargo", "std"] - -[dependencies.log] -version = "0.4.17" -features = ["release_max_level_info", "max_level_debug"] - -[dependencies.simple_logger] -version = "2.1.0" -default-features = false -features = ["colors"] - -[dependencies.reqwest] -version = "0.11.10" -default-features = false -features = ["json", "rustls-tls"] - -[dependencies.tokio] -version = "1.19.2" -features = ["macros", "rt-multi-thread"] diff --git a/README.md b/README.md index d37a422..1e785bf 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ +[github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +[total-lines]: https://img.shields.io/tokei/lines/github/MedzikUser/HomeDisk?style=for-the-badge&logo=github&color=fede00 +[code-size]: https://img.shields.io/github/languages/code-size/MedzikUser/HomeDisk?style=for-the-badge&color=c8df52&logo=github +[crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +[docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +[ci]: https://img.shields.io/github/workflow/status/MedzikUser/rust-crypto-utils/Rust/main?style=for-the-badge&logo=github + # Imgurs - CLI and Library for Imgur API -![](https://img.shields.io/github/license/MedzikUser/imgurs) -![](https://img.shields.io/tokei/lines/github/MedzikUser/imgurs) -![](https://img.shields.io/github/languages/code-size/MedzikUser/imgurs) +[![github]](https://github.com/MedzikUser/imgurs) +[![total-lines]](https://github.com/MedzikUser/HomeDisk) +[![code-size]](https://github.com/MedzikUser/HomeDisk) +[![crates-io]](https://crates.io/crates/imgurs) +[![docs-rs]](https://docs.rs/imgurs) +[![ci]](https://github.com/MedzikUser/imgurs/actions/workflows/rust.yml) ## Screenshots diff --git a/src/api/client.rs b/src/api/client.rs index ba0bef1..050cd84 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -4,16 +4,20 @@ macro_rules! api_url ( ); ); -use std::{fmt, fs, io, path::Path}; - -use anyhow::Result; pub(crate) use api_url; + +use std::{fmt, fs, path::Path}; + use reqwest::Client; -use super::*; +use crate::{Result, Error, requests::{self, RateLimitInfo}, ImageInfo}; +/// Imgur Client +#[derive(Clone)] pub struct ImgurClient { + /// Client id for a Imgur API pub client_id: String, + /// HTTP Client pub client: Client, } @@ -24,7 +28,7 @@ impl fmt::Debug for ImgurClient { } impl ImgurClient { - /// Create new Imgur Client + /// Create a new Imgur Client /// ``` /// use imgurs::ImgurClient; /// @@ -51,21 +55,16 @@ impl ImgurClient { let mut image = path.to_string(); // check if the specified file exists if not then check if it is a url - if Path::new(&path).exists() { - let bytes = fs::read(&path)?; + if Path::new(path).exists() { + let bytes = fs::read(path)?; image = base64::encode(bytes) } - // validate adress url - else if !validator::validate_url(&*path) { - let err = io::Error::new( - io::ErrorKind::Other, - format!("{path} is not url or file path"), - ); - - return Err(err.into()); + // validate url adress + else if !validator::validate_url(path) { + Err(Error::InvalidUrlOrFile(path.to_string()))?; } - upload_image(self, image).await + requests::upload_image(self, image).await } /// Delete image from Imgur @@ -83,10 +82,10 @@ impl ImgurClient { /// } /// ``` pub async fn delete_image(&self, delete_hash: &str) -> Result<()> { - delete_image(self, delete_hash).await + requests::delete_image(self, delete_hash).await } - /// Client Rate Limit + /// Get Rame Limit of this Imgur Client /// ``` /// use imgurs::ImgurClient; /// @@ -98,10 +97,10 @@ impl ImgurClient { /// } /// ``` pub async fn rate_limit(&self) -> Result { - rate_limit(self).await + requests::rate_limit(self).await } - /// Get Imgur image info + /// Get image info from a Imgur /// ``` /// use imgurs::ImgurClient; /// @@ -113,6 +112,6 @@ impl ImgurClient { /// } /// ``` pub async fn image_info(&self, id: &str) -> Result { - get_image(self, id).await + requests::get_image(self, id).await } } diff --git a/src/api/error.rs b/src/api/error.rs new file mode 100644 index 0000000..ef66a96 --- /dev/null +++ b/src/api/error.rs @@ -0,0 +1,38 @@ +use thiserror::Error; + +/// Client Errors +#[derive(Debug, Error)] +pub enum Error { + /// Imgur API returned non-successful status code + #[error("server reponse non-successful status code - {0}, body = `{1}`")] + ApiError(u16, String), + /// Imgur API returned non-successful status code (body is too long) + #[error("server reponse non-successful status code - {0}, (response body is too long)")] + ApiErrorBodyTooLong(u16), + /// Invalid file path or URL adress + #[error("{0} is not url or file path")] + InvalidUrlOrFile(String), + /// Reqwest error + #[error("reqwest error - {0}")] + ReqwestError(reqwest::Error), + /// Io Error + #[error("io error - {0}")] + IoError(std::io::Error), +} + +/// reqwest::Error +impl From for Error { + fn from(err: reqwest::Error) -> Self { + Error::ReqwestError(err) + } +} + +/// reqwest::Error +impl From for Error { + fn from(err: std::io::Error) -> Self { + Error::IoError(err) + } +} + +/// A `Result` alias where the `Err` case is `imgurs::Error`. +pub type Result = std::result::Result; diff --git a/src/api/get_image.rs b/src/api/get_image.rs deleted file mode 100644 index 54aa9ea..0000000 --- a/src/api/get_image.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::io; - -use reqwest::Method; - -use super::{client::api_url, send_api_request, ImageInfo, ImgurClient}; - -pub async fn get_image(client: &ImgurClient, image: &str) -> anyhow::Result { - // get imgur api url - let uri = api_url!(format!("image/{image}")); - - // send request to imgur api - let res = send_api_request(client, Method::GET, uri, None).await?; - - // get response http code - let status = res.status(); - - // check if an error has occurred - if status.is_client_error() || status.is_server_error() { - let err = io::Error::new( - io::ErrorKind::Other, - format!("server returned non-successful status code = {status}"), - ); - - Err(err.into()) - } else { - let content: ImageInfo = res.json().await?; - Ok(content) - } -} diff --git a/src/api/image_type.rs b/src/api/image_type.rs index e3ea0f3..25860ea 100644 --- a/src/api/image_type.rs +++ b/src/api/image_type.rs @@ -1,5 +1,6 @@ use serde_derive::{Deserialize, Serialize}; +/// Image Info Response #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ImageInfo { pub data: ImageInfoData, @@ -7,6 +8,7 @@ pub struct ImageInfo { pub status: i32, } +/// Image Info Reponse (`data` json) #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ImageInfoData { pub id: String, diff --git a/src/api/mod.rs b/src/api/mod.rs index 8ed1826..f9d5c25 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,52 +1,12 @@ -mod delete_image; -mod get_image; -mod image_type; -mod rate_limit; -mod upload_image; +pub mod requests; -pub mod client; +mod client; +mod error; +mod image_type; +mod send_api_request; pub use client::ImgurClient; -pub use delete_image::*; -pub use get_image::*; +pub use error::*; pub use image_type::*; -pub use rate_limit::*; -pub use upload_image::*; - -use std::collections::HashMap; - -use reqwest::{Method, Response}; - -// send request to imgur api -pub async fn send_api_request( - config: &ImgurClient, - method: Method, - uri: String, - form: Option>, -) -> anyhow::Result { - // get request client - let client = &config.client; - - // create request buidler - let mut req = client.request(method, uri.as_str()); - - // get program version - let version: Option<&str> = option_env!("CARGO_PKG_VERSION"); - let version = version.unwrap_or("unknown"); - - // add `Authorization` and `User-Agent` to request - req = req - .header("Authorization", format!("Client-ID {}", config.client_id)) - .header("User-Agent", format!("Imgur/{:?}", version)); - - // if exists add hashmap to request - if form != None { - req = req.form(&form.unwrap()) - } - - // build request - let req = req.build()?; - - // send request - Ok(client.execute(req).await?) -} +pub use send_api_request::*; +pub(crate) use client::api_url; diff --git a/src/api/delete_image.rs b/src/api/requests/delete_image.rs similarity index 50% rename from src/api/delete_image.rs rename to src/api/requests/delete_image.rs index 4ef9b6c..2d71e8d 100644 --- a/src/api/delete_image.rs +++ b/src/api/requests/delete_image.rs @@ -1,10 +1,8 @@ -use std::io; - use reqwest::Method; -use super::{client::api_url, send_api_request, ImgurClient}; +use crate::{Error, Result, api_url, send_api_request, ImgurClient}; -pub async fn delete_image(client: &ImgurClient, delete_hash: &str) -> anyhow::Result<()> { +pub async fn delete_image(client: &ImgurClient, delete_hash: &str) -> Result<()> { // get imgur api url let uri = api_url!(format!("image/{delete_hash}")); @@ -16,18 +14,9 @@ pub async fn delete_image(client: &ImgurClient, delete_hash: &str) -> anyhow::Re // check if an error has occurred if status.is_client_error() || status.is_server_error() { - let mut body = res.text().await?; + let body = res.text().await?; - if body.chars().count() > 30 { - body = "body is too length".to_string() - } - - let err = io::Error::new( - io::ErrorKind::Other, - format!("server returned non-successful status code = {status}, body = {body}"), - ); - - return Err(err.into()); + Err(Error::ApiError(status.as_u16(), body))?; } Ok(()) diff --git a/src/api/requests/get_image.rs b/src/api/requests/get_image.rs new file mode 100644 index 0000000..c0c565e --- /dev/null +++ b/src/api/requests/get_image.rs @@ -0,0 +1,24 @@ +use reqwest::Method; + +use crate::{Error, Result, api_url, send_api_request, ImgurClient, ImageInfo}; + +pub async fn get_image(client: &ImgurClient, image: &str) -> Result { + // get imgur api url + let uri = api_url!(format!("image/{image}")); + + // send request to imgur api + let res = send_api_request(client, Method::GET, uri, None).await?; + + // get response http code + let status = res.status(); + + // check if an error has occurred + if status.is_client_error() || status.is_server_error() { + let body = res.text().await?; + + return Err(Error::ApiError(status.as_u16(), body)); + } + + // return `ImageInfo` + Ok(res.json().await?) +} diff --git a/src/api/requests/mod.rs b/src/api/requests/mod.rs new file mode 100644 index 0000000..2430981 --- /dev/null +++ b/src/api/requests/mod.rs @@ -0,0 +1,9 @@ +mod delete_image; +mod get_image; +mod rate_limit; +mod upload_image; + +pub use delete_image::*; +pub use get_image::*; +pub use rate_limit::*; +pub use upload_image::*; diff --git a/src/api/rate_limit.rs b/src/api/requests/rate_limit.rs similarity index 68% rename from src/api/rate_limit.rs rename to src/api/requests/rate_limit.rs index 9abba5d..ae546ed 100644 --- a/src/api/rate_limit.rs +++ b/src/api/requests/rate_limit.rs @@ -1,9 +1,7 @@ -use std::io; - use reqwest::Method; use serde::{Deserialize, Serialize}; -use super::{client::api_url, send_api_request, ImgurClient}; +use crate::{Error, Result, api_url, send_api_request, ImgurClient}; #[derive(Debug, Serialize, Deserialize)] pub struct RateLimitInfo { @@ -26,7 +24,7 @@ pub struct RateLimitData { pub client_remaining: i32, } -pub async fn rate_limit(client: &ImgurClient) -> anyhow::Result { +pub async fn rate_limit(client: &ImgurClient) -> Result { // get imgur api url let uri = api_url!("credits"); @@ -40,14 +38,9 @@ pub async fn rate_limit(client: &ImgurClient) -> anyhow::Result { if status.is_client_error() || status.is_server_error() { let body = res.text().await?; - let err = io::Error::new( - io::ErrorKind::Other, - format!("server returned non-successful status code = {status}, body = {body}"), - ); - - Err(err.into()) - } else { - let content = res.json::().await?; - Ok(content) + return Err(Error::ApiError(status.as_u16(), body)); } + + // return `RateLimitInfo` + Ok(res.json().await?) } diff --git a/src/api/upload_image.rs b/src/api/requests/upload_image.rs similarity index 50% rename from src/api/upload_image.rs rename to src/api/requests/upload_image.rs index df596b9..7baf605 100644 --- a/src/api/upload_image.rs +++ b/src/api/requests/upload_image.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, io}; +use std::collections::HashMap; use reqwest::Method; -use super::{client::api_url, send_api_request, ImageInfo, ImgurClient}; +use crate::{Error, Result, api_url, send_api_request, ImgurClient, ImageInfo}; -pub async fn upload_image(client: &ImgurClient, image: String) -> anyhow::Result { +pub async fn upload_image(client: &ImgurClient, image: String) -> Result { // create http form (hashmap) let mut form = HashMap::new(); // insert image to form @@ -21,20 +21,16 @@ pub async fn upload_image(client: &ImgurClient, image: String) -> anyhow::Result // check if an error has occurred if status.is_client_error() || status.is_server_error() { - let mut body = res.text().await?; + let body = res.text().await?; - if body.chars().count() > 200 { - body = "server returned too long".to_string() + // if body is too long do not return it (imgur sometimes returns whole Request) + if body.chars().count() > 50 { + Err(Error::ApiErrorBodyTooLong(status.as_u16()))?; } - let err = io::Error::new( - io::ErrorKind::Other, - format!("server returned non-successful status code = {status}, body = {body}"), - ); - - Err(err.into()) - } else { - let content: ImageInfo = res.json().await?; - Ok(content) + return Err(Error::ApiError(status.as_u16(), body)); } + + // return `ImageInfo` + Ok(res.json().await?) } diff --git a/src/api/send_api_request.rs b/src/api/send_api_request.rs new file mode 100644 index 0000000..fe44bc2 --- /dev/null +++ b/src/api/send_api_request.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; + +use reqwest::{Method, Response}; + +use crate::{ImgurClient, Result}; + +/// Send request to a Imgur API +pub async fn send_api_request( + config: &ImgurClient, + method: Method, + uri: String, + form: Option>, +) -> Result { + // get http client + let client = &config.client; + + // create Request buidler + let mut req = client.request(method, uri.as_str()); + + // get imgurs version + let version: &str = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"); + + // add `Authorization` and `User-Agent` to Request + req = req + .header("Authorization", format!("Client-ID {}", config.client_id)) + .header("User-Agent", format!("Imgur/{:?}", version)); + + // if exists add HashMap to Request + if form != None { + req = req.form(&form.unwrap()) + } + + // build Request + let req = req.build()?; + + // send Request + Ok(client.execute(req).await?) +} diff --git a/src/cli/parse.rs b/src/cli/parse.rs index 97f5071..45695c7 100644 --- a/src/cli/parse.rs +++ b/src/cli/parse.rs @@ -5,13 +5,12 @@ use std::io::{self, stdout}; use crate::cli::{credits::*, delete_image::*, info_image::*, upload_image::*}; -// get program name and varsion from Cargo.toml +// get version from Cargo.toml const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); -const NAME: Option<&str> = option_env!("CARGO_PKG_NAME"); #[derive(Parser, Debug)] #[clap( - name = NAME.unwrap_or("unknown"), + name = "imgurs", about = "Imgur API CLI", long_about = None, version = VERSION.unwrap_or("unknown") )] @@ -48,6 +47,7 @@ fn print_completions(gen: G, app: &mut Command) { generate(gen, app, app.get_name().to_string(), &mut stdout()) } +#[tokio::main] pub async fn parse(client: ImgurClient) { let args = Cli::parse(); diff --git a/src/lib.rs b/src/lib.rs index 45ebf6d..c6f95c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,11 @@ +//! [![github]](https://github.com/MedzikUser/imgurs) +//! [![crates-io]](https://crates.io/crates/imgurs) +//! [![docs-rs]](https://docs.rs/imgurs) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! //! This crate is an unofficial implementation of the [Imgur API](https://imgur.com) in Rust. //! //! # Installation @@ -17,7 +25,7 @@ //! # Example Usage //! //! ## Create new ImgurClient -//! ```ignore +//! ``` //! use imgurs::ImgurClient; //! //! let client = ImgurClient::new("client id"); @@ -34,7 +42,7 @@ //! //! ## Delete Image //! ```ignore -//! client.delete_image("SuPeRsEcReTDeLeTeHaSh").await?; // delete hash +//! client.delete_image("Delete Hash").await?; // delete hash //! ``` //! //! ## Get Image Info diff --git a/src/main.rs b/src/main.rs index 9ca73ae..a713aa8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,7 @@ use simple_logger::SimpleLogger; mod cli; mod config; -#[tokio::main] -async fn main() { +fn main() { SimpleLogger::new().init().expect("init SimpleLogger"); better_panic::install(); @@ -15,5 +14,5 @@ async fn main() { // create imgur client let client = ImgurClient::new(&config.imgur.id); - cli::parse(client).await + cli::parse(client) }