From f8eb4674973096c013171c7ad5ab7dec14d930da Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Thu, 31 Oct 2024 16:55:28 +0530 Subject: [PATCH 1/2] Add Default directly & remove new() fn --- src/lib.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f500a20..da3845b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,18 +7,7 @@ pub use todos::*; pub const API_BASE_URL: &str = "https://dummyjson.com"; +#[derive(Default)] pub struct DummyJsonClient { pub client: Client, } - -impl DummyJsonClient { - pub fn new() -> Self { - Self { client: Client::new() } - } -} - -impl Default for DummyJsonClient { - fn default() -> Self { - Self::new() - } -} From 3cfedcfa0582a6f779b9a1c3e7f6faf16694a38a Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Thu, 31 Oct 2024 18:51:04 +0530 Subject: [PATCH 2/2] Add products module & its tests, code improve. via static -> const - Using const is a compile-time constant & lighter than static type. So, using this. --- api-requests/products.http | 82 ++++++++++++++++ src/auth.rs | 4 +- src/lib.rs | 4 +- src/products.rs | 196 +++++++++++++++++++++++++++++++++++++ src/todos.rs | 4 +- tests/products.rs | 101 +++++++++++++++++++ 6 files changed, 386 insertions(+), 5 deletions(-) create mode 100644 api-requests/products.http create mode 100644 src/products.rs create mode 100644 tests/products.rs diff --git a/api-requests/products.http b/api-requests/products.http new file mode 100644 index 0000000..06ca0c3 --- /dev/null +++ b/api-requests/products.http @@ -0,0 +1,82 @@ +@host=https://dummyjson.com/products + +### +# @name GetAllProducts +GET {{host}} +Accept: application/json +Content-Type: application/json + +### +# @name GetProductById +GET {{host}}/1 +Accept: application/json +Content-Type: application/json + +### +# @name SearchProducts +GET {{host}}/search?q=phone +Accept: application/json +Content-Type: application/json + +### +# @name LimitAndSkipProducts +GET {{host}}?limit=1&skip=10&select=title,price +Accept: application/json +Content-Type: application/json + +### +# @name SortProducts +GET {{host}}?sortBy=title&order=asc +Accept: application/json +Content-Type: application/json + +### +# @name ProductCategories +GET {{host}}/categories +Accept: application/json +Content-Type: application/json + +### +# @name ProductCategoriesList +GET {{host}}/category-list +Accept: application/json +Content-Type: application/json + +### +# @name GetProductsByCategory +GET {{host}}/category/smartphones +Accept: application/json +Content-Type: application/json + +### +# @name AddProduct +POST {{host}}/add +Accept: application/json +Content-Type: application/json + +{ + "title": "iPhone 9", + "description": "An apple mobile which is nothing like apple", + "price": 549, + "discountPercentage": 12.96, + "rating": 4.69, + "stock": 94, + "brand": "Apple", + "category": "smartphones" +} + +### +# @name UpdateProduct +PUT {{host}}/1 +Accept: application/json +Content-Type: application/json + +{ + "title": "iPhone 18" +} + +### +# @name DeleteProduct +DELETE {{host}}/1 +Accept: application/json +Content-Type: application/json diff --git a/src/auth.rs b/src/auth.rs index 8e64be7..311d22d 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -9,7 +9,7 @@ use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_json::json; -static AUTH_BASE_URL: Lazy = Lazy::new(|| format!("{}/auth", API_BASE_URL)); +const AUTH_BASE_URL: Lazy = Lazy::new(|| format!("{}/auth", API_BASE_URL)); /// Login request payload #[derive(Serialize)] @@ -66,7 +66,7 @@ pub struct User { #[serde(rename = "eyeColor")] pub eye_color: String, pub hair: Hair, - // Other fields + // TODO: Other fields } #[derive(Deserialize, Debug)] diff --git a/src/lib.rs b/src/lib.rs index da3845b..74ec347 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ mod auth; +mod products; mod todos; pub use auth::*; +pub use products::*; use reqwest::Client; pub use todos::*; -pub const API_BASE_URL: &str = "https://dummyjson.com"; +const API_BASE_URL: &str = "https://dummyjson.com"; #[derive(Default)] pub struct DummyJsonClient { diff --git a/src/products.rs b/src/products.rs new file mode 100644 index 0000000..2b126a2 --- /dev/null +++ b/src/products.rs @@ -0,0 +1,196 @@ +use crate::{DummyJsonClient, API_BASE_URL}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +const PRODUCTS_BASE_URL: Lazy = Lazy::new(|| format!("{}/products", API_BASE_URL)); + +#[derive(Deserialize, Debug)] +pub struct Product { + pub id: u32, + #[serde(flatten)] + pub other_fields: AddProduct, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct AddProduct { + pub title: String, + pub description: Option, + pub price: Option, + #[serde(rename = "discountPercentage")] + pub discount_percentage: Option, + pub rating: Option, + pub stock: Option, + pub tags: Option>, + // FIXME: Not sure, why the actual API response missing 'brand' field + // resulting in error. + // pub brand: Option, + pub sku: Option, + pub weight: Option, + pub dimensions: Option, + #[serde(rename = "warrantyInformation")] + pub warranty_info: Option, + #[serde(rename = "shippingInformation")] + pub shipping_info: Option, + #[serde(rename = "availabilityStatus")] + pub availability_status: Option, + pub reviews: Option>, + #[serde(rename = "returnPolicy")] + pub return_policy: Option, + #[serde(rename = "minimumOrderQuantity")] + pub min_order_qty: Option, + pub meta: Option, + pub images: Option>, + pub thumbnail: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Dimension { + pub width: f32, + pub height: f32, + pub depth: f32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Review { + pub rating: u8, + pub comment: String, + pub date: String, + #[serde(rename = "reviewerName")] + pub reviewer_name: String, + #[serde(rename = "reviewerEmail")] + pub reviewer_email: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Meta { + #[serde(rename = "createdAt")] + pub created_at: String, + #[serde(rename = "updatedAt")] + pub updated_at: String, + pub barcode: String, + #[serde(rename = "qrCode")] + pub qr_code: String, +} + +#[derive(Deserialize, Debug)] +pub struct GetAllProductsResponse { + pub products: Vec, + pub total: u32, + pub skip: u32, + pub limit: u32, +} + +#[derive(Deserialize, Debug)] +pub struct ProductCategory { + pub slug: String, + pub name: String, + pub url: String, +} + +#[derive(Deserialize, Debug)] +pub struct DeleteProductResponse { + #[serde(flatten)] + pub other_fields: Product, + #[serde(rename = "isDeleted")] + pub is_deleted: bool, + #[serde(rename = "deletedOn")] + pub deleted_on: String, +} + +impl DummyJsonClient { + /// Get all products + pub async fn get_all_products(&self) -> Result { + let response = self.client.get(&*PRODUCTS_BASE_URL).send().await?; + response.json::().await + } + + /// Get product by id + pub async fn get_product_by_id(&self, id: u32) -> Result { + let url = &format!("{}/{}", &*PRODUCTS_BASE_URL, id); + let response = self.client.get(url).send().await?; + response.json::().await + } + + /// Search products + pub async fn search_products( + &self, + query: &str, + ) -> Result { + let url = &format!("{}/search?q={}", &*PRODUCTS_BASE_URL, query); + let response = self.client.get(url).send().await?; + response.json::().await + } + + /// Limit and skip products + pub async fn limit_and_skip_products( + &self, + limit: u32, + skip: u32, + selects: &str, + ) -> Result { + let url = + &format!("{}/?limit={}&skip={}&select={}", &*PRODUCTS_BASE_URL, limit, skip, selects); + let response = self.client.get(url).send().await?; + response.json::().await + } + + /// Sort products by field + pub async fn sort_products_by( + &self, + field: &str, + order: &str, + ) -> Result { + let url = &format!("{}/?sortBy={}&order={}", &*PRODUCTS_BASE_URL, field, order); + let response = self.client.get(url).send().await?; + response.json::().await + } + + /// Get product categories + pub async fn get_product_categories(&self) -> Result, reqwest::Error> { + let url = &format!("{}/categories", &*PRODUCTS_BASE_URL); + let response = self.client.get(url).send().await?; + response.json::>().await + } + + /// Get product categories list + pub async fn get_product_categories_list(&self) -> Result, reqwest::Error> { + let url = &format!("{}/category-list", &*PRODUCTS_BASE_URL); + let response = self.client.get(url).send().await?; + response.json::>().await + } + + /// Get products by category + pub async fn get_products_by_category( + &self, + category: &str, + ) -> Result { + let url = &format!("{}/category/{}", &*PRODUCTS_BASE_URL, category); + let response = self.client.get(url).send().await?; + response.json::().await + } + + /// Add product + pub async fn add_product(&self, product: &AddProduct) -> Result { + let url = &format!("{}/add", &*PRODUCTS_BASE_URL); + let response = self.client.post(url).json(product).send().await?; + response.json::().await + } + + /// Update product + pub async fn update_product( + &self, + id: u32, + product: &AddProduct, + ) -> Result { + let url = &format!("{}/{}", &*PRODUCTS_BASE_URL, id); + let response = self.client.put(url).json(product).send().await?; + response.json::().await + } + + /// Delete product + pub async fn delete_product(&self, id: u32) -> Result { + let url = &format!("{}/{}", &*PRODUCTS_BASE_URL, id); + let response = self.client.delete(url).send().await?; + response.json::().await + } +} diff --git a/src/todos.rs b/src/todos.rs index fad480f..b29935f 100644 --- a/src/todos.rs +++ b/src/todos.rs @@ -7,9 +7,9 @@ use reqwest::Error; use serde::Deserialize; use serde_json::json; -static TODOS_BASE_URL: Lazy = Lazy::new(|| format!("{}/todos", API_BASE_URL)); +const TODOS_BASE_URL: Lazy = Lazy::new(|| format!("{}/todos", API_BASE_URL)); -/// Todo +/// Todo item #[derive(Deserialize, Debug)] pub struct Todo { pub id: u32, diff --git a/tests/products.rs b/tests/products.rs new file mode 100644 index 0000000..12dfb29 --- /dev/null +++ b/tests/products.rs @@ -0,0 +1,101 @@ +#[cfg(test)] +mod products { + use dummy_json_rs::{AddProduct, DummyJsonClient}; + + #[tokio::test] + async fn get_all_products() { + let client = DummyJsonClient::default(); + let response = client.get_all_products().await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_product_by_id() { + let client = DummyJsonClient::default(); + let response = client.get_product_by_id(1).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn search_products() { + let client = DummyJsonClient::default(); + let response = client.search_products("phone").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn limit_and_skip_products() { + let client = DummyJsonClient::default(); + let response = client.limit_and_skip_products(1, 10, "title,price").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn sort_products() { + let client = DummyJsonClient::default(); + let response = client.sort_products_by("title", "asc").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_product_categories() { + let client = DummyJsonClient::default(); + let response = client.get_product_categories().await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_product_categories_list() { + let client = DummyJsonClient::default(); + let response = client.get_product_categories_list().await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn get_products_by_category() { + let client = DummyJsonClient::default(); + let response = client.get_products_by_category("smartphones").await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn add_product() { + let client = DummyJsonClient::default(); + let response = client + .add_product(&AddProduct { + title: "iPhone 18".to_string(), + description: Some("New phone released in 2024".to_string()), + price: Some(1200.0), + ..Default::default() + }) + .await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn update_product() { + let client = DummyJsonClient::default(); + let response = client + .update_product(1, &AddProduct { title: "iPhone 19".to_string(), ..Default::default() }) + .await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } + + #[tokio::test] + async fn delete_product() { + let client = DummyJsonClient::default(); + let response = client.delete_product(1).await; + assert!(response.is_ok()); + println!("{:#?}", response.unwrap()); + } +}