use std::borrow::Cow; use hyper::{StatusCode, Response, Body}; use hyper::header::HeaderValue; use mysql_async::{Conn, Pool}; use mysql_async::error::Error; use mysql_async::prelude::{params, Queryable}; use serde_json::Value; use crate::db_types::{DbError, UBigInt, VarChar, Integer}; #[derive(Debug)] pub enum ChannelType { Voice, Text, Undefined } impl ChannelType { // These funcs are mainly here to help translation from mysql pub fn from_i32(x: i32) -> ChannelType { match x { 1 => ChannelType::Voice, 2 => ChannelType::Text, _ => ChannelType::Undefined } } pub fn as_i32(&self) -> i32 { match self { ChannelType::Voice => 1, ChannelType::Text => 2, ChannelType::Undefined => 3, } } // whole ass function exists because serde_json is a walking pos pub fn from_i64_opt(x: Option) -> ChannelType { if let Some(i) = x { match i { 1 => ChannelType::Voice, 2 => ChannelType::Text, _ => ChannelType::Undefined } } else { ChannelType::Undefined } } } // Primary way of interpretting sql data on our channels table pub struct Channel { id: u64, name: String, description: String, kind: ChannelType } #[derive(Debug)] struct InsertableChannel { name: String, kind: ChannelType } impl Channel { /* * When our sql library queries things we generally get back tuples rather reasily * we can use this method to get something that makes more sense */ fn from_tup(tup: (UBigInt, VarChar, Option, Integer)) -> Channel { let desc = match tup.2 { Some(val) => val, None => "None".into() }; Channel { id: tup.0, name: tup.1, description: desc, kind: ChannelType::from_i32(tup.3) } } /* * When responding with some channel data to the client we use json * this itemizes a single struct as the following(without the pretty output) * { * "id": id, * "name": "", * "description": Option<"">, * "kind": kind * } */ fn as_json_str(&self) -> String { let mut base = String::from("{"); base.push_str(&format!("\"id\":{},", self.id)); base.push_str(&format!("\"name\":\"{}\",", self.name)); base.push_str(&format!("\"description\":\"{}\",", self.description)); base.push_str(&format!("\"kind\":{}}}", self.kind.as_i32())); return base; } } /* * Forwarding SQL errors as we can handle those error in caller site for this leaf function * On success we back a Vec which we can JSON'ify later and use as a response */ async fn get_channels_vec(conn: Conn) -> Result, Error> { let rows_db = conn.prep_exec(r"SELECT * FROM channels", ()).await?; let (_, rows) = rows_db.map_and_drop(|row| { let (id, name, desc, kind): (UBigInt, VarChar, Option, Integer) = mysql_async::from_row(row); Channel::from_tup((id, name, desc, kind)) }).await?; Ok(rows) } pub async fn list_channels(pool: &Pool, response: &mut Response) { /* * Primary dispatcher for dealing with the CHANNELS_LIST route * For the most part this function will have a lot more error handling as it * should know what kind of issues its child functions will have */ if let Ok(conn) = pool.get_conn().await { match get_channels_vec(conn).await { Ok(chans) => { *response.status_mut() = StatusCode::OK; response.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json")); // At this point we build the content of our response body // which is a json payload hence why there is weird string manipulation // because we're trying to avoid dependancy issues and serializing things ourselves let mut new_body = String::from("{\"channels\":["); for chan in chans.iter() { let s = format!("{},", chan.as_json_str()); new_body.push_str(&s); } if new_body.ends_with(',') {new_body.pop();} new_body.push_str("]}"); *response.body_mut() = Body::from(new_body); }, Err(_) => { *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } } } else { *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } } fn parse_insert_channel_params(name_r: Option<&Value>, kind_r: Option<&Value>) -> Result { match (name_r, kind_r) { (Some(name), Some(kind)) => { let channel = InsertableChannel { name: name.as_str().unwrap().into(), // as_str removes the quotes from the output kind: ChannelType::from_i64_opt(kind.as_i64()), }; println!("Insertable channel name: {}", channel.name); Ok(channel) } _ => { let x = Cow::from("Missing required parameters"); Err(Error::Other(x)) } } } async fn insert_channel(pool: &Pool, channel: InsertableChannel) -> Result<(), DbError> { if let Ok(conn) = pool.get_conn().await { let db_result: Result = conn.drop_exec( "INSERT INTO channels (name, kind) VALUES (:name, 0)", params!{"name" => channel.name} ).await; match db_result { Ok(_) => Ok(()), Err(e) => { match e { Error::Server(_) => Err(DbError::BadParam("Couldn't process input somehow")), _ => Err(DbError::Internal) } } } } else { Err(DbError::Connection("Could not connect to database at all")) } } pub async fn create_channel(pool: &Pool, response: &mut Response, params: Value) { /* * Create a channel base on a few parameters that may or may not be there */ let name_r = params.get("name"); let kind_r = params.get("kind"); match parse_insert_channel_params(name_r, kind_r) { Ok(channel) => { match insert_channel(pool, channel).await { Ok(_) => *response.status_mut() = StatusCode::OK, Err(dbe) => { // Todo: add some form of loggin off the back of these values match dbe { DbError::BadParam(_msg) => *response.status_mut() = StatusCode::BAD_REQUEST, DbError::Connection(_msg) => *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR, DbError::Internal => *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR } } } }, Err(_) => *response.status_mut() = StatusCode::BAD_REQUEST } } async fn db_delete_channel(pool: &Pool, name: &Value) -> Result<(), Error> { let conn = pool.get_conn().await?; conn.prep_exec(r"DELETE FROM channels WHERE name = :name", params!{"name" => name.as_str().unwrap()}).await?; Ok(()) } pub async fn delete_channel(pool: &Pool, response: &mut Response, params: Value) { // make sure we have the right parameters provided if let Some(name) = params.get("name") { match db_delete_channel(pool, name).await { Ok(_) => *response.status_mut() = StatusCode::OK, Err(e) => { println!("delete_chanel sql error :\n{}", e); } } } else { *response.status_mut() = StatusCode::BAD_REQUEST; } }