How to use a custom async validator with the validator crate?

Hello. I am trying to create a custom validator that checks if a given value is unique depending on the field and table specified. I am using the validator crate with actix-web and seaorm. Here is my custom validator.

use sea_orm::{DbBackend, DbConn, FromQueryResult, JsonValue, Statement};
use validator::ValidationError;

pub async fn unique(value: &str, arg: (&str, &str, &DbConn)) -> Result<(), ValidationError> {
    let (field, table, conn) = arg;
    let query = format!(
        "SELECT * FROM {} WHERE {} = {} ORDER BY id LIMIT 1",
        table, field, value
    );

    let unique: Vec<JsonValue> = JsonValue::find_by_statement(Statement::from_sql_and_values(
        DbBackend::Postgres,
        &query,
        [],
    ))
    .all(conn)
    .await
    .unwrap();

    if !unique.is_empty() {
        return Err(ValidationError::new("unique constraint error"));
    }

    Ok(())
}

I am trying to use the validator in the a struct like so

use chrono::Utc;

use serde::{Deserialize, Serialize};
use uuid::Uuid;
use validator::Validate;

use crate::{entities::user::Model, validators::unique::unique};

#[derive(Serialize, Deserialize, Validate)]
pub struct CreateUserDTO {
    #[validate(required(message = "Name is required"))]
    pub name: Option<String>,

    #[validate(
        required(message = "Email is required"),
        email(message = "Invalid email format"),
        length(
            min = 3,
            message = "Email is too short. It must be at least 3 characters long"
        ),
        custom(
            function = "unique",
            arg = "(&'v_a field, &'v_a table, &'v_a DbConn)"
        )
    )]
    pub email: Option<String>,

    #[validate(
        required(message = "Password is required"),
        length(
            min = 8,
            message = "Password is too short. It must be at least 8 characters long"
        )
    )]
    pub password: Option<String>,
}

Now I am getting this error and I don't even know what it means.

error[E0308]: mismatched types
 --> src/dto/create_user.rs:9:34
  |
9 | #[derive(Serialize, Deserialize, Validate)]
  |                                  ^^^^^^^^
  |                                  |
  |                                  expected opaque type, found enum `Result`
  |                                  this expression has type `impl std::future::Future<Output = Result<(), ValidationError>>`
  |
note: while checking the return type of the `async fn`
 --> src/validators/unique.rs:4:65
  |
4 | pub async fn unique(value: &str, arg: (&str, &str, &DbConn)) -> Result<(), ValidationError> {
  |                                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ checked the `Output` of this `async fn`, expected opaque type
  = note: expected opaque type `impl std::future::Future<Output = Result<(), ValidationError>>`
                    found enum `Result<_, _>`
  = note: this error originates in the derive macro `Validate` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider `await`ing on the `Future`
  |
9 | #[derive(Serialize, Deserialize, Validate.await)]
  |                                          ++++++

I would really appreciate if anyone can help me with this. Thanks

If validator does not support async functions and you are using actix-web, you could wrap your future in a blocking call to make it synchronous. You'd need to import tokio with the rt feature enabled though. This is what I imagine will work:

use tokio::runtime::Handle;

pub fn unique(value: &str, arg: (&str, &str, &DbConn)) -> Result<(), ValidationError> {
    Handle::current().block_on(unique_inner(value, arg))
}

async fn unique_inner(value: &str, arg: (&str, &str, &DbConn)) -> Result<(), ValidationError> { ... }
2 Likes

Oh. This is a really nice approach. Thanks for the reply. I am going to try this.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.