Error Pages

This commit is contained in:
spikecodes 2020-11-19 20:42:18 -08:00
parent 7a176c6804
commit 1960e8a0fb
7 changed files with 204 additions and 63 deletions

4
Cargo.lock generated
View File

@ -958,9 +958,9 @@ dependencies = [
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.8" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]

View File

@ -8,7 +8,7 @@ use subreddit::{posts, Post};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
use utils::Params; use utils::{ErrorTemplate, Params};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -33,7 +33,17 @@ async fn render(sub_name: String, sort: Option<String>, ends: (Option<String>, O
}, },
}; };
let items = posts(url).await?; let items_result = posts(url).await;
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 { let s = PopularTemplate {
posts: items.0, posts: items.0,
@ -44,6 +54,7 @@ async fn render(sub_name: String, sort: Option<String>, ends: (Option<String>, O
.unwrap(); .unwrap();
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
}
// SERVICES // SERVICES
#[get("/")] #[get("/")]

View File

@ -6,7 +6,7 @@ use pulldown_cmark::{html, Options, Parser};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
use utils::{request, val, Comment, Flair, Params, Post}; use utils::{request, val, Comment, ErrorTemplate, Flair, Params, Post};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -19,21 +19,27 @@ struct PostTemplate {
async fn render(id: String, sort: String) -> Result<HttpResponse> { async fn render(id: String, sort: String) -> Result<HttpResponse> {
println!("id: {}", id); println!("id: {}", id);
let post: Post = fetch_post(&id).await?; let post = fetch_post(&id).await;
let comments: Vec<Comment> = fetch_comments(id, &sort).await?; let comments = fetch_comments(id, &sort).await;
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 { let s = PostTemplate {
comments: comments, comments: comments.unwrap(),
post: post, post: post.unwrap(),
sort: sort, sort: sort,
} }
.render() .render()
.unwrap(); .unwrap();
// println!("{}", s);
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
}
// SERVICES // SERVICES
#[get("/{id}")] #[get("/{id}")]
@ -86,12 +92,20 @@ async fn markdown_to_html(md: &str) -> String {
} }
// POSTS // POSTS
async fn fetch_post(id: &String) -> Result<Post> { async fn fetch_post(id: &String) -> Result<Post, &'static str> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let url: String = format!("https://reddit.com/{}.json", id); let url: String = format!("https://reddit.com/{}.json", id);
// Send a request to the url, receive JSON in response // 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]; let post_data: &serde_json::Value = &res[0]["data"]["children"][0];
@ -122,12 +136,20 @@ async fn fetch_post(id: &String) -> Result<Post> {
} }
// COMMENTS // COMMENTS
async fn fetch_comments(id: String, sort: &String) -> Result<Vec<Comment>> { async fn fetch_comments(id: String, sort: &String) -> Result<Vec<Comment>, &'static str> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let url: String = format!("https://reddit.com/{}.json?sort={}", id, sort); let url: String = format!("https://reddit.com/{}.json?sort={}", id, sort);
// Send a request to the url, receive JSON in response // 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(); let comment_data = res[1]["data"]["children"].as_array().unwrap();

View File

@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
pub use utils::{request, val, Flair, Params, Post, Subreddit}; pub use utils::{request, val, ErrorTemplate, Flair, Params, Post, Subreddit};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -37,8 +37,19 @@ pub async fn render(sub_name: String, sort: Option<String>, ends: (Option<String
}, },
}; };
let mut sub: Subreddit = subreddit(&sub_name).await?; let sub_result = subreddit(&sub_name).await;
let items = posts(url).await?; let items_result = posts(url).await;
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 {
let mut sub = sub_result.unwrap();
let items = items_result.unwrap();
sub.icon = if sub.icon != "" { sub.icon = if sub.icon != "" {
format!(r#"<img class="subreddit_icon" src="{}">"#, sub.icon) format!(r#"<img class="subreddit_icon" src="{}">"#, sub.icon)
@ -56,14 +67,23 @@ pub async fn render(sub_name: String, sort: Option<String>, ends: (Option<String
.unwrap(); .unwrap();
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
}
// SUBREDDIT // SUBREDDIT
async fn subreddit(sub: &String) -> Result<Subreddit> { async fn subreddit(sub: &String) -> Result<Subreddit, &'static str> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/r/{}/about.json", sub); let url: String = format!("https://www.reddit.com/r/{}/about.json", sub);
// Send a request to the url, receive JSON in response // 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: String = String::from(res["data"]["community_icon"].as_str().unwrap()); //val(&data, "community_icon");
let icon_split: std::str::Split<&str> = icon.split("?"); let icon_split: std::str::Split<&str> = icon.split("?");
@ -80,9 +100,17 @@ async fn subreddit(sub: &String) -> Result<Subreddit> {
} }
// POSTS // POSTS
pub async fn posts(url: String) -> Result<(Vec<Post>, String)> { pub async fn posts(url: String) -> Result<(Vec<Post>, String), &'static str> {
// Send a request to the url, receive JSON in response // 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 // Fetch the list of posts from the JSON response
let post_list = res["data"]["children"].as_array().unwrap(); let post_list = res["data"]["children"].as_array().unwrap();

View File

@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc};
#[path = "utils.rs"] #[path = "utils.rs"]
mod utils; mod utils;
use utils::{nested_val, request, val, Flair, Params, Post, User}; use utils::{nested_val, request, val, ErrorTemplate, Flair, Params, Post, User};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -17,11 +17,26 @@ struct UserTemplate {
} }
async fn render(username: String, sort: String) -> Result<HttpResponse> { async fn render(username: String, sort: String) -> Result<HttpResponse> {
let user: User = user(&username).await; let user = user(&username).await;
let posts: Vec<Post> = posts(username, &sort).await; let posts = posts(username, &sort).await;
let s = UserTemplate { user: user, posts: posts, sort: sort }.render().unwrap(); 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)) 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 // SERVICES
@ -34,29 +49,46 @@ async fn page(web::Path(username): web::Path<String>, params: web::Query<Params>
} }
// USER // USER
async fn user(name: &String) -> User { async fn user(name: &String) -> Result<User, &'static str> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/user/{}/about.json", name); let url: String = format!("https://www.reddit.com/user/{}/about.json", name);
// Send a request to the url, receive JSON in response // 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(), name: name.to_string(),
icon: nested_val(&res, "subreddit", "icon_img").await, icon: nested_val(&res, "subreddit", "icon_img").await,
karma: res["data"]["total_karma"].as_i64().unwrap(), karma: res["data"]["total_karma"].as_i64().unwrap(),
banner: nested_val(&res, "subreddit", "banner_img").await, banner: nested_val(&res, "subreddit", "banner_img").await,
description: nested_val(&res, "subreddit", "public_description").await, description: nested_val(&res, "subreddit", "public_description").await,
} })
} }
// POSTS // POSTS
async fn posts(sub: String, sort: &String) -> Vec<Post> { async fn posts(sub: String, sort: &String) -> Result<Vec<Post>, &'static str> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let url: String = format!("https://www.reddit.com/u/{}/.json?sort={}", sub, sort); let url: String = format!("https://www.reddit.com/u/{}/.json?sort={}", sub, sort);
// Send a request to the url, receive JSON in response // 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(); let post_list = res["data"]["children"].as_array().unwrap();
@ -93,5 +125,5 @@ async fn posts(sub: String, sort: &String) -> Vec<Post> {
}); });
} }
posts Ok(posts)
} }

View File

@ -1,3 +1,6 @@
//
// STRUCTS
//
#[allow(dead_code)] #[allow(dead_code)]
// Post flair with text, background color and foreground color // Post flair with text, background color and foreground color
pub struct Flair(pub String, pub String, pub String); pub struct Flair(pub String, pub String, pub String);
@ -52,6 +55,17 @@ pub struct Params {
pub before: Option<String>, pub before: Option<String>,
} }
// Error template
#[derive(askama::Template)]
#[template(path = "error.html", escape = "none")]
pub struct ErrorTemplate {
pub message: String,
}
//
// JSON PARSING
//
#[allow(dead_code)] #[allow(dead_code)]
// val() function used to parse JSON from Reddit APIs // val() function used to parse JSON from Reddit APIs
pub async fn val(j: &serde_json::Value, k: &str) -> String { 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()) String::from(j["data"][n][k].as_str().unwrap())
} }
//
// NETWORKING
//
// Make a request to a Reddit API and parse the JSON response // Make a request to a Reddit API and parse the JSON response
#[allow(dead_code)] #[allow(dead_code)]
pub async fn request(url: String) -> serde_json::Value { pub async fn request(url: String) -> Result<serde_json::Value, &'static str> {
// --- actix-web::client --- // --- actix-web::client ---
// let client = actix_web::client::Client::default(); // let client = actix_web::client::Client::default();
// let res = client // let res = client
@ -86,10 +104,22 @@ pub async fn request(url: String) -> serde_json::Value {
// let body = res.body_string().await.unwrap(); // let body = res.body_string().await.unwrap();
// --- reqwest --- // --- 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 // 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")
}
} }

18
templates/error.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Page not found.</title>
<meta name="description" content="View on Libreddit, an alternative private front-end to Reddit.">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header>
<a href="/"><span style="color:white">lib</span>reddit.</a>
<a style="color:white" href="https://github.com/spikecodes/libreddit">GITHUB</a>
</header>
<main>
<h1 style="text-align: center; font-size: 50px;">{{ message }}</h1>
</main>
</body>
</html>