291 lines
9.7 KiB
Rust
291 lines
9.7 KiB
Rust
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};
|
|
use crate::common;
|
|
|
|
#[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,
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Primary way of interpretting sql data on our channels table
|
|
pub struct Channel {
|
|
pub id: u64,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub 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<VarChar>, 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<i32>,
|
|
* "name": "<some name here>",
|
|
* "description": Option<"<description here>">,
|
|
* "kind": kind<i32>
|
|
* }
|
|
*/
|
|
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<Channel> which we can JSON'ify later and use as a response
|
|
*/
|
|
async fn get_channels_vec(conn: Conn) -> Result<Vec<Channel>, 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<VarChar>, 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<Body>) {
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
|
|
async fn insert_channel(pool: &Pool, name: &str, desc: &str, kind: i64) -> Result<(), Error>{
|
|
let conn = pool.get_conn().await?;
|
|
conn.prep_exec(
|
|
"INSERT INTO channels (name, description, kind) VALUES (:name, :description, :kind)",
|
|
params!{"name" => name, "kind" => kind, "description" => desc}).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn create_channel(pool: &Pool, response: &mut Response<Body>, params: Value) {
|
|
/*
|
|
* Create a channel base on a few parameters that may or may not be there
|
|
*/
|
|
// Theres an extra un-needed unwrap to be cut out from this proc
|
|
// specifically with the desc parameter
|
|
let req_params: (Option<&str>, Option<&str>, Option<i64>) =
|
|
match (params.get("name"), params.get("description"), params.get("kind")) {
|
|
(Some(name), Some(desc), Some(kind)) => (name.as_str(), desc.as_str(), kind.as_i64()),
|
|
(Some(name), None, Some(kind)) => (name.as_str(), Some("No Description"), kind.as_i64()),
|
|
_ => (None, None, None)
|
|
};
|
|
match req_params {
|
|
(Some(name), Some(desc), Some(kind)) => {
|
|
match insert_channel(pool, name, desc, kind).await {
|
|
// Server Errors are generally _ok_ to reveal in body I suppose
|
|
Err(Error::Server(se)) => {
|
|
common::db_err_response_body(response, se);
|
|
//*response.status_mut() = StatusCode::BAD_REQUEST;
|
|
//let b = format!("Server code: {}\nServer Message:{}", se.code, se.message);
|
|
//*response.body_mut() = Body::from(b);
|
|
},
|
|
// generic errors get a 500
|
|
Err(_) => {
|
|
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
|
}
|
|
// Nothing to do when things go right
|
|
_ => {}
|
|
}
|
|
},
|
|
// basically one of the parameter gets failed so we bail on all of this
|
|
_ => *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<Body>, 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) => {
|
|
*response.body_mut() = Body::from(format!("delete_chanel sql error :\n{}", e));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
*response.status_mut() = StatusCode::BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod channels_tests {
|
|
use crate::testing::{get_pool, hyper_resp};
|
|
use serde_json::Value;
|
|
use hyper::StatusCode;
|
|
const DUMMY_TRANSIENT_CHANNEL: &'static str = "sample channel";
|
|
|
|
#[tokio::test]
|
|
async fn list_all_channels_good() {
|
|
// Generation of data
|
|
let p = get_pool();
|
|
let mut resp = hyper_resp();
|
|
// @params: none
|
|
// Collection of data
|
|
super::list_channels(&p, &mut resp).await;
|
|
|
|
// Analysis
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
println!("list_all_channels_good : \t{:?}", resp.body());
|
|
let _ = p.disconnect().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_and_create_channel_good() {
|
|
use chrono::Utc;
|
|
let p = get_pool();
|
|
let mut resp = hyper_resp();
|
|
// @params: name + kind + [description]
|
|
let cname_id = Utc::now();
|
|
let params: Value = serde_json::from_str(&format!(r#"
|
|
{{
|
|
"name": "{}-{}",
|
|
"kind": 2,
|
|
"description": "some random bs"
|
|
}}
|
|
"#, DUMMY_TRANSIENT_CHANNEL, cname_id)).unwrap();
|
|
|
|
super::create_channel(&p, &mut resp, params).await;
|
|
|
|
println!("CREATE CHANNEL: {:?}", resp.body());
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
|
|
|
|
// clean up and hopefully delete the channel properly
|
|
let mut resp_delete = hyper_resp();
|
|
let params_delete: Value = serde_json::from_str(&format!(r#"
|
|
{{
|
|
"name": "{}-{}"
|
|
}}
|
|
"#, DUMMY_TRANSIENT_CHANNEL, cname_id)).unwrap();
|
|
println!("Parameters: {}", params_delete);
|
|
super::delete_channel(&p, &mut resp_delete, params_delete).await;
|
|
|
|
println!("Body: {:?}", resp.body());
|
|
let _ = p.disconnect().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_channel_missing_name() {
|
|
let p = get_pool();
|
|
let mut resp = hyper_resp();
|
|
// this endpoint is super lenient for some reason btw
|
|
let param: Value = serde_json::from_str("{}").expect("JSON is not written correctly");
|
|
|
|
super::delete_channel(&p, &mut resp, param).await;
|
|
|
|
assert_eq!(StatusCode::BAD_REQUEST, resp.status());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delet_channel_non_real_channel() {
|
|
let p = get_pool();
|
|
let mut resp = hyper_resp();
|
|
// this endpoint is super lenient for some reason btw
|
|
let param: Value = serde_json::from_str(r#"{
|
|
"name": "this channel doesn't exist"
|
|
}"#).expect("JSON is not written correctly");
|
|
|
|
super::delete_channel(&p, &mut resp, param).await;
|
|
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
}
|
|
}
|