Custom impl of `serde🇩🇪Error` possible?

So I have an EmailAddress and want to have a custom response when serde fails to deserialize it.

impl EmailAddress {
    pub fn parse(s: String) -> Result<Self, EmailAddressParseError> {
        match validate_email(&s) {
            true => Ok(Self(s)),
            false => Err(EmailAddressParseError),
        }
    }
}
#[derive(Debug)]
pub struct EmailAddressParseError;

impl std::fmt::Display for EmailAddressParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Email validation failed")
    }
}

impl std::error::Error for EmailAddressParseError {}

impl de::Error for EmailAddressParseError {
    fn custom<T>(_msg: T) -> Self
    where
        T: fmt::Display,
    {
        Self // I always and only want the text "Email validation failed"
    }
}
struct EmailAddressVisitor;

impl<'de> Visitor<'de> for EmailAddressVisitor {
    type Value = EmailAddress;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a valid email address")
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        // Attempt 1:
        // Simply return: `EmailAddress::parse(value.to_string())`
        // Even though this method returns a Result<_, EmailAddressParseError>
        // which impls de::Error, rust won't let me return it with the error:
        // ```
        // mismatched types
        // expected enum `Result<_, E>`
        // found enum `Result<_, EmailAddressParseError>`
        // ```

        // Attempt 2:
        EmailAddress::parse(value.to_string()).map_err(|e| E::custom(e))
        
    }
}

As it currently stands, I do get my custom "Email validation failed" error message, but it's prefixed with "Parse error: "

is there anyway to remove that?

So the issue here is that <EmailAddressVisitor as Visit>::parse_str doesn't return Result<_, EmailAddressParseError> like you are wanting it to. Rather, it returns some arbitrary E: de::Error determined by the caller; typically, the deserialization format's Error. (E.g. serde_json::Error.)

Whatever deserialization format you're using implements de::Error::custom to have a display of "Parse error: {custom}", so that's what you're seeing when you display the error.

There are a few ways to get the result you want:

  • Deserialize into a more permissive type (any string) and then validate/transcode to a more restrictive type, doing your own error reporting.
  • Detect your magic string in the custom error after the fact and wrap the error in your own error reporting logic.
  • Do some trait magic to inject your own de::Error type (for the entire deserialization! the whole deserialization uses the same error type) and recognize a sentinel custom error that way.

.... but also, validate_email, if it's any more restrictive than str.contains('@'), is probably forbidding valid email addresses, because the email address spec is a lot more permissive than most people realize.

1 Like

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.