Updated routes to use filters/error handling

This commit is contained in:
Benjamin Sherriff
2023-10-04 10:55:34 -04:00
parent 2b7ec386a0
commit 23d42953f0
7 changed files with 304 additions and 232 deletions

View File

@@ -32,7 +32,7 @@ pub fn load_data() {
Err(err) => log::error!("Failed to read from {}: {}", file, err)
};
}
let count = QuerySpell::get_count().unwrap();
let count = QuerySpell::get_count(&QueryFilters::default()).unwrap();
if count >= spells.len() as i64 {
log::warn!("Spell data is already loaded");
return;

View File

@@ -13,13 +13,48 @@ pub struct QuerySpell {
pub data: serde_json::Value,
}
#[derive(Debug)]
pub struct QueryFilters {
pub by_name: Option<String>,
pub by_schools: Option<Vec<SchoolType>>,
pub by_levels: Option<Vec<i32>>,
pub by_ritual: Option<bool>,
pub by_concentration: Option<bool>,
pub by_classes: Option<Vec<String>>,
pub by_damage_inflict: Option<Vec<SpellDamageType>>,
pub by_damage_resist: Option<Vec<SpellDamageType>>,
pub by_conditions: Option<Vec<ConditionType>>,
pub by_saving_throw: Option<Vec<AbilityType>>,
pub by_attack_type: Option<SpellAttackType>,
}
impl Default for QueryFilters {
fn default() -> Self {
Self {
by_name: None,
by_schools: None,
by_levels: None,
by_ritual: None,
by_concentration: None,
by_classes: None,
by_damage_inflict: None,
by_damage_resist: None,
by_conditions: None,
by_saving_throw: None,
by_attack_type: None,
}
}
}
impl QuerySpell {
pub fn get_all(limit: i32, page: i32) -> Result<Vec<Self>, ServiceError> {
pub fn get_all(filters: &QueryFilters, limit: i32, page: i32) -> Result<Vec<Self>, ServiceError> {
let mut conn = crate::db::connection()?;
let mut query = spells::table
.limit(limit as i64)
.into_boxed();
let mut query = spells::table.limit(limit as i64).into_boxed();
query = query.filter(spells::id.gt(std::cmp::max(0, page - 1) * limit));
if let Some(name) = filters.by_name.to_owned() {
query = query.filter(spells::name.ilike(format!("%{}%", name)));
}
let spells = query.load::<QuerySpell>(&mut conn)?;
Ok(spells)
}
@@ -32,9 +67,14 @@ impl QuerySpell {
Ok(spell)
}
pub fn get_count() -> Result<i64, ServiceError> {
pub fn get_count(filters: &QueryFilters) -> Result<i64, ServiceError> {
let mut conn = crate::db::connection()?;
let count = spells::table.count().get_result(&mut conn)?;
let mut query = spells::table.count().into_boxed();
if let Some(name) = filters.by_name.to_owned() {
query = query.filter(spells::name.ilike(format!("%{}%", name)));
}
let count = query.get_result(&mut conn)?;
Ok(count)
}

View File

@@ -1,28 +1,41 @@
use actix_web::{get, post, put, delete, web, HttpResponse, HttpRequest, ResponseError};
use serde::{Serialize, Deserialize};
use crate::db::{spells::QuerySpell, GetResponse, Metadata};
use crate::{db::{spells::{QuerySpell, QueryFilters}, GetResponse, Metadata}, error_handler::ServiceError};
use super::{Spell, InsertSpell};
#[derive(Serialize, Deserialize)]
struct GetAllParams {
name: Option<String>,
limit: Option<i32>,
page: Option<i32>,
}
#[get("/spells")]
async fn get_all(req: HttpRequest) -> HttpResponse {
let params = web::Query::<GetAllParams>::from_query(req.query_string()).unwrap();
let limit = params.limit.unwrap_or(20);
let page = params.page.unwrap_or(1);
match web::block(move || QuerySpell::get_all(limit, page)).await.unwrap() {
let params = match web::Query::<GetAllParams>::from_query(req.query_string()) {
Ok(params) => params,
Err(err) => return ResponseError::error_response(&ServiceError {
status: 422,
message: err.to_string()
})
};
let mut filters = QueryFilters::default();
filters.by_name = params.name.clone();
// Limit must be between 1 and 100
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(20), 1), 100);
let total_count = QuerySpell::get_count(&filters).unwrap();
let max_page = std::cmp::max(1, (total_count as f64 / limit as f64).ceil() as i32);
// Page must be between 1 and max_page
let page = std::cmp::min(std::cmp::max(params.page.unwrap_or(1), 1), max_page);
match web::block(move || QuerySpell::get_all(&filters, limit, page)).await.unwrap() {
Ok(spells) => {
let mut response: Vec<Spell> = Vec::new();
for spell in spells {
response.push(Spell::from(spell));
}
let total_count = QuerySpell::get_count().unwrap();
HttpResponse::Ok().json(GetResponse {
data: response,
metadata: Some(Metadata {
@@ -37,8 +50,15 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
}
#[get("/spells/{id}")]
async fn get_by_id(id: web::Path<i32>) -> HttpResponse {
match web::block(move || QuerySpell::get_by_id(id.into_inner())).await.unwrap() {
async fn get_by_id(id: web::Path<String>) -> HttpResponse {
let id = match id.parse::<i32>() {
Ok(id) => id,
Err(err) => return ResponseError::error_response(&ServiceError {
status: 422,
message: err.to_string()
})
};
match web::block(move || QuerySpell::get_by_id(id)).await.unwrap() {
Ok(spell) => HttpResponse::Ok().json(GetResponse {
data: Spell::from(spell),
metadata: None
@@ -56,16 +76,30 @@ async fn create(spell: web::Json<Spell>) -> HttpResponse {
}
#[put("/spells/{id}")]
async fn update(id: web::Path<i32>, spell: web::Json<Spell>) -> HttpResponse {
match web::block(move || InsertSpell::update(id.into_inner(), spell.into_inner().into())).await.unwrap() {
async fn update(id: web::Path<String>, spell: web::Json<Spell>) -> HttpResponse {
let id = match id.parse::<i32>() {
Ok(id) => id,
Err(err) => return ResponseError::error_response(&ServiceError {
status: 422,
message: err.to_string()
})
};
match web::block(move || InsertSpell::update(id, spell.into_inner().into())).await.unwrap() {
Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
Err(err) => ResponseError::error_response(&err)
}
}
#[delete("/spells/{id}")]
async fn delete(id: web::Path<i32>) -> HttpResponse {
match web::block(move || QuerySpell::delete(id.into_inner())).await.unwrap() {
async fn delete(id: web::Path<String>) -> HttpResponse {
let id = match id.parse::<i32>() {
Ok(id) => id,
Err(err) => return ResponseError::error_response(&ServiceError {
status: 422,
message: err.to_string()
})
};
match web::block(move || QuerySpell::delete(id)).await.unwrap() {
Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
Err(err) => ResponseError::error_response(&err)
}

View File

@@ -1,4 +1,4 @@
use std::str::FromStr;
// use std::str::FromStr;
use serde::{Deserialize, Serialize, ser::SerializeMap};
#[derive(Debug, Serialize, Deserialize)]
@@ -21,38 +21,38 @@ pub enum SchoolType {
Transmutation
}
impl SchoolType {
pub fn to_string(&self) -> String {
match self {
SchoolType::Abjuration => "abjuration".to_string(),
SchoolType::Conjuration => "conjuration".to_string(),
SchoolType::Divination => "divination".to_string(),
SchoolType::Enchantment => "enchantment".to_string(),
SchoolType::Evocation => "evocation".to_string(),
SchoolType::Illusion => "illusion".to_string(),
SchoolType::Necromancy => "necromancy".to_string(),
SchoolType::Transmutation => "transmutation".to_string()
}
}
}
// impl SchoolType {
// pub fn to_string(&self) -> String {
// match self {
// SchoolType::Abjuration => "abjuration".to_string(),
// SchoolType::Conjuration => "conjuration".to_string(),
// SchoolType::Divination => "divination".to_string(),
// SchoolType::Enchantment => "enchantment".to_string(),
// SchoolType::Evocation => "evocation".to_string(),
// SchoolType::Illusion => "illusion".to_string(),
// SchoolType::Necromancy => "necromancy".to_string(),
// SchoolType::Transmutation => "transmutation".to_string()
// }
// }
// }
impl FromStr for SchoolType {
type Err = ();
// impl FromStr for SchoolType {
// type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"abjuration" => Ok(SchoolType::Abjuration),
"conjuration" => Ok(SchoolType::Conjuration),
"divination" => Ok(SchoolType::Divination),
"enchantment" => Ok(SchoolType::Enchantment),
"evocation" => Ok(SchoolType::Evocation),
"illusion" => Ok(SchoolType::Illusion),
"necromancy" => Ok(SchoolType::Necromancy),
"transmutation" => Ok(SchoolType::Transmutation),
_ => Err(())
}
}
}
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// match s {
// "abjuration" => Ok(SchoolType::Abjuration),
// "conjuration" => Ok(SchoolType::Conjuration),
// "divination" => Ok(SchoolType::Divination),
// "enchantment" => Ok(SchoolType::Enchantment),
// "evocation" => Ok(SchoolType::Evocation),
// "illusion" => Ok(SchoolType::Illusion),
// "necromancy" => Ok(SchoolType::Necromancy),
// "transmutation" => Ok(SchoolType::Transmutation),
// _ => Err(())
// }
// }
// }
#[derive(Debug, Serialize, Deserialize)]
pub struct CastingTime {
@@ -75,32 +75,32 @@ pub enum CastingType {
Hours
}
impl CastingType {
pub fn to_string(&self) -> String {
match self {
CastingType::Action => "action".to_string(),
CastingType::BonusAction => "bonus".to_string(),
CastingType::Reaction => "reaction".to_string(),
CastingType::Minutes => "minutes".to_string(),
CastingType::Hours => "hours".to_string()
}
}
}
// impl CastingType {
// pub fn to_string(&self) -> String {
// match self {
// CastingType::Action => "action".to_string(),
// CastingType::BonusAction => "bonus".to_string(),
// CastingType::Reaction => "reaction".to_string(),
// CastingType::Minutes => "minutes".to_string(),
// CastingType::Hours => "hours".to_string()
// }
// }
// }
impl FromStr for CastingType {
type Err = ();
// impl FromStr for CastingType {
// type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"action" => Ok(CastingType::Action),
"bonus" => Ok(CastingType::BonusAction),
"reaction" => Ok(CastingType::Reaction),
"minutes" => Ok(CastingType::Minutes),
"hours" => Ok(CastingType::Hours),
_ => Err(())
}
}
}
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// match s {
// "action" => Ok(CastingType::Action),
// "bonus" => Ok(CastingType::BonusAction),
// "reaction" => Ok(CastingType::Reaction),
// "minutes" => Ok(CastingType::Minutes),
// "hours" => Ok(CastingType::Hours),
// _ => Err(())
// }
// }
// }
#[derive(Debug, Serialize, Deserialize)]
pub enum SpellAttackType {
@@ -110,26 +110,26 @@ pub enum SpellAttackType {
Ranged,
}
impl SpellAttackType {
pub fn to_string(&self) -> String {
match self {
SpellAttackType::Melee => "melee".to_string(),
SpellAttackType::Ranged => "ranged".to_string()
}
}
}
// impl SpellAttackType {
// pub fn to_string(&self) -> String {
// match self {
// SpellAttackType::Melee => "melee".to_string(),
// SpellAttackType::Ranged => "ranged".to_string()
// }
// }
// }
impl FromStr for SpellAttackType {
type Err = ();
// impl FromStr for SpellAttackType {
// type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"melee" => Ok(SpellAttackType::Melee),
"ranged" => Ok(SpellAttackType::Ranged),
_ => Err(())
}
}
}
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// match s {
// "melee" => Ok(SpellAttackType::Melee),
// "ranged" => Ok(SpellAttackType::Ranged),
// _ => Err(())
// }
// }
// }
#[derive(Debug, Serialize, Deserialize)]
pub enum SpellDamageType {
@@ -161,48 +161,48 @@ pub enum SpellDamageType {
Thunder
}
impl SpellDamageType {
pub fn to_string(&self) -> String {
match self {
SpellDamageType::Acid => "acid".to_string(),
SpellDamageType::Bludgeoning => "bludgeoning".to_string(),
SpellDamageType::Cold => "cold".to_string(),
SpellDamageType::Fire => "fire".to_string(),
SpellDamageType::Force => "force".to_string(),
SpellDamageType::Lightning => "lightning".to_string(),
SpellDamageType::Necrotic => "necrotic".to_string(),
SpellDamageType::Piercing => "piercing".to_string(),
SpellDamageType::Poison => "poison".to_string(),
SpellDamageType::Psychic => "psychic".to_string(),
SpellDamageType::Radiant => "radiant".to_string(),
SpellDamageType::Slashing => "slashing".to_string(),
SpellDamageType::Thunder => "thunder".to_string()
}
}
}
// impl SpellDamageType {
// pub fn to_string(&self) -> String {
// match self {
// SpellDamageType::Acid => "acid".to_string(),
// SpellDamageType::Bludgeoning => "bludgeoning".to_string(),
// SpellDamageType::Cold => "cold".to_string(),
// SpellDamageType::Fire => "fire".to_string(),
// SpellDamageType::Force => "force".to_string(),
// SpellDamageType::Lightning => "lightning".to_string(),
// SpellDamageType::Necrotic => "necrotic".to_string(),
// SpellDamageType::Piercing => "piercing".to_string(),
// SpellDamageType::Poison => "poison".to_string(),
// SpellDamageType::Psychic => "psychic".to_string(),
// SpellDamageType::Radiant => "radiant".to_string(),
// SpellDamageType::Slashing => "slashing".to_string(),
// SpellDamageType::Thunder => "thunder".to_string()
// }
// }
// }
impl FromStr for SpellDamageType {
type Err = ();
// impl FromStr for SpellDamageType {
// type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"acid" => Ok(SpellDamageType::Acid),
"bludgeoning" => Ok(SpellDamageType::Bludgeoning),
"cold" => Ok(SpellDamageType::Cold),
"fire" => Ok(SpellDamageType::Fire),
"force" => Ok(SpellDamageType::Force),
"lightning" => Ok(SpellDamageType::Lightning),
"necrotic" => Ok(SpellDamageType::Necrotic),
"piercing" => Ok(SpellDamageType::Piercing),
"poison" => Ok(SpellDamageType::Poison),
"psychic" => Ok(SpellDamageType::Psychic),
"radiant" => Ok(SpellDamageType::Radiant),
"slashing" => Ok(SpellDamageType::Slashing),
"thunder" => Ok(SpellDamageType::Thunder),
_ => Err(())
}
}
}
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// match s {
// "acid" => Ok(SpellDamageType::Acid),
// "bludgeoning" => Ok(SpellDamageType::Bludgeoning),
// "cold" => Ok(SpellDamageType::Cold),
// "fire" => Ok(SpellDamageType::Fire),
// "force" => Ok(SpellDamageType::Force),
// "lightning" => Ok(SpellDamageType::Lightning),
// "necrotic" => Ok(SpellDamageType::Necrotic),
// "piercing" => Ok(SpellDamageType::Piercing),
// "poison" => Ok(SpellDamageType::Poison),
// "psychic" => Ok(SpellDamageType::Psychic),
// "radiant" => Ok(SpellDamageType::Radiant),
// "slashing" => Ok(SpellDamageType::Slashing),
// "thunder" => Ok(SpellDamageType::Thunder),
// _ => Err(())
// }
// }
// }
#[derive(Debug, Serialize, Deserialize)]
pub struct Range {
@@ -238,32 +238,32 @@ pub enum AreaType {
Sphere
}
impl AreaType {
pub fn to_string(&self) -> String {
match self {
AreaType::Cone => "cone".to_string(),
AreaType::Cube => "cube".to_string(),
AreaType::Cylinder => "cylinder".to_string(),
AreaType::Line => "line".to_string(),
AreaType::Sphere => "sphere".to_string()
}
}
}
// impl AreaType {
// pub fn to_string(&self) -> String {
// match self {
// AreaType::Cone => "cone".to_string(),
// AreaType::Cube => "cube".to_string(),
// AreaType::Cylinder => "cylinder".to_string(),
// AreaType::Line => "line".to_string(),
// AreaType::Sphere => "sphere".to_string()
// }
// }
// }
impl FromStr for AreaType {
type Err = ();
// impl FromStr for AreaType {
// type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"cone" => Ok(AreaType::Cone),
"cube" => Ok(AreaType::Cube),
"cylinder" => Ok(AreaType::Cylinder),
"line" => Ok(AreaType::Line),
"sphere" => Ok(AreaType::Sphere),
_ => Err(())
}
}
}
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// match s {
// "cone" => Ok(AreaType::Cone),
// "cube" => Ok(AreaType::Cube),
// "cylinder" => Ok(AreaType::Cylinder),
// "line" => Ok(AreaType::Line),
// "sphere" => Ok(AreaType::Sphere),
// _ => Err(())
// }
// }
// }
#[derive(Debug, Serialize, Deserialize)]
pub struct Duration {