From c932042f58af37c9e22fdf140e3142519503cfba Mon Sep 17 00:00:00 2001 From: Pmarquez <48651252+pxp9@users.noreply.github.com> Date: Sun, 9 Jul 2023 20:26:55 +0200 Subject: [PATCH] Develop (#26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Pxp9/ft filters url (#14) * tenemos las tallas gracias a la cortesía de ChatGPT beautiful soup y una buena feature del navegador xd, subimos el JSON donde están todas las categorías (falta parsear) y empezamos con las marcas * script de python que te saca todos los nombres de marcas de la pagina brands/byLetter/Letra * nombres de marcas y tallas extraidas * script que hace requests a la api y obtiene el ID de la marca * Got brands_ids :D * formatting catalogs ids * organizando los recursos de los filtros * materiales * arreglillos de ultima hora * arreglillos de ultima hora * Added scrappers as submodule * Refactor completed * Fmt formatted * actualizando el submodulo del scrapper * primera migration sizes complete * get_catalogs , alvaro y README * Finished catalogs * Fix README * Fix both Readme * Fix dependency for Diesel * Created more migrations * Brands needs a fix * fix migrations and brands * migracion de countries y query de la BBDD de una marca * fix clippy * arreglando un bug con las cookies, haciendo un test y limitando get_item a 1 elemento * fix fmt * mejorando la API del wrapper , implementando FROM de Brand * fix fmt * fix clippy on test * review API and model types * fix fmt * Db tests * fix workflow * empezando a procesar los filtros en la URI * Finished db-feeder * Added some tests * Added query * Added optional num field in get_items * Added Get_category_by_name tested * Converted vec to String Co-authored-by: Pmarquez * Finished filters * Fix doctest * Added get-host * Cargo clippy fix * Small changes big steps * Fix languages * fuck cargo fmt --------- Co-authored-by: alvarocabo Co-authored-by: Pmarquez * Fix cargo lock * countries , price from and price to filter done (#25) * countries , price from and price to filter done * fix typo docs * Improved README * Fixing doc tests * Small fix * Added search by country * Como no el fmt * Added material and size filters * Removed debug element * Attemp of testing item by color * Ignoring the test for now * Fix clippy * Ignore test for now * Removed main.rs * Updated .gitignore * tests de filtros * fix clippy and fmt --------- Co-authored-by: alvarocabo --------- Co-authored-by: alvarocabo Co-authored-by: Pmarquez --- .github/workflows/rust.yml | 2 +- .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 28 +++---- src/db.rs | 17 +++- src/main.rs | 34 -------- src/model/filter.rs | 61 ++++++++++++++- src/model/filter/category.rs | 12 +-- src/model/filter/category_tree.rs | 6 +- src/model/filter/colors.rs | 7 +- src/model/filter/country.rs | 12 +-- src/model/filter/material.rs | 8 +- src/model/photo.rs | 8 +- src/queries.rs | 53 ++++++++++++- src/tests/db.rs | 19 ++++- src/tests/queries.rs | 124 +++++++++++++++++++++++++++++- 17 files changed, 308 insertions(+), 88 deletions(-) delete mode 100644 src/main.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7d36743..259ee28 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -69,7 +69,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --verbose --all-features -- --ignored + args: --verbose --all-features -- --ignored --nocapture release: name: Release x86_64-unknown-linux-gnu diff --git a/.gitignore b/.gitignore index ea8c4bf..246a668 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +**/main.rs \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index cbce8aa..16bd352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1504,7 +1504,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vinted-rs" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bb8-postgres", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index be3a722..971f5db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vinted-rs" -version = "0.1.0" +version = "0.2.0" edition = "2021" repository = "https://github.com/TuTarea/vinted-rs" authors = ["Pepe Márquez " , "Álvaro Cabo "] diff --git a/README.md b/README.md index ceacb47..d253dac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,17 @@ [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 -A complete Vinted API-Wrapper in Rust +## Table of Contents + +- [Vinted-rs: A Vinted API wrapper](#vinted-rs-a-vinted-api-wrapper) + - [Table of Contents](#table-of-contents) + - [Installation](#installation) + - [DB setup](#db-setup) + - [Create a migration](#create-a-migration) + - [Run a Docker container with PostgreSQL](#run-a-docker-container-with-postgresql) + - [Run migrations](#run-migrations) + - [Stop DB](#stop-db) + - [Running Tests](#running-tests) ## Installation @@ -14,24 +24,16 @@ Via `cargo` you can add the library to your project's `Cargo.toml` ```toml [dependencies] -vinted-rs = "0.0.1" +vinted-rs = "0.2" ``` -## Authors - -[Álvaro Cabo](https://github.com/alvarocabo) - -[Pepe Márquez](https://github.com/pxp9) - ## DB setup Advanced filtering features must require this setup before running. - First start installing diesel-cli (in order to run the migrations in PostgreSQL database) -### VERY IMPORTANT - -diesel-cli installation may fail if you do not have `libpq` library installed. +⚠️**Very important:** diesel-cli installation may fail if you do not have `libpq` library installed. To install `libpq`, just install PostgreSQL package on your machine. @@ -85,9 +87,7 @@ make stop ## Running Tests -### Very important - -Before running tests is important to do the DB setup +⚠️**Very important:** Before running tests is important to do the [DB setup](#db-setup) Then run the tests diff --git a/src/db.rs b/src/db.rs index 3447fbd..aea607b 100644 --- a/src/db.rs +++ b/src/db.rs @@ -22,11 +22,12 @@ use bb8_postgres::{ use postgres_types::ToSql; use thiserror::Error; -use crate::model::filter::{brand::Brand, category::Category}; +use crate::model::filter::{brand::Brand, category::Category, country::Country}; const GET_BRAND_BY_NAME: &str = include_str!("sql_queries/GET_BRAND_BY_NAME.sql"); const GET_BRANDS_BY_NAME: &str = include_str!("sql_queries/GET_BRANDS_BY_NAME.sql"); const GET_CATEGORY_BY_NAME: &str = include_str!("sql_queries/GET_CATEGORY_BY_NAME.sql"); +const GET_COUNTRY_BY_ISO_CODE: &str = include_str!("sql_queries/GET_COUNTRY_BY_ISO_CODE.sql"); /** Represents an error that can occur during database operations. @@ -122,4 +123,18 @@ where Ok(cat) } + + /// Retrieves a category by its title from the database + pub async fn get_country_by_iso + Sync + ToSql + Display>( + &self, + code: &S, + ) -> Result { + let conn = self.pool.get().await?; + let row: Row = conn.query_one(GET_COUNTRY_BY_ISO_CODE, &[&code]).await?; + + // Works because From for Category is implemented + let country: Country = row.into(); + + Ok(country) + } } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 07d584b..0000000 --- a/src/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -use bb8_postgres::tokio_postgres::NoTls; -use vinted_rs::{ - db::DbController, - model::{filter::brand::Brand, filter::Filter}, -}; - -use vinted_rs::VintedWrapper; - -const DB_URL: &str = "postgres://postgres:postgres@localhost/vinted-rs"; -const POOL_SIZE: u32 = 5; - -#[tokio::main] -async fn main() { - let vinted = VintedWrapper::new(); - - let filter: Filter = Filter::builder().search_text(String::from("shoes")).build(); - - let items = vinted.get_items(&filter, 5).await.unwrap(); - - print!("{items:?}"); - - let db: DbController = DbController::new(DB_URL, POOL_SIZE, NoTls).await.unwrap(); - - let brand_name: String = String::from("adidas"); - //let brand_name : &str = "adidas"; - - let b: Brand = db.get_brand_by_name(&brand_name).await.unwrap(); - - let brands = db.get_brands_by_name(&brand_name).await.unwrap(); - - println!("\n\n\n\nBrand {b:?}\n\n\n\n"); - - println!("Brands: {brands:?}"); -} diff --git a/src/model/filter.rs b/src/model/filter.rs index 32b36a0..08b540a 100644 --- a/src/model/filter.rs +++ b/src/model/filter.rs @@ -9,7 +9,7 @@ pub mod material; pub mod size; /// Represents a filter for querying items. - +/// /// Trait Implementations: /// - `TypedBuilder`: Implements the builder pattern for constructing a `Filter` instance. /// @@ -19,6 +19,7 @@ pub mod size; ///### Example /// ///```rust +/// use vinted_rs::Filter; /// /// let filter: Filter = Filter::builder() /// .catalog_ids(String::from("4,16")) @@ -30,11 +31,16 @@ pub mod size; /// ///``` /// +/// +/// `price_from` filter should be always <= `price_to` , otherwise Vinted will not find anything +/// #[derive(TypedBuilder, Debug, Clone)] pub struct Filter { ///The search text to filter items by. ///### Example ///```rust + /// use vinted_rs::Filter; + /// ///let filter: Filter = Filter::builder().search_text(String::from("shoes")).build(); ///``` /// @@ -73,6 +79,8 @@ pub struct Filter { /// ///Try it in [Regex 101](https://regex101.com/r/u8ZEpv/1) /// + /// + /// **Note:** Color names are only avalible in French in our database at the moment ///### Example ///```rust /// use vinted_rs::Filter; @@ -117,7 +125,7 @@ pub struct Filter { /// use vinted_rs::Filter; /// /// - /// let filter: Filter = Filter::builder().country_ids(String::from("7,16")).build(); + /// let filter: Filter = Filter::builder().countries_ids(String::from("7,16")).build(); /// // Where 7 and 16 are country_ids from Vinted /// // 7 is country_id for Spain /// // 16 is country_id for France @@ -144,6 +152,26 @@ pub struct Filter { ///``` /// #[builder(default, setter(strip_option))] + pub material_ids: Option, + /// The material IDs to filter items by. Must be formatted as `^[\d+,]*\d+$` regex. + /// + ///If not formated with the specified regex, undefined behaviour. (Input will not be checked). + /// + /// + ///Try it in [Regex 101](https://regex101.com/r/u8ZEpv/1) + /// + ///### Example + ///```rust + /// use vinted_rs::Filter; + /// + /// + /// let filter: Filter = Filter::builder().material_ids(String::from("44,102")).build(); + /// // Where 7 and 16 are country_ids from Vinted + /// // 44 is material_id for coton + /// // 49 is material_id for silk + ///``` + /// + #[builder(default, setter(strip_option))] pub size_ids: Option, #[builder(default, setter(strip_option))] /// The article statuses to filter items by. @@ -151,7 +179,7 @@ pub struct Filter { ///### Example ///```rust /// use vinted_rs::Filter; - /// + /// use vinted_rs::model::filter::ArticleStatus; /// /// let filter: Filter = Filter::builder().article_status(vec![ArticleStatus::NewTags , /// ArticleStatus::NewNoTags]).build(); @@ -163,13 +191,38 @@ pub struct Filter { ///### Example ///```rust /// use vinted_rs::Filter; - /// + /// use vinted_rs::model::filter::SortBy; + /// /// let filter: Filter = Filter::builder().sort_by(SortBy::PriceAscendant).build(); ///``` /// #[builder(default, setter(strip_option))] pub sort_by: Option, + /// The minimum price of the article + /// + ///### Example + ///```rust + /// use vinted_rs::Filter; + /// + /// + /// let filter: Filter = Filter::builder().price_from(10u32).build(); + ///``` + /// + #[builder(default, setter(strip_option))] + pub price_from: Option, + /// The max price of the article + /// + ///### Example + ///```rust + /// use vinted_rs::Filter; + /// + /// + /// let filter: Filter = Filter::builder().price_from(20u32).build(); + ///``` + /// + #[builder(default, setter(strip_option))] + pub price_to: Option, } /* diff --git a/src/model/filter/category.rs b/src/model/filter/category.rs index 35847f7..eff10fb 100644 --- a/src/model/filter/category.rs +++ b/src/model/filter/category.rs @@ -4,13 +4,13 @@ use typed_builder::TypedBuilder; #[derive(Debug, Clone, TypedBuilder, PartialEq, Eq)] pub struct Category { - id: i32, + pub id: i32, // TODO creo que estos titulos solo estan en ingles - title: String, - code: String, - parent_id: i32, - url: String, - url_en: String, + pub title: String, + pub code: String, + pub parent_id: i32, + pub url: String, + pub url_en: String, } #[cfg(feature = "advanced_filters")] diff --git a/src/model/filter/category_tree.rs b/src/model/filter/category_tree.rs index da7de6c..c09c3d2 100644 --- a/src/model/filter/category_tree.rs +++ b/src/model/filter/category_tree.rs @@ -4,9 +4,9 @@ use typed_builder::TypedBuilder; #[derive(Debug, Clone, TypedBuilder, PartialEq, Eq)] pub struct CategoryTree { - id: i32, - parent_id: i32, - child_id: i32, + pub id: i32, + pub parent_id: i32, + pub child_id: i32, } #[cfg(feature = "advanced_filters")] diff --git a/src/model/filter/colors.rs b/src/model/filter/colors.rs index 732dd7d..ba48698 100644 --- a/src/model/filter/colors.rs +++ b/src/model/filter/colors.rs @@ -4,10 +4,9 @@ use typed_builder::TypedBuilder; #[derive(Debug, Clone, TypedBuilder, PartialEq, Eq)] pub struct Color { - id: i32, - // TODO el titulo solo está en francés valorar que podemos hacer - title: String, - hex: String, + pub id: i32, + pub title: String, + pub hex: String, } #[cfg(feature = "advanced_filters")] diff --git a/src/model/filter/country.rs b/src/model/filter/country.rs index c7a4c2e..e0f364e 100644 --- a/src/model/filter/country.rs +++ b/src/model/filter/country.rs @@ -3,11 +3,12 @@ use bb8_postgres::tokio_postgres::Row; use typed_builder::TypedBuilder; #[derive(Debug, Clone, TypedBuilder, PartialEq, Eq)] -struct Country { - id: i32, - local_name: String, - iso_code: String, - flag: String, +pub struct Country { + pub id: i32, + pub name: String, + pub local_name: String, + pub iso_code: String, + pub flag: String, } #[cfg(feature = "advanced_filters")] @@ -15,6 +16,7 @@ impl From for Country { fn from(row: Row) -> Self { Country::builder() .id(row.get("id")) + .name(row.get("name")) .local_name(row.get("local_name")) .iso_code(row.get("iso_code")) .flag(row.get("flag")) diff --git a/src/model/filter/material.rs b/src/model/filter/material.rs index 699aa8e..a5788fe 100644 --- a/src/model/filter/material.rs +++ b/src/model/filter/material.rs @@ -4,10 +4,10 @@ use typed_builder::TypedBuilder; #[derive(Debug, Clone, TypedBuilder, PartialEq, Eq)] pub struct Material { - id: i32, - material_es: String, - material_fr: String, - material_en: String, + pub id: i32, + pub material_es: String, + pub material_fr: String, + pub material_en: String, } #[cfg(feature = "advanced_filters")] diff --git a/src/model/photo.rs b/src/model/photo.rs index 33b7fd7..a912daf 100644 --- a/src/model/photo.rs +++ b/src/model/photo.rs @@ -2,8 +2,8 @@ use crate::model::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Photo { - id: i64, - url: String, - dominant_color: String, - dominant_color_opaque: String, + pub id: i64, + pub url: String, + pub dominant_color: String, + pub dominant_color_opaque: String, } diff --git a/src/queries.rs b/src/queries.rs index dea1fce..498c826 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -357,13 +357,13 @@ impl<'a> VintedWrapper<'a> { let mut url = format!("https://www.vinted.{}/api/v2/catalog/items", self.host); - // Filtro search text + // Filter search text if let Some(text) = &filters.search_text { url = format!("{url}?search_text={text}"); first = false; } - // Filtro catalogo + // Filter catalogo if let Some(catalog_ids) = &filters.catalog_ids { let mut catalog_args: String = format!("&catalog_ids={}", catalog_ids); @@ -372,7 +372,7 @@ impl<'a> VintedWrapper<'a> { url = format!("{url}{catalog_args}"); } - // Filtro colores + // Filter colors if let Some(color_ids) = &filters.color_ids { let mut color_args: String = format!("&color_ids={}", color_ids); @@ -381,6 +381,7 @@ impl<'a> VintedWrapper<'a> { url = format!("{url}{color_args}"); } + //Filter brand if let Some(brand_ids) = &filters.brand_ids { let mut brand_args: String = format!("&brand_ids={}", brand_ids); @@ -389,6 +390,51 @@ impl<'a> VintedWrapper<'a> { url = format!("{url}{brand_args}"); } + //Filter sizes + if let Some(size_ids) = &filters.size_ids { + let mut size_args: String = format!("&size_ids={}", size_ids); + + VintedWrapper::substitute_if_first(&mut first, &mut size_args); + + url = format!("{url}{size_args}"); + } + + //Filter materials + if let Some(material_ids) = &filters.material_ids { + let mut material_args: String = format!("&material_ids={}", material_ids); + + VintedWrapper::substitute_if_first(&mut first, &mut material_args); + + url = format!("{url}{material_args}"); + } + //Filter country + if let Some(countries_ids) = &filters.countries_ids { + let mut countries_args: String = format!("&country_ids={}", countries_ids); + + VintedWrapper::substitute_if_first(&mut first, &mut countries_args); + + url = format!("{url}{countries_args}"); + } + + //Filter price from + if let Some(price_from) = &filters.price_from { + let mut price_from_arg: String = format!("&price_from={}", price_from); + + VintedWrapper::substitute_if_first(&mut first, &mut price_from_arg); + + url = format!("{url}{price_from_arg}"); + } + + //Filter price to + if let Some(price_to) = &filters.price_to { + let mut price_to_arg: String = format!("&price_to={}", price_to); + + VintedWrapper::substitute_if_first(&mut first, &mut price_to_arg); + + url = format!("{url}{price_to_arg}"); + } + + // Filter article_status if let Some(vec) = &filters.article_status { let querify_vec: Vec<&str> = vec.iter().map(|status| status.into()).collect(); @@ -399,6 +445,7 @@ impl<'a> VintedWrapper<'a> { url = format!("{url}{article_status_args}"); } + //Order by if let Some(sort_by) = &filters.sort_by { let sort_by_str: &str = sort_by.into(); diff --git a/src/tests/db.rs b/src/tests/db.rs index a5beebc..d453550 100644 --- a/src/tests/db.rs +++ b/src/tests/db.rs @@ -1,6 +1,6 @@ use crate::{ db::DbController, - model::filter::{brand::Brand, category::Category}, + model::filter::{brand::Brand, category::Category, country::Country}, }; use bb8_postgres::tokio_postgres::NoTls; @@ -56,3 +56,20 @@ async fn test_get_category_by_name() { .build() ); } + +#[tokio::test] +async fn test_get_country_by_iso() { + let db: DbController = DbController::new(DB_URL, POOL_SIZE, NoTls).await.unwrap(); + let c = db.get_country_by_iso(&String::from("ES")).await.unwrap(); + + assert_eq!( + c, + Country::builder() + .id(7) + .name(String::from("Espagne")) + .local_name(String::from("España")) + .iso_code(String::from("ES")) + .flag(String::from("🇪🇸")) + .build() + ); +} diff --git a/src/tests/queries.rs b/src/tests/queries.rs index 8cf390c..94e2cb8 100644 --- a/src/tests/queries.rs +++ b/src/tests/queries.rs @@ -7,6 +7,26 @@ use bb8_postgres::tokio_postgres::NoTls; const DB_URL: &str = "postgres://postgres:postgres@localhost/vinted-rs"; const POOL_SIZE: u32 = 5; +fn _calculate_color_props(hex_color1: &str) -> (f64, f64, f64) { + let color1 = _hex_to_rgb(hex_color1); + + let r_prop = color1.0 as f64 / 255.0; + let g_prop = color1.1 as f64 / 255.0; + let b_prop = color1.2 as f64 / 255.0; + + (r_prop, g_prop, b_prop) +} + +fn _hex_to_rgb(hex_color: &str) -> (u8, u8, u8) { + let hex = hex_color.trim_start_matches('#'); + + let r = u8::from_str_radix(&hex[0..2], 16).unwrap(); + let g = u8::from_str_radix(&hex[2..4], 16).unwrap(); + let b = u8::from_str_radix(&hex[4..6], 16).unwrap(); + + (r, g, b) +} + #[tokio::test] async fn test_get_item_query_text() { let vinted = VintedWrapper::new(); @@ -67,18 +87,118 @@ async fn test_get_items_brands() { } #[tokio::test] +#[ignore] async fn test_get_items_catalogs_no_db() { let vinted = VintedWrapper::new(); //Woman elements let filter: Filter = Filter::builder().catalog_ids(String::from("1904")).build(); - let _substrings = vec![ + let substrings = vec![ "women", "mujer", "femme", "kobiety", "donna", "moterims", "noi", "dames", "zeny", "damen", - "femei", "mulher", + "femei", "mulher", "beauty", "femmes", "dam", ]; match vinted.get_items(&filter, 10).await { Ok(items) => { assert_eq!(items.items.len(), 10); + items.items.iter().for_each(|item| { + let url_item: &str = &item.url; + let category = url_item.split('/').nth(3).unwrap(); + println!("{:?}", category); + assert!( + substrings.contains(&category), + "Category not found {}", + category + ); + }); + } + Err(err) => match err { + VintedWrapperError::ItemNumberError => unreachable!(), + VintedWrapperError::CookiesError(_) => (), + }, + }; +} + +#[tokio::test] +async fn test_get_items_by_price() { + let vinted = VintedWrapper::new(); + let min = 50; + let max = 100; + + let filter: Filter = Filter::builder().price_from(min).price_to(max).build(); + + match vinted.get_items(&filter, 10).await { + Ok(items) => { + assert_eq!(items.items.len(), 10); + let ok: bool = items.items.iter().all(|item| { + let price: f32 = item.price.parse().unwrap(); + price <= max as f32 && price >= min as f32 + }); + + assert!(ok); + } + Err(err) => match err { + VintedWrapperError::ItemNumberError => unreachable!(), + VintedWrapperError::CookiesError(_) => (), + }, + }; +} + +#[tokio::test] +async fn test_get_items_by_size() { + let vinted = VintedWrapper::new(); + let size_id = String::from("1568"); + let size_title = String::from("XS"); + + let filter: Filter = Filter::builder().size_ids(size_id).build(); + + match vinted.get_items(&filter, 20).await { + Ok(items) => { + assert_eq!(items.items.len(), 20); + let ok: bool = items.items.iter().all(|item| item.size_title == size_title); + + assert!(ok); + } + Err(err) => match err { + VintedWrapperError::ItemNumberError => unreachable!(), + VintedWrapperError::CookiesError(_) => (), + }, + }; +} + +#[tokio::test] +async fn test_get_items_by_material() { + let vinted = VintedWrapper::new(); + let id = 49; // Silk + + let filter: Filter = Filter::builder().material_ids(id.to_string()).build(); + let num: usize = 15; + + match vinted.get_items(&filter, num as u32).await { + Ok(items) => { + assert_eq!(items.items.len(), num); + } + Err(err) => match err { + VintedWrapperError::ItemNumberError => unreachable!(), + VintedWrapperError::CookiesError(_) => (), + }, + }; +} + +#[tokio::test] +async fn test_get_items_by_color() { + let vinted = VintedWrapper::new(); + let id = 7; //Red + //let hex = "#CC3300"; //Red + + //let props = calculate_color_props(hex); + + let filter: Filter = Filter::builder().color_ids(id.to_string()).build(); + + let num: usize = 20; + + match vinted.get_items(&filter, num as u32).await { + Ok(items) => { + assert_eq!(items.items.len(), num); } Err(err) => match err { VintedWrapperError::ItemNumberError => unreachable!(),