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::{UBigInt, VarChar, Integer}; 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 type ChannelID = u64; pub struct Channel { id: u64, name: String, description: String, kind: ChannelType } struct InsertableChannel { name: String, description: 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: (u64, String, String, i32)) -> Channel { Channel { id: tup.0, name: tup.1, description: tup.2, 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": "", * "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, VarChar, Integer) = mysql_async::from_row(row); println!("{}, {}, {}, {}", id, name, desc, kind); 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>, desc_r: Option<&Value>) -> Result { match (name_r, kind_r) { (Some(name), Some(kind)) => { let desc: String = match desc_r { Some(d) => d.as_str().unwrap().to_string(), // if this fails burn the server None => "".into() }; let channel = InsertableChannel { name: name.to_string(), kind: ChannelType::from_i64_opt(kind.as_i64()), description: desc }; Ok(channel) } _ => { let x = Cow::from("Missing required parameters"); Err(Error::Other(x)) } } } async fn insert_channel(pool: &Pool, channel: InsertableChannel) -> Result<(), Error> { let conn = pool.get_conn().await?; conn.batch_exec( r"INSERT INTO channels (name, kind, description) VALUES (:name, :kind, :description)", params!{ "name" => channel.name, "kind" => channel.kind.as_i32(), "description" => channel.description, }).await?; Ok(()) } 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 desc_r = params.get("description"); let kind_r = params.get("kind"); match parse_insert_channel_params(name_r, kind_r, desc_r) { Ok(channel) => { match insert_channel(pool, channel).await { Ok(_) => *response.status_mut() = StatusCode::OK, Err(_) => *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR } }, Err(_) => *response.status_mut() = StatusCode::BAD_REQUEST } }