Fix error responses and content types
This commit is contained in:
		
							parent
							
								
									25c6dffc85
								
							
						
					
					
						commit
						cbd1ae6fa4
					
				| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										62
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										62
									
								
								src/main.rs
								
								
								
								
							| 
						 | 
					@ -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) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue