Skip to content

Commit

Permalink
refactor: Rename structs and reorganize modules for better clarity (#151
Browse files Browse the repository at this point in the history
)

* rename `FeedPreview` to `NormalizedFeed` (under `feed::norm` module)
- this change is meant to reflect the fact the shift in usage of this
struct from merely a preview to a more generic representation
* rename `PostPreview` to `NormalizedPost` (under `feed::norm` module)
  - same reason as above
* move `ConfigError`, `Error`, `Result` to the new `error` module
  - these types fits better in their own `error` module
* move `TimedLruCache` from `cache` to `util` module
- this type is more of a utility type than to justifying its own module
* move html manipulation functions to `util` module
  • Loading branch information
shouya authored Sep 27, 2024
1 parent 34c7176 commit 15dfebd
Show file tree
Hide file tree
Showing 37 changed files with 283 additions and 297 deletions.
47 changes: 0 additions & 47 deletions src/cache.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use url::Url;

use crate::{
server::{self, EndpointConfig, ServerConfig},
util::{ConfigError, Result},
ConfigError, Result,
};

#[derive(Parser)]
Expand Down
5 changes: 1 addition & 4 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
feed::Feed,
util::{ConfigError, Error, Result},
};
use crate::{feed::Feed, ConfigError, Error, Result};

use self::cache::{Response, ResponseCache};

Expand Down
4 changes: 1 addition & 3 deletions src/client/cache.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::sync::Arc;

use crate::cache::TimedLruCache;

use mime::Mime;
use reqwest::header::HeaderMap;
use url::Url;

use crate::util::{Error, Result};
use crate::{util::TimedLruCache, Error, Result};

pub type ResponseCache = TimedLruCache<Url, Response>;

Expand Down
145 changes: 145 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use http::StatusCode;

// pub type DateTime = time::OffsetDateTime;
pub type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Debug, thiserror::Error)]
pub enum JsError {
#[error("{0}")]
Message(String),

#[error("Exception: {0}")]
Exception(crate::js::Exception),

#[error("{0}")]
Error(#[from] rquickjs::Error),
}

#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("Bad selector: {0}")]
BadSelector(String),

#[error("YAML parse error: {0}")]
Yaml(#[from] serde_yaml::Error),

#[error("Regex error: {0}")]
Regex(#[from] regex::Error),

#[error("Invalid URL {0}")]
InvalidUrl(#[from] url::ParseError),

#[error("IO error: {0}")]
Io(#[from] std::io::Error),

#[error("Reqwest client error: {0}")]
Reqwest(#[from] reqwest::Error),

#[error("Js runtime initialization error: {0}")]
Js(#[from] JsError),

#[error("Client config error - bad header value: {0}")]
ClientHeader(#[from] reqwest::header::InvalidHeaderValue),

#[error("Duplicate endpoint: {0}")]
DuplicateEndpoint(String),

#[error("Bad source template: {0}")]
BadSourceTemplate(String),

#[error("{0}")]
Message(String),
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),

#[error("HTTP error: {0}")]
Http(#[from] http::Error),

#[error("Axum error: {0}")]
Axum(#[from] axum::Error),

#[error("RSS feed error: {0}")]
Rss(#[from] rss::Error),

#[error("Atom feed error: {0}")]
Atom(#[from] atom_syndication::Error),

#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),

#[error("Feed parsing error: {0}")]
FeedParse(&'static str),

#[error("Feed merge error: {0}")]
FeedMerge(&'static str),

#[error("Reqwest client error: {0}")]
Reqwest(#[from] reqwest::Error),

#[error("HTTP status error {0} (url: {1})")]
HttpStatus(reqwest::StatusCode, url::Url),

#[error("Js runtime error: {0}")]
Js(#[from] JsError),

#[error("Failed to extract webpage: {0}")]
Readability(#[from] readability::error::Error),

#[error("Config error: {0}")]
Config(#[from] ConfigError),

#[error("Tokio task join error: {0}")]
Join(#[from] tokio::task::JoinError),

#[error("Endpoint not found: {0}")]
EndpointNotFound(String),

#[error("Unsupported feed format: {0}")]
UnsupportedFeedFormat(String),

#[error("Failed fetching source: {0}")]
FetchSource(Box<Error>),

#[error("Source URL unspecified for dynamic source")]
DynamicSourceUnspecified,

#[error("Source parameter {placeholder} failed to match validation: {validation} (input: {input})")]
SourceTemplateValidation {
placeholder: String,
validation: String,
input: String,
},

#[error("Source template placeholder unspecified: {0}")]
MissingSourceTemplatePlaceholder(String),

#[error("Can't infer app base, please refer to https://github.com/shouya/rss-funnel/wiki/App-base")]
BaseUrlNotInferred,

#[error("{0}")]
Message(String),
}

impl Error {
pub fn into_http(self) -> (StatusCode, String) {
match self {
Error::FetchSource(e) => {
let (status, body) = e.into_http();
(status, format!("Error fetching source: {body}"))
}
Error::HttpStatus(status, url) => {
let body = format!("Error requesting {url}: {status}");
(StatusCode::BAD_GATEWAY, body)
}
Error::SourceTemplateValidation { .. }
| Error::MissingSourceTemplatePlaceholder(_) => {
(StatusCode::BAD_REQUEST, format!("{self}"))
}
_ => (StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")),
}
}
}
27 changes: 13 additions & 14 deletions src/feed.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod conversion;
mod extension;
mod preview;
mod norm;

use std::hash::Hash;

Expand All @@ -11,16 +11,15 @@ use serde::Deserialize;
use serde::Serialize;
use url::Url;

use crate::html::convert_relative_url;
use crate::html::html_body;
use crate::source::FromScratch;
use crate::util::Error;
use crate::util::Result;
use crate::util::{convert_relative_url, html_body};
use crate::Error;
use crate::Result;

use extension::ExtensionExt;

use self::preview::FeedPreview;
pub use self::preview::PostPreview;
use self::norm::NormalizedFeed;
pub use self::norm::NormalizedPost;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(untagged)]
Expand Down Expand Up @@ -84,7 +83,7 @@ impl Feed {
}
}

pub fn preview(&self) -> FeedPreview {
pub fn normalize(&self) -> NormalizedFeed {
let title = self.title().to_string();
let link = self.link().to_string();
let description = self.description().map(String::from);
Expand All @@ -94,16 +93,16 @@ impl Feed {
Feed::Rss(channel) => channel
.items
.iter()
.map(|item| Post::Rss(item.clone()).preview())
.map(|item| Post::Rss(item.clone()).normalize())
.collect(),
Feed::Atom(feed) => feed
.entries
.iter()
.map(|entry| Post::Atom(entry.clone()).preview())
.map(|entry| Post::Atom(entry.clone()).normalize())
.collect(),
};

FeedPreview {
NormalizedFeed {
title,
link,
description,
Expand Down Expand Up @@ -257,7 +256,7 @@ impl Feed {
}

#[allow(clippy::field_reassign_with_default)]
pub fn add_post(&mut self, post_preview: PostPreview) {
pub fn add_post(&mut self, post_preview: NormalizedPost) {
match self {
Feed::Rss(channel) => {
channel.items.push(post_preview.into_rss_item());
Expand Down Expand Up @@ -377,14 +376,14 @@ enum PostField {
}

impl Post {
pub fn preview(&self) -> PostPreview {
pub fn normalize(&self) -> NormalizedPost {
let title = self.title().map(String::from).unwrap_or_default();
let author = self.author().map(String::from);
let link = self.link().map(String::from).unwrap_or_default();
let body = self.first_body().map(String::from);
let published = self.pub_date();

PostPreview {
NormalizedPost {
title,
author,
link,
Expand Down
8 changes: 4 additions & 4 deletions src/feed/preview.rs → src/feed/norm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ use chrono::{DateTime, FixedOffset};
use serde::Serialize;

#[derive(Debug, Serialize, Default)]
pub struct FeedPreview {
pub struct NormalizedFeed {
pub title: String,
pub link: String,
pub description: Option<String>,
pub posts: Vec<PostPreview>,
pub posts: Vec<NormalizedPost>,
}

#[derive(Debug, Serialize, PartialEq, Eq, Hash, Default)]
pub struct PostPreview {
pub struct NormalizedPost {
pub title: String,
pub author: Option<String>,
pub link: String,
pub body: Option<String>,
pub date: Option<DateTime<FixedOffset>>,
}

impl PostPreview {
impl NormalizedPost {
pub fn into_rss_item(self) -> rss::Item {
let guid = rss::Guid {
value: self.link.clone(),
Expand Down
5 changes: 1 addition & 4 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use serde::{Deserialize, Serialize};
use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator};
use url::Url;

use crate::{
feed::Feed,
util::{ConfigError, Error, Result},
};
use crate::{feed::Feed, ConfigError, Error, Result};

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
Expand Down
4 changes: 2 additions & 2 deletions src/filter/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};

use crate::{
feed::{Feed, FeedFormat},
util::{ConfigError, Result},
ConfigError, Result,
};

use super::{FeedFilter, FeedFilterConfig, FilterContext};
Expand Down Expand Up @@ -43,7 +43,7 @@ impl FeedFilter for ConvertTo {
mod tests {
use super::*;
use crate::test_utils::fetch_endpoint;
use crate::util::Result;
use crate::Result;

#[tokio::test]
async fn test_convert_to() -> Result<()> {
Expand Down
Loading

0 comments on commit 15dfebd

Please sign in to comment.