Lines
31.9 %
Functions
6.36 %
Branches
100 %
//web/src/pages/transaction/validate.rs - Shared validation logic for transaction operations
use axum::{Extension, Json, extract::State, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
use server::command::{CmdResult, FinanceEntity, account::ListAccounts};
use sqlx::types::Uuid;
use std::sync::Arc;
use crate::{
AppState, jwt_auth::JWTAuthMiddleware, pages::HtmlTemplate,
pages::validation::feedback::ValidationFeedback,
};
#[derive(Deserialize, Debug)]
pub struct ValidateAmountRequest {
pub amount: Option<String>,
}
pub struct ValidateAccountRequest {
pub account_id: Option<String>,
pub struct ValidateFromAccountRequest {
pub from_account: Option<String>,
pub struct ValidateToAccountRequest {
pub to_account: Option<String>,
pub struct ValidateNoteRequest {
pub note: Option<String>,
/// Validation result type that can be converted to either HTML or JSON response
#[derive(Debug)]
pub enum ValidationResult {
Success(String),
Error(String),
impl ValidationResult {
#[must_use]
pub fn to_html_response(self) -> impl IntoResponse {
match self {
ValidationResult::Success(msg) => HtmlTemplate(ValidationFeedback::success(msg)),
ValidationResult::Error(msg) => HtmlTemplate(ValidationFeedback::error(msg)),
pub fn to_json_response(
self,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
ValidationResult::Success(msg) => Ok(msg),
ValidationResult::Error(msg) => {
let error_response = serde_json::json!({
"status": "fail",
"message": msg,
});
Err((StatusCode::BAD_REQUEST, Json(error_response)))
/// Core validation logic for amounts
pub fn validate_amount_logic(amount_str: Option<&str>) -> ValidationResult {
let amount_str = match amount_str {
Some(s) if !s.trim().is_empty() => s.trim(),
_ => return ValidationResult::Error(t!("Amount is required").to_string()),
match amount_str.parse::<f64>() {
Ok(num) => {
if num <= 0.0 {
ValidationResult::Error(t!("Amount must be greater than zero").to_string())
} else {
ValidationResult::Success(t!("Valid amount").to_string())
Err(_) => ValidationResult::Error(t!("Please enter a valid number").to_string()),
/// Core validation logic for account IDs
pub async fn validate_account_logic(
user_id: Uuid,
account_id_str: Option<&str>,
field_name: &str,
) -> ValidationResult {
let account_id_str = match account_id_str {
_ => return ValidationResult::Error(format!("{field_name} is required")),
// Verify UUID format
let account_uuid = match Uuid::parse_str(account_id_str) {
Ok(uuid) => uuid,
Err(_) => return ValidationResult::Error(t!("Invalid account ID format").to_string()),
// Verify that the account exists and belongs to the user
match ListAccounts::new().user_id(user_id).run().await {
Ok(Some(CmdResult::TaggedEntities { entities, .. })) => {
for (entity, _) in entities {
if let FinanceEntity::Account(account) = entity
&& account.id == account_uuid
{
return ValidationResult::Success(t!("Valid account").to_string());
ValidationResult::Error(t!("Account not found or not accessible").to_string())
_ => ValidationResult::Error(t!("Error validating account").to_string()),
/// Core validation logic for notes
pub fn validate_note_logic(note: Option<&str>) -> ValidationResult {
match note {
Some(note_str) if note_str.len() > 500 => {
ValidationResult::Error(t!("Note is too long (max 500 characters)").to_string())
_ => ValidationResult::Success(t!("Note is valid").to_string()),
// HTML-based validation endpoints (for create transaction)
pub async fn validate_amount_html(Json(form): Json<ValidateAmountRequest>) -> impl IntoResponse {
let result = validate_amount_logic(form.amount.as_deref());
result.to_html_response()
pub async fn validate_from_account_html(
State(_data): State<Arc<AppState>>,
Extension(jwt_auth): Extension<JWTAuthMiddleware>,
Json(form): Json<ValidateFromAccountRequest>,
) -> impl IntoResponse {
let user = &jwt_auth.user;
let result =
validate_account_logic(user.id, form.from_account.as_deref(), "Source account").await;
pub async fn validate_to_account_html(
Json(form): Json<ValidateToAccountRequest>,
validate_account_logic(user.id, form.to_account.as_deref(), "Destination account").await;
pub async fn validate_note_html(Json(form): Json<ValidateNoteRequest>) -> impl IntoResponse {
let result = validate_note_logic(form.note.as_deref());
// JSON-based validation endpoints (for edit transaction)
pub async fn validate_amount_json(
Extension(_jwt_auth): Extension<JWTAuthMiddleware>,
Json(form): Json<ValidateAmountRequest>,
result.to_json_response()
pub async fn validate_from_account_json(
Json(form): Json<ValidateAccountRequest>,
let result = validate_account_logic(user.id, form.account_id.as_deref(), "From account").await;
pub async fn validate_to_account_json(
let result = validate_account_logic(user.id, form.account_id.as_deref(), "To account").await;
pub async fn validate_note_json(
Json(form): Json<ValidateNoteRequest>,