diff --git a/Cargo.toml b/Cargo.toml index d3bbd44..f9b523f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ path = "src/main.rs" [lints.rust] unsafe_code = "forbid" +deprecated = "allow" [dependencies] actix-web = "4.9.0" diff --git a/src/models/creature/creature_struct.rs b/src/models/creature/creature_struct.rs index 71b6c2a..6b66fd7 100644 --- a/src/models/creature/creature_struct.rs +++ b/src/models/creature/creature_struct.rs @@ -120,20 +120,17 @@ impl Creature { } fn check_creature_pass_equality_filters(&self, filters: &CreatureFieldFilters) -> bool { - filters - .rarity_filter - .as_ref() - .map_or(true, |rarity| self.core_data.essential.rarity == *rarity) - && filters - .size_filter - .as_ref() - .map_or(true, |size| self.core_data.essential.size == *size) - && filters.alignment_filter.as_ref().map_or(true, |alignment| { - self.core_data.essential.alignment == *alignment - }) - && filters - .is_melee_filter - .map_or(true, |is_melee| self.core_data.derived.is_melee == is_melee) + filters.rarity_filter.as_ref().map_or(true, |x| { + x.iter() + .any(|rarity| self.core_data.essential.rarity == *rarity) + }) && filters.size_filter.as_ref().map_or(true, |x| { + x.iter().any(|size| self.core_data.essential.size == *size) + }) && filters.alignment_filter.as_ref().map_or(true, |x| { + x.iter() + .any(|align| self.core_data.essential.alignment == *align) + }) && filters + .is_melee_filter + .map_or(true, |is_melee| self.core_data.derived.is_melee == is_melee) && filters.is_ranged_filter.map_or(true, |is_ranged| { self.core_data.derived.is_ranged == is_ranged }) @@ -142,14 +139,14 @@ impl Creature { .map_or(true, |is_spell_caster| { self.core_data.derived.is_spell_caster == is_spell_caster }) - && filters - .type_filter - .as_ref() - .map_or(true, |cr_type| self.core_data.essential.cr_type == *cr_type) + && filters.type_filter.as_ref().map_or(true, |x| { + x.iter() + .any(|cr_type| self.core_data.essential.cr_type == *cr_type) + }) && (filters.role_threshold.is_none() || filters.role_filter.as_ref().map_or(true, |cr_role| { let t = filters.role_threshold.unwrap_or(0); - match cr_role { + cr_role.iter().any(|role| match role { CreatureRoleEnum::Brute => self.core_data.derived.brute_percentage >= t, CreatureRoleEnum::MagicalStriker => { self.core_data.derived.magical_striker_percentage >= t @@ -165,7 +162,7 @@ impl Creature { CreatureRoleEnum::SpellCaster => { self.core_data.derived.spell_caster_percentage >= t } - } + }) })) && match filters.pathfinder_version.clone().unwrap_or_default() { PathfinderVersionEnum::Legacy => !self.core_data.essential.remaster, @@ -176,13 +173,19 @@ impl Creature { fn check_creature_pass_string_filters(&self, filters: &CreatureFieldFilters) -> bool { filters.name_filter.as_ref().map_or(true, |name| { - self.core_data.essential.name.to_lowercase().contains(name) - }) && filters.family_filter.as_ref().map_or(true, |name| { self.core_data .essential - .family + .name .to_lowercase() - .contains(name) + .contains(name.to_lowercase().as_str()) + }) && filters.family_filter.as_ref().map_or(true, |x| { + x.iter().any(|fam| { + self.core_data + .essential + .family + .to_lowercase() + .contains(fam.to_lowercase().as_str()) + }) }) } } diff --git a/src/models/item/item_struct.rs b/src/models/item/item_struct.rs index 40ae12e..a35559b 100644 --- a/src/models/item/item_struct.rs +++ b/src/models/item/item_struct.rs @@ -156,6 +156,11 @@ impl Item { .type_filter .as_ref() .map_or(true, |x| x.iter().any(|t_filt| self.item_type == *t_filt)) + && match filters.pathfinder_version.clone().unwrap_or_default() { + PathfinderVersionEnum::Legacy => !self.remaster, + PathfinderVersionEnum::Remaster => self.remaster, + PathfinderVersionEnum::Any => true, + } } fn check_item_pass_string_filters(&self, filters: &ItemFieldFilters) -> bool { @@ -171,11 +176,7 @@ impl Item { .to_lowercase() .contains(cat.to_lowercase().as_str()) }) - }) && match filters.pathfinder_version.clone().unwrap_or_default() { - PathfinderVersionEnum::Legacy => !self.remaster, - PathfinderVersionEnum::Remaster => self.remaster, - PathfinderVersionEnum::Any => true, - } && filters.source_filter.as_ref().map_or(true, |x| { + }) && filters.source_filter.as_ref().map_or(true, |x| { x.iter().any(|source| { self.source .to_lowercase() diff --git a/src/models/routers_validator_structs.rs b/src/models/routers_validator_structs.rs index fa0f5b1..37ade05 100644 --- a/src/models/routers_validator_structs.rs +++ b/src/models/routers_validator_structs.rs @@ -8,25 +8,25 @@ use crate::models::shared::size_enum::SizeEnum; use serde::{Deserialize, Serialize}; use strum::Display; use utoipa::{IntoParams, ToSchema}; -#[derive(Serialize, Deserialize, IntoParams)] +#[derive(Serialize, Deserialize, IntoParams, ToSchema)] pub struct CreatureFieldFilters { pub name_filter: Option, - pub source_filter: Option, - pub family_filter: Option, - pub rarity_filter: Option, - pub size_filter: Option, - pub alignment_filter: Option, - pub role_filter: Option, - pub type_filter: Option, - #[param(minimum = 0, maximum = 100, example = 50)] + pub source_filter: Option>, + pub family_filter: Option>, + pub rarity_filter: Option>, + pub size_filter: Option>, + pub alignment_filter: Option>, + pub role_filter: Option>, + pub type_filter: Option>, + #[schema(minimum = 0, maximum = 100, example = 50)] pub role_threshold: Option, - #[param(minimum = 0, example = 0)] + #[schema(minimum = 0, example = 0)] pub min_hp_filter: Option, - #[param(minimum = 0, example = 100)] + #[schema(minimum = 0, example = 100)] pub max_hp_filter: Option, - #[param(minimum = -1, example = -1)] + #[schema(minimum = -1, example = -1)] pub min_level_filter: Option, - #[param(minimum = -1, example = 5)] + #[schema(minimum = -1, example = 5)] pub max_level_filter: Option, pub is_melee_filter: Option, pub is_ranged_filter: Option, diff --git a/src/routes/bestiary.rs b/src/routes/bestiary.rs index a341f01..8a0ff22 100644 --- a/src/routes/bestiary.rs +++ b/src/routes/bestiary.rs @@ -33,9 +33,10 @@ use crate::models::db::sense::Sense; use crate::models::routers_validator_structs::{CreatureFieldFilters, PaginatedRequest}; use crate::services::bestiary_service; use crate::services::bestiary_service::BestiaryResponse; +use crate::services::sanitizer::sanitize_id; use crate::AppState; use actix_web::web::Query; -use actix_web::{error, get, web, Responder, Result}; +use actix_web::{get, post, web, Responder}; use utoipa::OpenApi; pub fn init_endpoints(cfg: &mut web::ServiceConfig) { @@ -61,6 +62,7 @@ pub fn init_docs(doc: &mut utoipa::openapi::OpenApi) { #[openapi( paths( get_bestiary, + get_bestiary_listing, get_families_list, get_traits_list, get_sources_list, @@ -122,14 +124,15 @@ pub fn init_docs(doc: &mut utoipa::openapi::OpenApi) { ), )] #[get("/list")] +#[deprecated(since = "2.4.0", note = "please use `get_bestiary_listing` instead")] pub async fn get_bestiary( data: web::Data, filters: Query, pagination: Query, sort_data: Query, -) -> Result { +) -> actix_web::Result { Ok(web::Json( - bestiary_service::get_bestiary( + bestiary_service::get_bestiary_listing( &data, &filters.0, &BestiaryPaginatedRequest { @@ -141,6 +144,42 @@ pub async fn get_bestiary( )) } +#[utoipa::path( + post, + path = "/bestiary/list", + tag = "bestiary", + request_body( + content = CreatureFieldFilters, + content_type = "application/json" + ), + params( + PaginatedRequest, BestiarySortData + ), + responses( + (status=200, description = "Successful Response", body = BestiaryResponse), + (status=400, description = "Bad request.") + ), +)] +#[post("/list")] +pub async fn get_bestiary_listing( + data: web::Data, + web::Json(body): web::Json, + pagination: Query, + sort_data: Query, +) -> actix_web::Result { + Ok(web::Json( + bestiary_service::get_bestiary_listing( + &data, + &body, + &BestiaryPaginatedRequest { + paginated_request: pagination.0, + bestiary_sort_data: sort_data.0, + }, + ) + .await, + )) +} + #[utoipa::path( get, path = "/bestiary/families", @@ -154,7 +193,7 @@ pub async fn get_bestiary( ), )] #[get("/families")] -pub async fn get_families_list(data: web::Data) -> Result { +pub async fn get_families_list(data: web::Data) -> actix_web::Result { Ok(web::Json(bestiary_service::get_families_list(&data).await)) } @@ -171,7 +210,7 @@ pub async fn get_families_list(data: web::Data) -> Result) -> Result { +pub async fn get_traits_list(data: web::Data) -> actix_web::Result { Ok(web::Json(bestiary_service::get_traits_list(&data).await)) } @@ -188,7 +227,7 @@ pub async fn get_traits_list(data: web::Data) -> Result) -> Result { +pub async fn get_sources_list(data: web::Data) -> actix_web::Result { Ok(web::Json(bestiary_service::get_sources_list(&data).await)) } @@ -205,7 +244,7 @@ pub async fn get_sources_list(data: web::Data) -> Result) -> Result { +pub async fn get_rarities_list(data: web::Data) -> actix_web::Result { Ok(web::Json(bestiary_service::get_rarities_list(&data).await)) } @@ -222,7 +261,7 @@ pub async fn get_rarities_list(data: web::Data) -> Result) -> Result { +pub async fn get_sizes_list(data: web::Data) -> actix_web::Result { Ok(web::Json(bestiary_service::get_sizes_list(&data).await)) } @@ -239,7 +278,7 @@ pub async fn get_sizes_list(data: web::Data) -> Result ), )] #[get("/alignments")] -pub async fn get_alignments_list(data: web::Data) -> Result { +pub async fn get_alignments_list(data: web::Data) -> actix_web::Result { Ok(web::Json( bestiary_service::get_alignments_list(&data).await, )) @@ -258,7 +297,9 @@ pub async fn get_alignments_list(data: web::Data) -> Result) -> Result { +pub async fn get_creature_types_list( + data: web::Data, +) -> actix_web::Result { Ok(web::Json( bestiary_service::get_creature_types_list(&data).await, )) @@ -277,7 +318,7 @@ pub async fn get_creature_types_list(data: web::Data) -> Result Result { +pub async fn get_creature_roles_list() -> actix_web::Result { Ok(web::Json(bestiary_service::get_creature_roles_list().await)) } @@ -299,7 +340,7 @@ pub async fn get_creature( data: web::Data, creature_id: web::Path, response_data_mods: Query, -) -> Result { +) -> actix_web::Result { Ok(web::Json( bestiary_service::get_creature(&data, sanitize_id(&creature_id)?, &response_data_mods.0) .await, @@ -324,7 +365,7 @@ pub async fn get_elite_creature( data: web::Data, creature_id: web::Path, response_data_mods: Query, -) -> Result { +) -> actix_web::Result { Ok(web::Json( bestiary_service::get_elite_creature( &data, @@ -353,7 +394,7 @@ pub async fn get_weak_creature( data: web::Data, creature_id: web::Path, response_data_mods: Query, -) -> Result { +) -> actix_web::Result { Ok(web::Json( bestiary_service::get_weak_creature( &data, @@ -363,11 +404,3 @@ pub async fn get_weak_creature( .await, )) } - -fn sanitize_id(creature_id: &str) -> Result { - let id = creature_id.parse::(); - match id { - Ok(s) => Ok(s), - Err(e) => Err(error::ErrorNotFound(e)), - } -} diff --git a/src/routes/shop.rs b/src/routes/shop.rs index d4689d8..da830e7 100644 --- a/src/routes/shop.rs +++ b/src/routes/shop.rs @@ -12,11 +12,12 @@ use crate::models::shop_structs::ShopTemplateData; use crate::models::shop_structs::ShopTemplateEnum; use crate::models::shop_structs::{ItemSortEnum, ShopPaginatedRequest}; use crate::models::shop_structs::{RandomShopData, ShopSortData}; +use crate::services::sanitizer::sanitize_id; use crate::services::shop_service; use crate::services::shop_service::ShopListingResponse; use crate::AppState; use actix_web::web::Query; -use actix_web::{error, get, post, web, Responder}; +use actix_web::{get, post, web, Responder}; use utoipa::OpenApi; pub fn init_endpoints(cfg: &mut web::ServiceConfig) { @@ -201,11 +202,3 @@ pub async fn get_items_traits_list(data: web::Data) -> actix_web::Resu pub async fn get_templates_data() -> actix_web::Result { Ok(web::Json(shop_service::get_shop_templates_data().await)) } - -fn sanitize_id(creature_id: &str) -> actix_web::Result { - let id = creature_id.parse::(); - match id { - Ok(s) => Ok(s), - Err(e) => Err(error::ErrorNotFound(e)), - } -} diff --git a/src/services/bestiary_service.rs b/src/services/bestiary_service.rs index 1eabff1..566fffd 100644 --- a/src/services/bestiary_service.rs +++ b/src/services/bestiary_service.rs @@ -54,7 +54,7 @@ pub async fn get_weak_creature( } } -pub async fn get_bestiary( +pub async fn get_bestiary_listing( app_state: &AppState, field_filter: &CreatureFieldFilters, pagination: &BestiaryPaginatedRequest, diff --git a/src/services/mod.rs b/src/services/mod.rs index 8eb26ed..3b56e67 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,5 +1,6 @@ pub mod bestiary_service; pub mod encounter_handler; pub mod encounter_service; +pub mod sanitizer; pub mod shop_service; pub mod url_calculator; diff --git a/src/services/sanitizer.rs b/src/services/sanitizer.rs new file mode 100644 index 0000000..6d0dea4 --- /dev/null +++ b/src/services/sanitizer.rs @@ -0,0 +1,9 @@ +use actix_web::error; + +pub fn sanitize_id(creature_id: &str) -> actix_web::Result { + let id = creature_id.parse::(); + match id { + Ok(s) => Ok(s), + Err(e) => Err(error::ErrorNotFound(e)), + } +} diff --git a/src/services/url_calculator.rs b/src/services/url_calculator.rs index 5ae554e..20af47b 100644 --- a/src/services/url_calculator.rs +++ b/src/services/url_calculator.rs @@ -65,30 +65,6 @@ fn creature_filter_query_calculator(field_filters: &CreatureFieldFilters) -> Str .name_filter .clone() .map(|name| format!("name_filter={}", name)), - field_filters - .family_filter - .clone() - .map(|fam| format!("family_filter={}", fam)), - field_filters - .rarity_filter - .clone() - .map(|rar| format!("rarity_filter={}", rar)), - field_filters - .size_filter - .clone() - .map(|size| format!("size_filter={}", size)), - field_filters - .alignment_filter - .clone() - .map(|align| format!("alignment_filter={}", align)), - field_filters - .role_filter - .clone() - .map(|role| format!("role_filter={}", role)), - field_filters - .type_filter - .clone() - .map(|cr_type| format!("type_filter={}", cr_type)), field_filters .role_threshold .map(|threshold| format!("role_threshold={}", threshold)),