Skip to content

Commit c35c02d

Browse files
committed
feat: extract route-specific logic from mod.rs, add 2 custom error variants, better error handling
Route-specific logic has been extracted from controllers/mod.rs into the corresponding controllers 2 new CustomError variants: Notfound(for when a route is correct, but no data is returned) and ServiceUnavailable(for when a connection to the database cannot be established), both with a customized error message that includes the error based on status code and details given by the user The DB name is now set from an env variable, DATABASE_NAME, and all env variable logic has been moved to main.rs, as well as critical error handling(panicking)
1 parent 14df2fc commit c35c02d

File tree

8 files changed

+177
-88
lines changed

8 files changed

+177
-88
lines changed
Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,49 @@
11
use crate::{
2-
controllers::MongoRepo, custom::error::CustomError, models::contactinfo_model::Contactinfo,
2+
controllers::{MongoRepo, DB_CON_ERR},
3+
custom::error::CustomError,
4+
models::contactinfo_model::Contactinfo,
35
};
6+
use mongodb::{bson::doc, options::FindOptions, sync::Collection};
47
use rocket::{http::Status, serde::json::Json, State};
58

9+
impl MongoRepo {
10+
pub fn get_contactinfo(&self) -> Result<Vec<Contactinfo>, String> {
11+
let col: Collection<Contactinfo> = self.db.collection("contactinfo");
12+
let cursors = match col
13+
.find(
14+
None,
15+
FindOptions::builder()
16+
.projection(doc! {"_id":0})
17+
.sort(doc! {"_id": 1})
18+
.build(),
19+
)
20+
.ok()
21+
{
22+
Some(c) => c,
23+
None => return Err(DB_CON_ERR.to_string()),
24+
};
25+
let contactinfo = cursors.map(|doc| doc.unwrap()).collect();
26+
Ok(contactinfo)
27+
}
28+
}
29+
630
#[get("/contactinfo")]
731
pub fn get_contactinfo(db: &State<MongoRepo>) -> Result<Json<Vec<Contactinfo>>, CustomError> {
832
let contactinfo = db.get_contactinfo();
933
match contactinfo {
1034
Ok(contactinfo) => {
1135
if contactinfo.is_empty() {
12-
Err(CustomError::Default(Status::NotFound))
36+
Err(CustomError::NotFound(
37+
Status::NotFound,
38+
"contactinfo".to_string(),
39+
))
1340
} else {
1441
Ok(Json(contactinfo))
1542
}
1643
}
17-
Err(_) => Err(CustomError::Default(Status::InternalServerError)),
44+
Err(error) => Err(CustomError::ServiceUnavailable(
45+
Status::ServiceUnavailable,
46+
error,
47+
)),
1848
}
1949
}
Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,46 @@
1-
use crate::{controllers::MongoRepo, custom::error::CustomError, models::location_model::Location};
1+
use crate::{
2+
controllers::{MongoRepo, DB_CON_ERR},
3+
custom::error::CustomError,
4+
models::location_model::Location,
5+
};
6+
use mongodb::{bson::doc, options::FindOptions, sync::Collection};
27
use rocket::{http::Status, serde::json::Json, State};
38

9+
impl MongoRepo {
10+
pub fn get_locations(&self) -> Result<Vec<Location>, String> {
11+
let col: Collection<Location> = self.db.collection("locations");
12+
let cursors = match col
13+
.find(
14+
None,
15+
FindOptions::builder().projection(doc! {"_id":0}).build(),
16+
)
17+
.ok()
18+
{
19+
Some(c) => c,
20+
None => return Err(DB_CON_ERR.to_string()),
21+
};
22+
let locations = cursors.map(|doc| doc.unwrap()).collect();
23+
Ok(locations)
24+
}
25+
}
26+
427
#[get("/locations")]
528
pub fn get_locations(db: &State<MongoRepo>) -> Result<Json<Vec<Location>>, CustomError> {
629
let locations = db.get_locations();
730
match locations {
831
Ok(locations) => {
932
if locations.is_empty() {
10-
Err(CustomError::Default(Status::NotFound))
33+
Err(CustomError::NotFound(
34+
Status::NotFound,
35+
"locations".to_string(),
36+
))
1137
} else {
1238
Ok(Json(locations))
1339
}
1440
}
15-
Err(_) => Err(CustomError::Default(Status::InternalServerError)),
41+
Err(error) => Err(CustomError::ServiceUnavailable(
42+
Status::ServiceUnavailable,
43+
error,
44+
)),
1645
}
1746
}

src/controllers/mod.rs

Lines changed: 8 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,85 +3,21 @@ pub mod location_controller;
33
pub mod notification_controller;
44
pub mod service_controller;
55

6-
use std::env;
7-
extern crate dotenv;
8-
use dotenv::dotenv;
9-
10-
use crate::models::{
11-
contactinfo_model::Contactinfo, location_model::Location, notification_model::Notification,
12-
service_model::Service,
13-
};
146
use mongodb::{
15-
bson::{doc, extjson::de::Error},
16-
options::FindOptions,
17-
sync::{Client, Collection, Database},
7+
error::Error,
8+
sync::{Client, Database},
189
};
1910

11+
pub const DB_CON_ERR: &str = "Couldn't connect to database";
12+
2013
pub struct MongoRepo {
2114
db: Database,
2215
}
2316

2417
impl MongoRepo {
25-
pub fn init() -> Self {
26-
dotenv().ok();
27-
let uri = match env::var("DATABASE_URL") {
28-
Ok(v) => v.to_string(),
29-
Err(_) => format!("Error loading env variable"),
30-
};
31-
let client = Client::with_uri_str(uri).unwrap();
32-
let db = client.database("obs");
33-
MongoRepo { db }
34-
}
35-
36-
pub fn get_locations(&self) -> Result<Vec<Location>, Error> {
37-
let col: Collection<Location> = self.db.collection("locations");
38-
let cursors = col
39-
.find(
40-
None,
41-
FindOptions::builder().projection(doc! {"_id":0}).build(),
42-
)
43-
.ok()
44-
.expect("Error fetching locations");
45-
let locations = cursors.map(|doc| doc.unwrap()).collect();
46-
Ok(locations)
47-
}
48-
49-
pub fn get_contactinfo(&self) -> Result<Vec<Contactinfo>, Error> {
50-
let col: Collection<Contactinfo> = self.db.collection("contactinfo");
51-
let cursors = col
52-
.find(
53-
None,
54-
FindOptions::builder()
55-
.projection(doc! {"_id":0})
56-
.sort(doc! {"_id": 1})
57-
.build(),
58-
)
59-
.ok()
60-
.expect("Error fetching contactinfo");
61-
let contactinfo = cursors.map(|doc| doc.unwrap()).collect();
62-
Ok(contactinfo)
63-
}
64-
65-
pub fn get_services(&self) -> Result<Vec<Service>, Error> {
66-
let col: Collection<Service> = self.db.collection("services");
67-
let cursors = col
68-
.find(
69-
None,
70-
FindOptions::builder().projection(doc! {"_id":0}).build(),
71-
)
72-
.ok()
73-
.expect("Error fetching services");
74-
let services = cursors.map(|doc| doc.unwrap()).collect();
75-
Ok(services)
76-
}
77-
78-
pub fn get_notifications(&self) -> Result<Vec<Notification>, Error> {
79-
let col: Collection<Notification> = self.db.collection("notifications");
80-
let cursors = col
81-
.find(None, None)
82-
.ok()
83-
.expect("Error fetching notifications");
84-
let notifications = cursors.map(|doc| doc.unwrap()).collect();
85-
Ok(notifications)
18+
pub fn init(db_name: String, uri: String) -> Result<Self, Error> {
19+
let client = Client::with_uri_str(uri)?;
20+
let db = client.database(&db_name);
21+
Ok(MongoRepo { db })
8622
}
8723
}
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
11
use crate::{
2-
controllers::MongoRepo, custom::error::CustomError, models::notification_model::Notification,
2+
controllers::{MongoRepo, DB_CON_ERR},
3+
custom::error::CustomError,
4+
models::notification_model::Notification,
35
};
6+
use mongodb::{bson::doc, sync::Collection};
47
use rocket::{http::Status, serde::json::Json, State};
58

9+
impl MongoRepo {
10+
pub fn get_notifications(&self) -> Result<Vec<Notification>, String> {
11+
let col: Collection<Notification> = self.db.collection("notifications");
12+
let cursors = match col.find(None, None).ok() {
13+
Some(c) => c,
14+
None => return Err(DB_CON_ERR.to_string()),
15+
};
16+
let notifications = cursors.map(|doc| doc.unwrap()).collect();
17+
Ok(notifications)
18+
}
19+
}
20+
621
#[get("/notifications")]
722
pub fn get_notifications(db: &State<MongoRepo>) -> Result<Json<Vec<Notification>>, CustomError> {
823
let notifications = db.get_notifications();
924
match notifications {
1025
Ok(notifications) => {
1126
if notifications.is_empty() {
12-
Err(CustomError::Default(Status::NotFound))
27+
Err(CustomError::NotFound(
28+
Status::NotFound,
29+
"notifications".to_string(),
30+
))
1331
} else {
1432
Ok(Json(notifications))
1533
}
1634
}
17-
Err(_) => Err(CustomError::Default(Status::InternalServerError)),
35+
Err(error) => Err(CustomError::ServiceUnavailable(
36+
Status::ServiceUnavailable,
37+
error,
38+
)),
1839
}
1940
}
Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,46 @@
1-
use crate::{controllers::MongoRepo, custom::error::CustomError, models::service_model::Service};
1+
use crate::{
2+
controllers::{MongoRepo, DB_CON_ERR},
3+
custom::error::CustomError,
4+
models::service_model::Service,
5+
};
6+
use mongodb::{bson::doc, options::FindOptions, sync::Collection};
27
use rocket::{http::Status, serde::json::Json, State};
38

9+
impl MongoRepo {
10+
pub fn get_services(&self) -> Result<Vec<Service>, String> {
11+
let col: Collection<Service> = self.db.collection("services");
12+
let cursors = match col
13+
.find(
14+
None,
15+
FindOptions::builder().projection(doc! {"_id":0}).build(),
16+
)
17+
.ok()
18+
{
19+
Some(c) => c,
20+
None => return Err(DB_CON_ERR.to_string()),
21+
};
22+
let services = cursors.map(|doc| doc.unwrap()).collect();
23+
Ok(services)
24+
}
25+
}
26+
427
#[get("/services")]
528
pub fn get_services(db: &State<MongoRepo>) -> Result<Json<Vec<Service>>, CustomError> {
629
let services = db.get_services();
730
match services {
831
Ok(services) => {
932
if services.is_empty() {
10-
Err(CustomError::Default(Status::NotFound))
33+
Err(CustomError::NotFound(
34+
Status::NotFound,
35+
"services".to_string(),
36+
))
1137
} else {
1238
Ok(Json(services))
1339
}
1440
}
15-
Err(_) => Err(CustomError::Default(Status::InternalServerError)),
41+
Err(error) => Err(CustomError::ServiceUnavailable(
42+
Status::ServiceUnavailable,
43+
error,
44+
)),
1645
}
1746
}

src/custom/error.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ pub struct Error {
2121

2222
#[derive(Debug, Clone)]
2323
pub enum CustomError {
24+
NotFound(Status, String),
25+
ServiceUnavailable(Status, String),
2426
Default(Status),
2527
}
2628

2729
impl CustomError {
2830
fn get_http_status(&self) -> Status {
2931
match self {
32+
CustomError::NotFound(status, _) => *status,
33+
CustomError::ServiceUnavailable(status, _) => *status,
3034
CustomError::Default(status) => *status,
3135
}
3236
}
@@ -45,10 +49,32 @@ impl<'r> Responder<'r, 'static> for CustomError {
4549
error: Error {
4650
status: status.code,
4751
message: status.reason().unwrap().to_string(),
48-
cat: format!("https://http.cat/{}.jpg", status),
52+
cat: format!("https://http.cat/{}.jpg", status.code),
4953
},
5054
})
5155
.unwrap(),
56+
CustomError::NotFound(status, details) => serde::json::to_string(&ErrorResponse {
57+
error: Error {
58+
status: status.code,
59+
message: format!(
60+
"{} (No available {})",
61+
status.reason().unwrap().to_string(),
62+
details
63+
),
64+
cat: format!("https://http.cat/{}.jpg", status.code),
65+
},
66+
})
67+
.unwrap(),
68+
CustomError::ServiceUnavailable(status, details) => {
69+
serde::json::to_string(&ErrorResponse {
70+
error: Error {
71+
status: status.code,
72+
message: format!("{} ({})", status.reason().unwrap().to_string(), details),
73+
cat: format!("https://http.cat/{}.jpg", status.code),
74+
},
75+
})
76+
.unwrap()
77+
}
5278
};
5379

5480
Response::build()

src/main.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ mod controllers;
22
mod custom;
33
mod models;
44

5+
use std::env;
6+
extern crate dotenv;
7+
use dotenv::dotenv;
8+
59
#[macro_use]
610
extern crate rocket;
711
use rocket::{http::Status, Request};
@@ -14,13 +18,27 @@ use crate::controllers::MongoRepo;
1418
use custom::error::CustomError;
1519

1620
#[catch(default)]
17-
fn default_catcher(status: Status, req: &Request) -> Option<CustomError> {
21+
fn default_catcher(status: Status, _: &Request) -> Option<CustomError> {
1822
Some(CustomError::Default(status))
1923
}
2024

2125
#[launch]
2226
fn rocket() -> _ {
23-
let db = MongoRepo::init();
27+
dotenv().ok();
28+
let uri = match env::var("DATABASE_URL") {
29+
Ok(v) => v.to_string(),
30+
Err(_) => panic!("Couldn't load env variable: DATABASE_URL"),
31+
};
32+
33+
let db_name = match env::var("DATABASE_NAME") {
34+
Ok(v) => v.to_string(),
35+
Err(_) => panic!("Couldn't load env variable: DATABASE_NAME"),
36+
};
37+
38+
let db = match MongoRepo::init(db_name, uri) {
39+
Ok(db) => db,
40+
Err(_) => panic!("Couldn't connect to database"),
41+
};
2442
rocket::build()
2543
.register("/", catchers![default_catcher])
2644
.manage(db)

src/models/notification_model.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
33

44
#[derive(Debug, Serialize, Deserialize)]
55
pub struct Notification {
6-
pub _id: Option<ObjectId>,
6+
pub _id: ObjectId,
77
pub notif_type: String,
88
pub date: String,
99
pub description: String,

0 commit comments

Comments
 (0)