Skip to content

← Errors Are Values step 14 of 24

Medium Primitives

Define a structured validation error

Implement a name validator with a structured error type. Callers should be able to inspect which rule failed and display a useful message.

pub enum ValidationError {
    Empty,
    TooLong { len: usize, max: usize },
    BadChar { ch: char, index: usize },
}

pub fn validate(name: String, max: usize) -> Result<String, ValidationError>

Keep the variant and field names shown above, derive Debug, implement std::fmt::Display, and implement std::error::Error by hand.

Validate in this order

  1. If name is empty, return Empty, even when max is zero.
  2. Count the Unicode scalar values with name.chars().count(). If the count exceeds max, return TooLong { len, max }.
  3. Find the first character for which ch.is_alphanumeric() is false and ch != '_'. Return BadChar with that character and its zero-based position in name.chars().
  4. Otherwise return the original name unchanged.

Do not trim, lowercase or normalize the input. A whitespace-only name is not empty: it fails the length rule or character rule. The length rule takes precedence even if an invalid character occurs at the start.

Here, a character means a Unicode scalar value, not a byte or a user-perceived character. "héllo" contains five scalar values and six UTF-8 bytes. A visually similar decomposed spelling can have a different count and validation result. Use the specified Unicode-aware predicate rather than an ASCII-only check.

Exact error output

The harness checks the Display message and derived Debug output:

Error Display Debug
Empty name must not be empty Empty
TooLong { len: 11, max: 5 } name is 11 characters, maximum is 5 TooLong { len: 11, max: 5 }
BadChar { ch: '-', index: 2 } invalid character '-' at index 2 BadChar { ch: '-', index: 2 }

Substitute the actual values in each message. Keep the wording characters even when the length is one. For BadChar, put the character's Display form between literal single quotes, as in write!(f, "invalid character '{ch}' at index {index}"). Do not add escaping to that message. Derived Debug supplies its own character escaping separately.

Name Maximum Result
"ada" 10 Ok("ada")
"a_b9" 4 Ok("a_b9")
"日本語" 3 Ok("日本語")
"-" 0 TooLong { len: 1, max: 0 }
"é-" 2 BadChar { ch: '-', index: 1 }

These are validation failures with no underlying cause. Leave source() at its default None; the harness checks that there is no source. Successful results contain the original string, including its original spelling.

Implement the formatting trait

The starter derives Debug but does not implement Display. Its empty Error implementation therefore fails with E0277: Error requires both supertraits.

Add impl fmt::Display for ValidationError. Its method has this signature:

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

Match the three variants and return each arm's write!(f, ...) result. This passes any formatting failure back to the caller. The trait signature above is a fragment to put inside the implementation, not a standalone program.

Once Display is implemented, .to_string() is available through the standard blanket implementation of ToString. The existing empty Error implementation needs no methods for this type. Lowercase messages without trailing punctuation follow the standard library's error-message convention and this task's contract.

What Error adds

Error provides a common interface for reporting errors and inspecting optional causes. Implementing it does not automatically convert your type into every other error type: ? still needs a suitable conversion into the enclosing function's error type. Standard conversions do support boxing compatible error types into Box<dyn Error>.

Send, Sync and 'static are useful bounds for many error APIs, but they are not supertraits of Error. This enum satisfies them because it contains only usize and char fields. A borrowed or thread-local error type can still implement Error while being unsuitable for APIs requiring those extra bounds.

See the Error trait documentation and character predicate. This exercise requires compilation, passing tests and Clippy with warnings denied. Its Debug fixtures reflect the supported compiler; Debug text is not a stable interchange format across toolchain changes.