diff --git a/Cargo.lock b/Cargo.lock index 98cbf77..19d885b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -958,9 +958,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ "cfg-if 1.0.0", ] diff --git a/src/popular.rs b/src/popular.rs index 08399be..e0f22c5 100644 --- a/src/popular.rs +++ b/src/popular.rs @@ -8,7 +8,7 @@ use subreddit::{posts, Post}; #[path = "utils.rs"] mod utils; -use utils::Params; +use utils::{ErrorTemplate, Params}; // STRUCTS #[derive(Template)] @@ -33,16 +33,27 @@ async fn render(sub_name: String, sort: Option, ends: (Option, O }, }; - let items = posts(url).await?; + let items_result = posts(url).await; - let s = PopularTemplate { - posts: items.0, - sort: sorting, - ends: (before, items.1), + if items_result.is_err() { + let s = ErrorTemplate { + message: items_result.err().unwrap().to_string(), + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) + } else { + let items = items_result.unwrap(); + + let s = PopularTemplate { + posts: items.0, + sort: sorting, + ends: (before, items.1), + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) } - .render() - .unwrap(); - Ok(HttpResponse::Ok().content_type("text/html").body(s)) } // SERVICES diff --git a/src/post.rs b/src/post.rs index 20c69f0..dc62699 100644 --- a/src/post.rs +++ b/src/post.rs @@ -6,7 +6,7 @@ use pulldown_cmark::{html, Options, Parser}; #[path = "utils.rs"] mod utils; -use utils::{request, val, Comment, Flair, Params, Post}; +use utils::{request, val, Comment, ErrorTemplate, Flair, Params, Post}; // STRUCTS #[derive(Template)] @@ -19,20 +19,26 @@ struct PostTemplate { async fn render(id: String, sort: String) -> Result { println!("id: {}", id); - let post: Post = fetch_post(&id).await?; - let comments: Vec = fetch_comments(id, &sort).await?; + let post = fetch_post(&id).await; + let comments = fetch_comments(id, &sort).await; - let s = PostTemplate { - comments: comments, - post: post, - sort: sort, + if post.is_err() || comments.is_err() { + let s = ErrorTemplate { + message: post.err().unwrap().to_string(), + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) + } else { + let s = PostTemplate { + comments: comments.unwrap(), + post: post.unwrap(), + sort: sort, + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) } - .render() - .unwrap(); - - // println!("{}", s); - - Ok(HttpResponse::Ok().content_type("text/html").body(s)) } // SERVICES @@ -86,12 +92,20 @@ async fn markdown_to_html(md: &str) -> String { } // POSTS -async fn fetch_post(id: &String) -> Result { +async fn fetch_post(id: &String) -> Result { // Build the Reddit JSON API url let url: String = format!("https://reddit.com/{}.json", id); // Send a request to the url, receive JSON in response - let res = request(url).await; + let req = request(url).await; + + // If the Reddit API returns an error, exit this function + if req.is_err() { + return Err(req.err().unwrap()); + } + + // Otherwise, grab the JSON output from the request + let res = req.unwrap(); let post_data: &serde_json::Value = &res[0]["data"]["children"][0]; @@ -122,12 +136,20 @@ async fn fetch_post(id: &String) -> Result { } // COMMENTS -async fn fetch_comments(id: String, sort: &String) -> Result> { +async fn fetch_comments(id: String, sort: &String) -> Result, &'static str> { // Build the Reddit JSON API url let url: String = format!("https://reddit.com/{}.json?sort={}", id, sort); // Send a request to the url, receive JSON in response - let res = request(url).await; + let req = request(url).await; + + // If the Reddit API returns an error, exit this function + if req.is_err() { + return Err(req.err().unwrap()); + } + + // Otherwise, grab the JSON output from the request + let res = req.unwrap(); let comment_data = res[1]["data"]["children"].as_array().unwrap(); diff --git a/src/subreddit.rs b/src/subreddit.rs index 41041a3..e5bed96 100644 --- a/src/subreddit.rs +++ b/src/subreddit.rs @@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc}; #[path = "utils.rs"] mod utils; -pub use utils::{request, val, Flair, Params, Post, Subreddit}; +pub use utils::{request, val, ErrorTemplate, Flair, Params, Post, Subreddit}; // STRUCTS #[derive(Template)] @@ -37,33 +37,53 @@ pub async fn render(sub_name: String, sort: Option, ends: (Option"#, sub.icon) + if sub_result.is_err() || items_result.is_err() { + let s = ErrorTemplate { + message: sub_result.err().unwrap().to_string(), + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) } else { - String::new() - }; + let mut sub = sub_result.unwrap(); + let items = items_result.unwrap(); - let s = SubredditTemplate { - sub: sub, - posts: items.0, - sort: sorting, - ends: (before, items.1), + sub.icon = if sub.icon != "" { + format!(r#""#, sub.icon) + } else { + String::new() + }; + + let s = SubredditTemplate { + sub: sub, + posts: items.0, + sort: sorting, + ends: (before, items.1), + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) } - .render() - .unwrap(); - Ok(HttpResponse::Ok().content_type("text/html").body(s)) } // SUBREDDIT -async fn subreddit(sub: &String) -> Result { +async fn subreddit(sub: &String) -> Result { // Build the Reddit JSON API url let url: String = format!("https://www.reddit.com/r/{}/about.json", sub); // Send a request to the url, receive JSON in response - let res = request(url).await; + let req = request(url).await; + + // If the Reddit API returns an error, exit this function + if req.is_err() { + return Err(req.err().unwrap()); + } + + // Otherwise, grab the JSON output from the request + let res = req.unwrap(); let icon: String = String::from(res["data"]["community_icon"].as_str().unwrap()); //val(&data, "community_icon"); let icon_split: std::str::Split<&str> = icon.split("?"); @@ -80,9 +100,17 @@ async fn subreddit(sub: &String) -> Result { } // POSTS -pub async fn posts(url: String) -> Result<(Vec, String)> { +pub async fn posts(url: String) -> Result<(Vec, String), &'static str> { // Send a request to the url, receive JSON in response - let res = request(url).await; + let req = request(url).await; + + // If the Reddit API returns an error, exit this function + if req.is_err() { + return Err(req.err().unwrap()); + } + + // Otherwise, grab the JSON output from the request + let res = req.unwrap(); // Fetch the list of posts from the JSON response let post_list = res["data"]["children"].as_array().unwrap(); diff --git a/src/user.rs b/src/user.rs index 7409475..29ea770 100644 --- a/src/user.rs +++ b/src/user.rs @@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc}; #[path = "utils.rs"] mod utils; -use utils::{nested_val, request, val, Flair, Params, Post, User}; +use utils::{nested_val, request, val, ErrorTemplate, Flair, Params, Post, User}; // STRUCTS #[derive(Template)] @@ -17,11 +17,26 @@ struct UserTemplate { } async fn render(username: String, sort: String) -> Result { - let user: User = user(&username).await; - let posts: Vec = posts(username, &sort).await; + let user = user(&username).await; + let posts = posts(username, &sort).await; - let s = UserTemplate { user: user, posts: posts, sort: sort }.render().unwrap(); - Ok(HttpResponse::Ok().content_type("text/html").body(s)) + if user.is_err() || posts.is_err() { + let s = ErrorTemplate { + message: user.err().unwrap().to_string(), + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) + } else { + let s = UserTemplate { + user: user.unwrap(), + posts: posts.unwrap(), + sort: sort, + } + .render() + .unwrap(); + Ok(HttpResponse::Ok().content_type("text/html").body(s)) + } } // SERVICES @@ -34,29 +49,46 @@ async fn page(web::Path(username): web::Path, params: web::Query } // USER -async fn user(name: &String) -> User { +async fn user(name: &String) -> Result { // Build the Reddit JSON API url let url: String = format!("https://www.reddit.com/user/{}/about.json", name); // Send a request to the url, receive JSON in response - let res = request(url).await; + let req = request(url).await; - User { + // If the Reddit API returns an error, exit this function + if req.is_err() { + return Err(req.err().unwrap()); + } + + // Otherwise, grab the JSON output from the request + let res = req.unwrap(); + + // Parse the JSON output into a User struct + Ok(User { name: name.to_string(), icon: nested_val(&res, "subreddit", "icon_img").await, karma: res["data"]["total_karma"].as_i64().unwrap(), banner: nested_val(&res, "subreddit", "banner_img").await, description: nested_val(&res, "subreddit", "public_description").await, - } + }) } // POSTS -async fn posts(sub: String, sort: &String) -> Vec { +async fn posts(sub: String, sort: &String) -> Result, &'static str> { // Build the Reddit JSON API url let url: String = format!("https://www.reddit.com/u/{}/.json?sort={}", sub, sort); // Send a request to the url, receive JSON in response - let res = request(url).await; + let req = request(url).await; + + // If the Reddit API returns an error, exit this function + if req.is_err() { + return Err(req.err().unwrap()); + } + + // Otherwise, grab the JSON output from the request + let res = req.unwrap(); let post_list = res["data"]["children"].as_array().unwrap(); @@ -93,5 +125,5 @@ async fn posts(sub: String, sort: &String) -> Vec { }); } - posts + Ok(posts) } diff --git a/src/utils.rs b/src/utils.rs index 647cfc9..0cea899 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,6 @@ +// +// STRUCTS +// #[allow(dead_code)] // Post flair with text, background color and foreground color pub struct Flair(pub String, pub String, pub String); @@ -52,6 +55,17 @@ pub struct Params { pub before: Option, } +// Error template +#[derive(askama::Template)] +#[template(path = "error.html", escape = "none")] +pub struct ErrorTemplate { + pub message: String, +} + +// +// JSON PARSING +// + #[allow(dead_code)] // val() function used to parse JSON from Reddit APIs pub async fn val(j: &serde_json::Value, k: &str) -> String { @@ -64,9 +78,13 @@ pub async fn nested_val(j: &serde_json::Value, n: &str, k: &str) -> String { String::from(j["data"][n][k].as_str().unwrap()) } +// +// NETWORKING +// + // Make a request to a Reddit API and parse the JSON response #[allow(dead_code)] -pub async fn request(url: String) -> serde_json::Value { +pub async fn request(url: String) -> Result { // --- actix-web::client --- // let client = actix_web::client::Client::default(); // let res = client @@ -86,10 +104,22 @@ pub async fn request(url: String) -> serde_json::Value { // let body = res.body_string().await.unwrap(); // --- reqwest --- - let resp: String = reqwest::get(&url).await.unwrap().text().await.unwrap(); + let res = reqwest::get(&url).await.unwrap(); + // Read the status from the response + let success = res.status().is_success(); + // Read the body of the response + let body = res.text().await.unwrap(); // Parse the response from Reddit as JSON - let json: serde_json::Value = serde_json::from_str(resp.as_str()).expect("Failed to parse JSON"); + let json: serde_json::Value = serde_json::from_str(body.as_str()).unwrap_or(serde_json::Value::Null); - json + if !success { + Ok(json) + } else if json == serde_json::Value::Null { + println!("! {} - {}", url, "Failed to parse page JSON data"); + Err("Failed to parse page JSON data") + } else { + println!("! {} - {}", url, "Page not found"); + Err("Page not found") + } } diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..7f0830f --- /dev/null +++ b/templates/error.html @@ -0,0 +1,18 @@ + + + + Page not found. + + + + + +
+ libreddit. + GITHUB +
+
+

{{ message }}

+
+ + \ No newline at end of file