Fix error responses and content types

This commit is contained in:
Leonora Tindall 2022-10-31 21:21:05 -05:00
parent 25c6dffc85
commit cbd1ae6fa4
Signed by: nora
GPG Key ID: 7A8B52EC67E09AAF
5 changed files with 67 additions and 44 deletions

View File

@ -14,7 +14,8 @@ ports to use for development and deployment.
- [x] Webfinger for Cohost projects - [x] Webfinger for Cohost projects
- [ ] Handle redirects - [ ] Handle redirects
- [ ] RSS feeds for projects - [x] RSS feeds for projects
- [x] Index page explaining what's going on
- [ ] RSS feeds for tags - [ ] RSS feeds for tags
- [ ] Atom Extension pagination support - [ ] Atom Extension pagination support
- [ ] Read More support - [ ] Read More support

View File

@ -1,13 +0,0 @@
{
"subject": "acct:noracodes@example.com",
"aliases": [
"acct:noracodes@cohost.org"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://cohost.org/noracodes"
}
]
}

View File

@ -37,8 +37,26 @@ fn index() -> RawHtml<&'static str> {
RawHtml(include_str!("../static/index.html")) RawHtml(include_str!("../static/index.html"))
} }
#[derive(Responder)]
#[response(content_type = "application/rss+xml")]
struct RssResponse {
inner: String,
}
#[derive(Responder)]
#[response(content_type = "text/plain")]
enum ErrorResponse {
#[response(status = 404)]
NotFound(String),
#[response(status = 500)]
InternalError(String),
}
#[get("/<project>/feed.rss?<page>")] #[get("/<project>/feed.rss?<page>")]
async fn syndication_rss_route(project: &str, page: Option<u64>) -> Option<String> { async fn syndication_rss_route(
project: &str,
page: Option<u64>,
) -> Result<RssResponse, ErrorResponse> {
let page = page.unwrap_or(0); let page = page.unwrap_or(0);
let project_url = format!("{}{}", COHOST_ACCOUNT_API_URL, project); let project_url = format!("{}{}", COHOST_ACCOUNT_API_URL, project);
let posts_url = cohost_posts_api_url(project, page); let posts_url = cohost_posts_api_url(project, page);
@ -49,25 +67,28 @@ async fn syndication_rss_route(project: &str, page: Option<u64>) -> Option<Strin
StatusCode::OK => match v.json::<CohostAccount>().await { StatusCode::OK => match v.json::<CohostAccount>().await {
Ok(a) => a, Ok(a) => a,
Err(e) => { Err(e) => {
eprintln!("Couldn't deserialize Cohost project '{}': {:?}", project, e); let err = format!("Couldn't deserialize Cohost project '{}': {:?}", project, e);
return None; eprintln!("{}", err);
return Err(ErrorResponse::InternalError(err));
} }
}, },
// TODO NORA: Handle possible redirects // TODO NORA: Handle possible redirects
s => { s => {
eprintln!( let err = format!(
"Didn't receive status code 200 for Cohost project '{}'; got {:?} instead.", "Didn't receive status code 200 for Cohost project '{}'; got {:?} instead.",
project, s project, s
); );
return None; eprintln!("{}", err);
return Err(ErrorResponse::NotFound(err));
} }
}, },
Err(e) => { Err(e) => {
eprintln!( let err = format!(
"Error making request to Cohost for project '{}': {:?}", "Error making request to Cohost for project '{}': {:?}",
project, e project, e
); );
return None; eprintln!("{}", err);
return Err(ErrorResponse::InternalError(err));
} }
}; };
@ -76,33 +97,41 @@ async fn syndication_rss_route(project: &str, page: Option<u64>) -> Option<Strin
Ok(v) => match v.status() { Ok(v) => match v.status() {
StatusCode::OK => match v.json::<CohostPostsPage>().await { StatusCode::OK => match v.json::<CohostPostsPage>().await {
Ok(page_data) => { Ok(page_data) => {
return Some( return Ok(RssResponse {
syndication::channel_for_posts_page(project, page, project_data, page_data) inner: syndication::channel_for_posts_page(
project,
page,
project_data,
page_data,
)
.to_string(), .to_string(),
); });
} }
Err(e) => { Err(e) => {
eprintln!( let err = format!(
"Couldn't deserialize Cohost posts page for '{}': {:?}", "Couldn't deserialize Cohost posts page for '{}': {:?}",
project, e project, e
); );
eprintln!("{}", err);
return Err(ErrorResponse::InternalError(err));
} }
}, },
// TODO NORA: Handle possible redirects // TODO NORA: Handle possible redirects
s => { s => {
eprintln!("Didn't receive status code 200 for posts for Cohost project '{}'; got {:?} instead.", page, s); let err = format!("Didn't receive status code 200 for posts for Cohost project '{}'; got {:?} instead.", page, s);
return None; eprintln!("{}", err);
return Err(ErrorResponse::NotFound(err));
} }
}, },
Err(e) => { Err(e) => {
eprintln!( let err = format!(
"Error making request to Cohost for posts for project '{}': {:?}", "Error making request to Cohost for posts for project '{}': {:?}",
project, e project, e
); );
return None; eprintln!("{}", err);
return Err(ErrorResponse::InternalError(err));
} }
}; };
None
} }
#[get("/.well-known/webfinger?<params..>")] #[get("/.well-known/webfinger?<params..>")]
@ -125,6 +154,7 @@ async fn webfinger_route(params: HashMap<String, String>) -> Option<Json<CohostW
return Some(Json(CohostWebfingerResource::new( return Some(Json(CohostWebfingerResource::new(
param.0.as_str(), param.0.as_str(),
&ARGS.domain, &ARGS.domain,
&ARGS.base_url,
))); )));
} }
Err(e) => { Err(e) => {

View File

@ -16,11 +16,24 @@ pub struct CohostWebfingerProfileLink {
} }
impl CohostWebfingerResource { impl CohostWebfingerResource {
pub fn new<S: AsRef<str>, T: AsRef<str>>(project_id: S, domain: T) -> Self { pub fn new<S: AsRef<str>, T: AsRef<str>>(
project_id: S,
domain: T,
base_url: impl AsRef<str>,
) -> Self {
let mut corobel_link = CohostWebfingerProfileLink::new(project_id.as_ref().clone());
corobel_link.href = format!(
"https://{}{}{}/feed.rss",
domain.as_ref().clone(),
base_url.as_ref().clone(),
project_id.as_ref().clone()
);
corobel_link.rel = "feed".into();
corobel_link.t_type = "application+rss/xml".into();
Self { Self {
subject: format!("acct:{}@{}", project_id.as_ref(), domain.as_ref()), subject: format!("acct:{}@{}", project_id.as_ref(), domain.as_ref()),
aliases: vec![format!("acct:{}@cohost.org", project_id.as_ref())], aliases: vec![format!("acct:{}@cohost.org", project_id.as_ref())],
links: vec![CohostWebfingerProfileLink::new(project_id)], links: vec![CohostWebfingerProfileLink::new(project_id), corobel_link],
} }
} }
} }
@ -34,14 +47,3 @@ impl CohostWebfingerProfileLink {
} }
} }
} }
#[test]
fn serialize_webfinger_resource() -> Result<(), Box<dyn std::error::Error>> {
let expected_json = include_str!("../samples/corobel/.well-known/webfinger");
let resource: CohostWebfingerResource =
CohostWebfingerResource::new("noracodes", "example.com");
let actual_json = serde_json::to_string_pretty(&resource)?;
println!("{}", actual_json);
assert_eq!(&expected_json, &actual_json);
Ok(())
}

View File

@ -30,6 +30,9 @@
Go to <code>/project_name/feed.rss</code> to get a feed for a project. Go to <code>/project_name/feed.rss</code> to get a feed for a project.
For example, <a href="/noracodes/feed.rss"><code>/noracodes/feed.rss</code></a> will give you the feed for my page. For example, <a href="/noracodes/feed.rss"><code>/noracodes/feed.rss</code></a> will give you the feed for my page.
</p> </p>
<p>
Webfinger resources for accounts are provided at the Webfinger well-known URL <code>/.well-known/webfinger?project_name</code>.
</p>
<p> <p>
Brought to you by Leonora Tindall, written in Rust with Rocket. Brought to you by Leonora Tindall, written in Rust with Rocket.
</p> </p>