Skip to content
This repository was archived by the owner on Sep 8, 2019. It is now read-only.

Commit 7b08854

Browse files
Implemented get, create, and search with error handling.
0 parents  commit 7b08854

File tree

10 files changed

+325
-0
lines changed

10 files changed

+325
-0
lines changed

Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "web_dev"
3+
version = "0.1.0"
4+
authors = ["Tim <[email protected]>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
rouille = "3.0.0"
9+
diesel = { version = "1.3.3", features = ["mysql"] }
10+
dotenv = "0.13.0"
11+
serde = { version = "1.0", features = ["derive"]}
12+
serde_json = "1.0"
13+
log = "0.4"
14+
simplelog = "0.5"

diesel.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
# For documentation on how to configure this file,
3+
# see diesel.rs/guides/configuring-diesel-cli
4+
5+
[print_schema]
6+
file = "src/schema.rs

rest_api/delete.http

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DELETE http://localhost:8000/users/6
2+
3+
###

rest_api/get_all.http

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
GET http://localhost:8000/users
2+
3+
###

rest_api/get_search.http

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
GET http://localhost:8000/users?first_name=timothy&last_name=hollabaut
2+
3+
###

rest_api/insert.http

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
POST http://localhost:8000/users/create
2+
3+
{ "first_name": "timothy", "last_name": "hollabaut", "email": "[email protected]", "banner_id": 916246947 }
4+
5+
###

src/errors.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::convert::From;
2+
use std::error::Error;
3+
use std::fmt;
4+
5+
#[derive(Debug, Copy, Clone)]
6+
pub enum WebdevErrorKind {
7+
Database,
8+
Format,
9+
NotFound,
10+
}
11+
12+
#[derive(Debug)]
13+
pub struct WebdevError {
14+
kind: WebdevErrorKind,
15+
source: Option<Box<dyn Error>>,
16+
}
17+
18+
impl std::fmt::Display for WebdevError {
19+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20+
match self.kind {
21+
WebdevErrorKind::Database => write!(f, "Database error!"),
22+
WebdevErrorKind::Format => write!(f, "Format error!"),
23+
WebdevErrorKind::NotFound => write!(f, "Not found!"),
24+
}
25+
}
26+
}
27+
28+
impl Error for WebdevError {
29+
fn source(&self) -> Option<&(dyn Error + 'static)> {
30+
match self.source {
31+
Some(ref e) => Some(e.as_ref()),
32+
None => None,
33+
}
34+
}
35+
}
36+
37+
impl WebdevError {
38+
pub fn new(kind: WebdevErrorKind) -> WebdevError {
39+
WebdevError { kind, source: None }
40+
}
41+
42+
pub fn with_source(kind: WebdevErrorKind, source: Box<dyn Error>) -> WebdevError {
43+
WebdevError {
44+
kind,
45+
source: Some(source),
46+
}
47+
}
48+
49+
pub fn kind(&self) -> WebdevErrorKind {
50+
return self.kind;
51+
}
52+
}
53+
54+
impl From<diesel::result::Error> for WebdevError {
55+
fn from(d: diesel::result::Error) -> WebdevError {
56+
WebdevError::with_source(WebdevErrorKind::Database, Box::new(d))
57+
}
58+
}
59+
60+
impl From<serde_json::Error> for WebdevError {
61+
fn from(s: serde_json::Error) -> WebdevError {
62+
WebdevError::with_source(WebdevErrorKind::Format, Box::new(s))
63+
}
64+
}
65+
66+
impl From<std::num::ParseIntError> for WebdevError {
67+
fn from(s: std::num::ParseIntError) -> WebdevError {
68+
WebdevError::with_source(WebdevErrorKind::Format, Box::new(s))
69+
}
70+
}
71+
72+
impl From<WebdevError> for rouille::Response {
73+
fn from(e: WebdevError) -> rouille::Response {
74+
match e.kind() {
75+
WebdevErrorKind::NotFound => {
76+
rouille::Response::text(e.to_string()).with_status_code(404)
77+
}
78+
WebdevErrorKind::Format => rouille::Response::text(e.to_string()).with_status_code(400),
79+
WebdevErrorKind::Database => {
80+
rouille::Response::text(e.to_string()).with_status_code(500)
81+
}
82+
}
83+
}
84+
}

src/main.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#[macro_use]
2+
extern crate diesel;
3+
4+
use std::env;
5+
use std::error::Error;
6+
use std::sync::Mutex;
7+
8+
use log::debug;
9+
use log::error;
10+
use log::info;
11+
use log::trace;
12+
use log::warn;
13+
14+
use diesel::expression::AsExpression;
15+
use diesel::prelude::*;
16+
use diesel::query_builder::AsQuery;
17+
use diesel::MysqlConnection;
18+
19+
use rouille::router;
20+
21+
use serde;
22+
use serde_json;
23+
24+
use dotenv::dotenv;
25+
26+
use self::errors::WebdevError;
27+
use self::errors::WebdevErrorKind;
28+
use self::schema::users;
29+
30+
mod errors;
31+
mod models;
32+
mod schema;
33+
34+
fn main() {
35+
dotenv().ok();
36+
37+
simplelog::TermLogger::init(simplelog::LevelFilter::Trace, simplelog::Config::default())
38+
.unwrap();
39+
40+
info!("Connecting to database");
41+
42+
let database_url = env::var("DATABASE_URL").expect("DATABSE_URL needs to be set");
43+
44+
debug!("Connecting to {}", database_url);
45+
46+
let connection = match MysqlConnection::establish(&database_url) {
47+
Ok(c) => c,
48+
Err(e) => {
49+
error!("Could not connect to database: {}", e);
50+
return;
51+
}
52+
};
53+
54+
debug!("Connected to database");
55+
56+
let connection_mutex = Mutex::new(connection);
57+
58+
info!("Starting server on 0.0.0.0:8000");
59+
60+
rouille::start_server("0.0.0.0:8000", move |request| {
61+
debug!(
62+
"Handling request {} {} from {}",
63+
request.method(),
64+
request.url(),
65+
request.remote_addr()
66+
);
67+
68+
let current_connection = match connection_mutex.lock() {
69+
Ok(c) => c,
70+
Err(_e) => {
71+
error!("Could not lock database");
72+
return rouille::Response::from(WebdevError::new(WebdevErrorKind::Database));
73+
}
74+
};
75+
76+
let response = handle_request(request, &current_connection);
77+
78+
match response {
79+
Ok(json) => {
80+
if let Some(j) = json {
81+
rouille::Response::json(&j)
82+
} else {
83+
rouille::Response::empty_204()
84+
}
85+
}
86+
Err(e) => {
87+
if let Some(err_source) = e.source() {
88+
error!("Error processing request: {}", err_source);
89+
} else {
90+
error!("Error processing request");
91+
}
92+
93+
rouille::Response::from(e)
94+
}
95+
}
96+
});
97+
}
98+
99+
fn handle_request(
100+
request: &rouille::Request,
101+
database_connection: &MysqlConnection,
102+
) -> Result<Option<String>, WebdevError> {
103+
router!(request,
104+
(GET) (/users) => {
105+
handle_get(
106+
request.get_param("first_name"),
107+
request.get_param("last_name"),
108+
if let Some(p) = request.get_param("banner_id") { Some(p.parse()?) } else { None },
109+
request.get_param("email"),
110+
database_connection
111+
).map(|s| Some(s))
112+
},
113+
(POST) (/users/create) => {
114+
let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?;
115+
let new_user: models::NewUser = serde_json::from_reader(request_body)?;
116+
117+
handle_insert(new_user, database_connection).map(|s| Some(s))
118+
},
119+
(DELETE) (/users/{id: u64}) => {
120+
handle_delete(id, database_connection).map(|_| None)
121+
},
122+
_ => Err(WebdevError::new(WebdevErrorKind::NotFound))
123+
)
124+
}
125+
126+
fn handle_get(
127+
first_name_filter: Option<String>,
128+
last_name_filter: Option<String>,
129+
banner_id_filter: Option<u32>,
130+
email_filter: Option<String>,
131+
database_connection: &MysqlConnection,
132+
) -> Result<String, WebdevError> {
133+
let mut users_query = users::table.as_query().into_boxed();
134+
135+
if let Some(p) = first_name_filter {
136+
users_query = users_query.filter(users::first_name.eq(p));
137+
}
138+
139+
if let Some(p) = last_name_filter {
140+
users_query = users_query.filter(users::last_name.eq(p));
141+
}
142+
143+
if let Some(p) = banner_id_filter {
144+
users_query = users_query.filter(users::banner_id.eq(p));
145+
}
146+
147+
if let Some(p) = email_filter {
148+
users_query = users_query.filter(users::email.eq(p));
149+
}
150+
151+
let all_users = users_query.load::<models::User>(database_connection)?;
152+
Ok(serde_json::to_string(&all_users)?)
153+
}
154+
155+
fn handle_insert(
156+
new_user: models::NewUser,
157+
database_connection: &MysqlConnection,
158+
) -> Result<String, WebdevError> {
159+
160+
diesel::insert_into(users::table)
161+
.values(new_user)
162+
.execute(database_connection)?;
163+
164+
let inserted_user = users::table
165+
.filter(diesel::dsl::sql("id = LAST_INSERT_ID()"))
166+
.load::<models::User>(database_connection)?;
167+
168+
Ok(serde_json::to_string(&inserted_user)?)
169+
}
170+
171+
fn handle_delete(id: u64, database_connection: &MysqlConnection) -> Result<(), WebdevError> {
172+
diesel::delete(users::table.filter(users::id.eq(id))).execute(database_connection)?;
173+
174+
Ok(())
175+
}

src/models.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use diesel::Queryable;
2+
use serde::Deserialize;
3+
use serde::Serialize;
4+
5+
use super::schema::users;
6+
7+
#[derive(Queryable, Serialize, Deserialize)]
8+
pub struct User {
9+
pub id: u64,
10+
pub first_name: String,
11+
pub last_name: String,
12+
pub banner_id: u32,
13+
pub email: Option<String>,
14+
}
15+
16+
#[derive(Insertable, Serialize, Deserialize)]
17+
#[table_name = "users"]
18+
pub struct NewUser {
19+
pub first_name: String,
20+
pub last_name: String,
21+
pub banner_id: u32,
22+
pub email: Option<String>,
23+
}

src/schema.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
table! {
2+
users (id) {
3+
id -> Unsigned<Bigint>,
4+
first_name -> Varchar,
5+
last_name -> Varchar,
6+
banner_id -> Unsigned<Integer>,
7+
email -> Nullable<Varchar>,
8+
}
9+
}

0 commit comments

Comments
 (0)