feat: add validation for semantic ID in post and update error handling
All checks were successful
Frontend CI / build (push) Successful in 1m23s
All checks were successful
Frontend CI / build (push) Successful in 1m23s
This commit is contained in:
parent
43582af2a5
commit
8f91d815bc
9
backend/Cargo.lock
generated
9
backend/Cargo.lock
generated
@ -2407,6 +2407,7 @@ dependencies = [
|
|||||||
"auth",
|
"auth",
|
||||||
"chrono",
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
|
"regex",
|
||||||
"sentry",
|
"sentry",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
@ -2638,9 +2639,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -2650,9 +2651,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.9"
|
version = "0.4.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -5,7 +5,6 @@ members = [
|
|||||||
"feature/common",
|
"feature/common",
|
||||||
"feature/image",
|
"feature/image",
|
||||||
"feature/post",
|
"feature/post",
|
||||||
"feature/common",
|
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@ -33,6 +32,7 @@ openidconnect = { version = "4.0.1", features = [
|
|||||||
"reqwest-blocking",
|
"reqwest-blocking",
|
||||||
] }
|
] }
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
|
regex = "1.12.1"
|
||||||
sentry = { version = "0.42.0", features = ["actix", "anyhow"] }
|
sentry = { version = "0.42.0", features = ["actix", "anyhow"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
sqlx = { version = "0.8.5", features = [
|
sqlx = { version = "0.8.5", features = [
|
||||||
|
@ -8,6 +8,7 @@ actix-web.workspace = true
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
regex.workspace = true
|
||||||
sentry.workspace = true
|
sentry.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
|
@ -4,6 +4,7 @@ use std::fmt::Display;
|
|||||||
pub enum PostError {
|
pub enum PostError {
|
||||||
NotFound,
|
NotFound,
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
|
InvalidSemanticId,
|
||||||
Unexpected(anyhow::Error),
|
Unexpected(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,6 +13,10 @@ impl Display for PostError {
|
|||||||
match self {
|
match self {
|
||||||
PostError::NotFound => write!(f, "Post not found"),
|
PostError::NotFound => write!(f, "Post not found"),
|
||||||
PostError::Unauthorized => write!(f, "Unauthorized access to post"),
|
PostError::Unauthorized => write!(f, "Unauthorized access to post"),
|
||||||
|
PostError::InvalidSemanticId => write!(
|
||||||
|
f,
|
||||||
|
"Semantic ID shouldn't be numeric and must conform to `^[0-9a-zA-Z_\\-]+$`"
|
||||||
|
),
|
||||||
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,7 @@ impl CreatePostUseCaseImpl {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl CreatePostUseCase for CreatePostUseCaseImpl {
|
impl CreatePostUseCase for CreatePostUseCaseImpl {
|
||||||
async fn execute(&self, post: Post, label_ids: &[i32]) -> Result<i32, PostError> {
|
async fn execute(&self, post: Post, label_ids: &[i32]) -> Result<i32, PostError> {
|
||||||
self.post_repository
|
post.validate()?;
|
||||||
.create_post(post, label_ids)
|
self.post_repository.create_post(post, label_ids).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ impl UpdatePostUseCaseImpl {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl UpdatePostUseCase for UpdatePostUseCaseImpl {
|
impl UpdatePostUseCase for UpdatePostUseCaseImpl {
|
||||||
async fn execute(&self, post: Post, label_ids: &[i32]) -> Result<(), PostError> {
|
async fn execute(&self, post: Post, label_ids: &[i32]) -> Result<(), PostError> {
|
||||||
|
post.validate()?;
|
||||||
self.post_repository.update_post(post, label_ids).await
|
self.post_repository.update_post(post, label_ids).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use crate::application::error::post_error::PostError;
|
||||||
|
|
||||||
use super::post_info::PostInfo;
|
use super::post_info::PostInfo;
|
||||||
|
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
@ -5,3 +7,10 @@ pub struct Post {
|
|||||||
pub info: PostInfo,
|
pub info: PostInfo,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Post {
|
||||||
|
pub fn validate(&self) -> Result<(), PostError> {
|
||||||
|
self.info.validate()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::application::error::post_error::PostError;
|
||||||
|
|
||||||
use super::label::Label;
|
use super::label::Label;
|
||||||
|
|
||||||
@ -11,3 +14,18 @@ pub struct PostInfo {
|
|||||||
pub labels: Vec<Label>,
|
pub labels: Vec<Label>,
|
||||||
pub published_time: Option<DateTime<Utc>>,
|
pub published_time: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PostInfo {
|
||||||
|
pub fn validate(&self) -> Result<(), PostError> {
|
||||||
|
if self.semantic_id.parse::<i32>().is_ok() {
|
||||||
|
return Err(PostError::InvalidSemanticId);
|
||||||
|
}
|
||||||
|
|
||||||
|
let re = Regex::new(r"^[0-9a-zA-Z_\-]+$").unwrap();
|
||||||
|
if !re.is_match(&self.semantic_id) {
|
||||||
|
return Err(PostError::InvalidSemanticId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,12 +32,16 @@ pub async fn create_label_handler(
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(label) => HttpResponse::Created().json(label),
|
Ok(label) => HttpResponse::Created().json(label),
|
||||||
Err(e) => {
|
Err(e) => match e {
|
||||||
match e {
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
PostError::Unexpected(e) => capture_anyhow(&e),
|
PostError::NotFound | PostError::InvalidSemanticId => {
|
||||||
_ => capture_anyhow(&anyhow!(e)),
|
capture_anyhow(&anyhow!(e));
|
||||||
};
|
HttpResponse::InternalServerError().finish()
|
||||||
HttpResponse::InternalServerError().finish()
|
}
|
||||||
}
|
PostError::Unexpected(e) => {
|
||||||
|
capture_anyhow(&e);
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,12 +34,17 @@ pub async fn create_post_handler(
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(post) => HttpResponse::Created().json(post),
|
Ok(post) => HttpResponse::Created().json(post),
|
||||||
Err(e) => {
|
Err(e) => match e {
|
||||||
match e {
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
PostError::Unexpected(e) => capture_anyhow(&e),
|
PostError::InvalidSemanticId => HttpResponse::BadRequest().finish(),
|
||||||
_ => capture_anyhow(&anyhow!(e)),
|
PostError::NotFound => {
|
||||||
};
|
capture_anyhow(&anyhow!(e));
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
|
PostError::Unexpected(e) => {
|
||||||
|
capture_anyhow(&e);
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,15 @@ pub async fn get_all_labels_handler(
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(labels) => HttpResponse::Ok().json(labels),
|
Ok(labels) => HttpResponse::Ok().json(labels),
|
||||||
Err(e) => {
|
Err(e) => match e {
|
||||||
match e {
|
PostError::NotFound | PostError::Unauthorized | PostError::InvalidSemanticId => {
|
||||||
PostError::Unexpected(e) => capture_anyhow(&e),
|
capture_anyhow(&anyhow!(e));
|
||||||
_ => capture_anyhow(&anyhow!(e)),
|
HttpResponse::InternalServerError().finish()
|
||||||
};
|
}
|
||||||
HttpResponse::InternalServerError().finish()
|
PostError::Unexpected(e) => {
|
||||||
}
|
capture_anyhow(&e);
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,15 @@ pub async fn get_all_post_info_handler(
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
Ok(post_info_list) => HttpResponse::Ok().json(post_info_list),
|
||||||
Err(e) => {
|
Err(e) => match e {
|
||||||
match e {
|
PostError::NotFound | PostError::Unauthorized | PostError::InvalidSemanticId => {
|
||||||
PostError::Unexpected(e) => capture_anyhow(&e),
|
capture_anyhow(&anyhow!(e));
|
||||||
_ => capture_anyhow(&anyhow!(e)),
|
HttpResponse::InternalServerError().finish()
|
||||||
};
|
}
|
||||||
HttpResponse::InternalServerError().finish()
|
PostError::Unexpected(e) => {
|
||||||
}
|
capture_anyhow(&e);
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix_web::{HttpResponse, Responder, web};
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
use anyhow::anyhow;
|
||||||
use auth::framework::web::auth_middleware::UserId;
|
use auth::framework::web::auth_middleware::UserId;
|
||||||
use sentry::integrations::anyhow::capture_anyhow;
|
use sentry::integrations::anyhow::capture_anyhow;
|
||||||
|
|
||||||
@ -33,6 +34,10 @@ pub async fn get_post_by_id_handler(
|
|||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
|
PostError::InvalidSemanticId => {
|
||||||
|
capture_anyhow(&anyhow!(e));
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
PostError::Unexpected(e) => {
|
PostError::Unexpected(e) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix_web::{HttpResponse, Responder, web};
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
use anyhow::anyhow;
|
||||||
use auth::framework::web::auth_middleware::UserId;
|
use auth::framework::web::auth_middleware::UserId;
|
||||||
use sentry::integrations::anyhow::capture_anyhow;
|
use sentry::integrations::anyhow::capture_anyhow;
|
||||||
|
|
||||||
@ -39,6 +40,10 @@ pub async fn update_label_handler(
|
|||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
|
PostError::InvalidSemanticId => {
|
||||||
|
capture_anyhow(&anyhow!(e));
|
||||||
|
HttpResponse::InternalServerError().finish()
|
||||||
|
}
|
||||||
PostError::Unexpected(e) => {
|
PostError::Unexpected(e) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
|
@ -39,6 +39,7 @@ pub async fn update_post_handler(
|
|||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
PostError::NotFound => HttpResponse::NotFound().finish(),
|
PostError::NotFound => HttpResponse::NotFound().finish(),
|
||||||
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
PostError::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||||
|
PostError::InvalidSemanticId => HttpResponse::BadRequest().finish(),
|
||||||
PostError::Unexpected(e) => {
|
PostError::Unexpected(e) => {
|
||||||
capture_anyhow(&e);
|
capture_anyhow(&e);
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user