Improve IPFS errors
This commit is contained in:
parent
fcce7d07c8
commit
8e5387600a
7 changed files with 126 additions and 93 deletions
|
@ -5,42 +5,35 @@ use axum::{
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub struct Error(StatusCode);
|
use crate::ipfs::error::IPFSError;
|
||||||
|
|
||||||
impl Error {
|
#[derive(Error, Debug)]
|
||||||
pub fn new() -> Self {
|
pub enum CDNError {
|
||||||
Error(StatusCode::INTERNAL_SERVER_ERROR)
|
#[error("SQL error: {0}")]
|
||||||
}
|
SQL(#[from] sqlx::Error),
|
||||||
|
#[error("IPFS error: {0}")]
|
||||||
|
IPFS(#[from] IPFSError),
|
||||||
|
#[error("Decode error: {0}")]
|
||||||
|
Decode(#[from] FromUtf8Error),
|
||||||
|
#[error("Internal server error")]
|
||||||
|
Internal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Error {
|
impl IntoResponse for CDNError {
|
||||||
type Body = Empty<Bytes>;
|
type Body = Empty<Bytes>;
|
||||||
|
|
||||||
type BodyError = Infallible;
|
type BodyError = Infallible;
|
||||||
|
|
||||||
fn into_response(self) -> axum::http::Response<Self::Body> {
|
fn into_response(self) -> axum::http::Response<Self::Body> {
|
||||||
self.0.into_response()
|
let status = match self {
|
||||||
}
|
CDNError::SQL(err) => match err {
|
||||||
}
|
sqlx::Error::RowNotFound => StatusCode::NOT_FOUND,
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
impl From<sqlx::Error> for Error {
|
},
|
||||||
fn from(err: sqlx::Error) -> Self {
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
match err {
|
};
|
||||||
sqlx::Error::RowNotFound => Error(StatusCode::NOT_FOUND),
|
status.into_response()
|
||||||
_ => Error(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<reqwest::Error> for Error {
|
|
||||||
fn from(err: reqwest::Error) -> Self {
|
|
||||||
Error(err.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FromUtf8Error> for Error {
|
|
||||||
fn from(_: FromUtf8Error) -> Self {
|
|
||||||
Error(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use sqlx::MySqlPool;
|
||||||
use crate::config::ConfVars;
|
use crate::config::ConfVars;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
error::Error,
|
error::CDNError,
|
||||||
templates::{DirTemplate, HtmlTemplate},
|
templates::{DirTemplate, HtmlTemplate},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,58 +37,48 @@ async fn image(
|
||||||
Path((user, filename)): Path<(String, String)>,
|
Path((user, filename)): Path<(String, String)>,
|
||||||
Extension(db_pool): Extension<MySqlPool>,
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
Extension(vars): Extension<ConfVars>,
|
Extension(vars): Extension<ConfVars>,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, CDNError> {
|
||||||
let filename = urlencoding::decode(&filename)?.into_owned();
|
let filename = urlencoding::decode(&filename)?.into_owned();
|
||||||
let cid = sql::get_cid(user, filename.clone(), &db_pool).await?;
|
let cid = sql::get_cid(user, filename.clone(), &db_pool).await?;
|
||||||
let ipfs = vars.ipfs_client()?;
|
let ipfs = vars.ipfs_client()?;
|
||||||
let res = ipfs.cat(cid).await?;
|
let res = ipfs.cat(cid).await?;
|
||||||
let clength = res
|
let clength = res
|
||||||
.headers()
|
.headers()
|
||||||
.get(HeaderName::from_static("x-content-length"));
|
.get(HeaderName::from_static("x-content-length"))
|
||||||
match clength {
|
.ok_or(CDNError::Internal)?;
|
||||||
Some(h) => {
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
let ctype =
|
|
||||||
ContentType::from(new_mime_guess::from_path(filename).first_or_octet_stream());
|
|
||||||
headers.typed_insert(ctype);
|
|
||||||
headers.insert(CONTENT_LENGTH, h.clone());
|
|
||||||
|
|
||||||
Ok((
|
let mut headers = HeaderMap::new();
|
||||||
StatusCode::OK,
|
let ctype = ContentType::from(new_mime_guess::from_path(filename).first_or_octet_stream());
|
||||||
headers,
|
headers.typed_insert(ctype);
|
||||||
Body::wrap_stream(res.bytes_stream()),
|
headers.insert(CONTENT_LENGTH, clength.clone());
|
||||||
))
|
|
||||||
}
|
Ok((
|
||||||
None => Err(Error::new()),
|
StatusCode::OK,
|
||||||
}
|
headers,
|
||||||
|
Body::wrap_stream(res.bytes_stream()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn users(
|
async fn users(
|
||||||
Extension(db_pool): Extension<MySqlPool>,
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
Extension(vars): Extension<ConfVars>,
|
Extension(vars): Extension<ConfVars>,
|
||||||
) -> Result<impl IntoResponse, StatusCode> {
|
) -> Result<impl IntoResponse, CDNError> {
|
||||||
let q = sql::get_users(&db_pool).await;
|
let users = sql::get_users(&db_pool).await?;
|
||||||
match q {
|
Ok(HtmlTemplate(DirTemplate {
|
||||||
Ok(users) => Ok(HtmlTemplate(DirTemplate {
|
entries: users,
|
||||||
entries: users,
|
prefix: vars.cdn,
|
||||||
prefix: vars.cdn,
|
suffix: "/".to_string(),
|
||||||
suffix: "/".to_string(),
|
}))
|
||||||
})),
|
|
||||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn memes(
|
async fn memes(
|
||||||
Path(user): Path<String>,
|
Path(user): Path<String>,
|
||||||
Extension(db_pool): Extension<MySqlPool>,
|
Extension(db_pool): Extension<MySqlPool>,
|
||||||
) -> Result<impl IntoResponse, StatusCode> {
|
) -> Result<impl IntoResponse, CDNError> {
|
||||||
let q = sql::get_memes(user, &db_pool).await;
|
let memes = sql::get_memes(user, &db_pool).await?;
|
||||||
match q {
|
Ok(HtmlTemplate(DirTemplate {
|
||||||
Ok(memes) => Ok(HtmlTemplate(DirTemplate {
|
entries: memes,
|
||||||
entries: memes,
|
prefix: ".".to_string(),
|
||||||
prefix: ".".to_string(),
|
suffix: "".to_string(),
|
||||||
suffix: "".to_string(),
|
}))
|
||||||
})),
|
|
||||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum JMError {
|
pub enum JMError {
|
||||||
#[error("File read error: {0}")]
|
#[error("File read error: {0}")]
|
||||||
|
|
10
src/ipfs/error.rs
Normal file
10
src/ipfs/error.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
use url::ParseError;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum IPFSError {
|
||||||
|
#[error("Reqwest error: {0}")]
|
||||||
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
#[error("URL parse error: {0}")]
|
||||||
|
URL(#[from] ParseError),
|
||||||
|
}
|
|
@ -1,12 +1,20 @@
|
||||||
use reqwest::{Client, Response, Result, Url};
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use axum::{body::Bytes, http::request};
|
||||||
|
use reqwest::{
|
||||||
|
multipart::{Form, Part},
|
||||||
|
Body, Client, Response, Url,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::config::ConfVars;
|
use crate::config::ConfVars;
|
||||||
|
|
||||||
|
use self::error::IPFSError;
|
||||||
|
|
||||||
|
pub(crate) mod error;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct AddResponse {
|
pub struct IPFSFile {
|
||||||
#[serde(rename = "Bytes")]
|
|
||||||
pub bytes: String,
|
|
||||||
#[serde(rename = "Hash")]
|
#[serde(rename = "Hash")]
|
||||||
pub hash: String,
|
pub hash: String,
|
||||||
#[serde(rename = "Name")]
|
#[serde(rename = "Name")]
|
||||||
|
@ -20,46 +28,76 @@ pub struct CatQuery {
|
||||||
pub arg: String,
|
pub arg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AddQuery {
|
||||||
|
pub pin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct PinQuery {
|
||||||
|
pub arg: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IpfsClient {
|
pub struct IpfsClient {
|
||||||
url: Url,
|
url: Url,
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IpfsClient {
|
impl IpfsClient {
|
||||||
|
pub async fn cat(&self, cid: String) -> Result<Response, IPFSError> {
|
||||||
pub fn cat_url(&self) -> Url {
|
let request = self
|
||||||
self.url.join("/api/v0/cat").expect("Something went wrong with the IPFS URL")
|
.client
|
||||||
|
.post(self.url.join("/api/v0/cat")?)
|
||||||
|
.query(&CatQuery::new(cid));
|
||||||
|
Ok(request.send().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_url(&self) -> Url {
|
pub async fn add(&self, file: Bytes, filename: String) -> Result<IPFSFile, IPFSError> {
|
||||||
self.url.join("/api/v0/add").expect("Something went wrong with the IPFS URL")
|
let request = self
|
||||||
|
.client
|
||||||
|
.post(self.url.join("/api/v0/add")?)
|
||||||
|
.query(&AddQuery::new(false))
|
||||||
|
.multipart(Form::new().part("file", Part::stream(file).file_name(filename)));
|
||||||
|
let response = request.send().await?;
|
||||||
|
let res: IPFSFile = response.json().await?;
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cat(&self, cid: String) -> Result<Response> {
|
pub async fn pin(&self, cid: String) -> Result<(), IPFSError> {
|
||||||
let request = self.client.post(self.cat_url()).query(&CatQuery::new(cid));
|
let request = self
|
||||||
request.send().await
|
.client
|
||||||
|
.post(self.url.join("/api/v0/pin/add")?)
|
||||||
|
.query(&PinQuery::new(cid))
|
||||||
|
.timeout(Duration::from_secs(60));
|
||||||
|
let response = request.send().await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CatQuery {
|
impl CatQuery {
|
||||||
|
|
||||||
pub fn new(cid: String) -> Self {
|
pub fn new(cid: String) -> Self {
|
||||||
Self {
|
Self { arg: cid }
|
||||||
arg: cid,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddQuery {
|
||||||
|
pub fn new(pin: bool) -> Self {
|
||||||
|
Self { pin }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinQuery {
|
||||||
|
pub fn new(cid: String) -> Self {
|
||||||
|
Self { arg: cid }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfVars {
|
impl ConfVars {
|
||||||
|
pub fn ipfs_client(&self) -> Result<IpfsClient, IPFSError> {
|
||||||
pub fn ipfs_client(&self) -> Result<IpfsClient> {
|
let client = reqwest::ClientBuilder::new().user_agent("curl").build()?;
|
||||||
let client =reqwest::ClientBuilder::new().user_agent("curl").build()?;
|
|
||||||
Ok(IpfsClient {
|
Ok(IpfsClient {
|
||||||
url: self.ipfs_api.clone(),
|
url: self.ipfs_api.clone(),
|
||||||
client,
|
client,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,9 +12,9 @@ use tower_http::{add_extension::AddExtensionLayer, set_header::SetResponseHeader
|
||||||
|
|
||||||
mod cdn;
|
mod cdn;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod error;
|
||||||
mod ipfs;
|
mod ipfs;
|
||||||
mod v1;
|
mod v1;
|
||||||
mod error;
|
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
|
@ -33,8 +33,7 @@ async fn main() -> Result<(), JMError> {
|
||||||
let config = std::fs::read(&opt.config)?;
|
let config = std::fs::read(&opt.config)?;
|
||||||
let config = toml::from_slice::<Config>(&config)?;
|
let config = toml::from_slice::<Config>(&config)?;
|
||||||
|
|
||||||
let db_pool = MySqlPool::new(&config.database)
|
let db_pool = MySqlPool::new(&config.database).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/api/v1", v1::routes())
|
.nest("/api/v1", v1::routes())
|
||||||
|
|
|
@ -10,6 +10,7 @@ use reqwest::StatusCode;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::models::ErrorResponse;
|
use super::models::ErrorResponse;
|
||||||
|
use crate::ipfs::error::IPFSError;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum APIError {
|
pub enum APIError {
|
||||||
|
@ -19,6 +20,8 @@ pub enum APIError {
|
||||||
Multipart(#[from] MultipartError),
|
Multipart(#[from] MultipartError),
|
||||||
#[error("Bad request: {0}")]
|
#[error("Bad request: {0}")]
|
||||||
BadRequest(String),
|
BadRequest(String),
|
||||||
|
#[error("IPFS error: {0}")]
|
||||||
|
IPFS(#[from] IPFSError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorResponse {
|
impl ErrorResponse {
|
||||||
|
@ -44,6 +47,7 @@ impl IntoResponse for APIError {
|
||||||
},
|
},
|
||||||
APIError::Multipart(_) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
|
APIError::Multipart(_) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
|
||||||
APIError::BadRequest(err) => ErrorResponse::new(StatusCode::BAD_REQUEST, Some(err)),
|
APIError::BadRequest(err) => ErrorResponse::new(StatusCode::BAD_REQUEST, Some(err)),
|
||||||
|
APIError::IPFS(_) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
|
||||||
};
|
};
|
||||||
let status = res.status.clone();
|
let status = res.status.clone();
|
||||||
(status, Json(res)).into_response()
|
(status, Json(res)).into_response()
|
||||||
|
|
Loading…
Add table
Reference in a new issue