Course contentsShow
← Rust
Lesson 891 of 1,9107. Error Handling PatternsFree lesson

Validation Logic Encapsulation

Isolate input validation logic from operational business logic using custom error types.

In this lesson, you will learn how to isolate input validation from your core business logic by leveraging custom error types, ensuring your operational code remains clean and focused.

The Problem: Cluttered Logic

When we mix validation checks—like range constraints or string format requirements—directly into our business logic, we create "noise." An update_user_balance function should ideally be concerned with arithmetic, not whether an email address contains an @ symbol. If these concerns are intertwined, the function becomes hard to test and fragile.

The Solution: Encapsulation

To clean this up, we extract validation into its own domain. By defining a custom ValidationError type, we can return specific, descriptive errors before our business logic even begins. This ensures that when your main function executes, it can operate under the "happy path" assumption, knowing the data is already proven valid.

pub enum ValidationError {
    InvalidEmail,
    AmountTooLow,
}

struct Email(String);

impl Email {
    pub fn try_new(value: String) -> Result<Self, ValidationError> {
        if value.contains('@') {
            Ok(Self(value))
        } else {
            Err(ValidationError::InvalidEmail)
        }
    }
}

// Business logic remains clean:
pub fn process_payment(email: Email, amount: u64) -> Result<(), BusinessError> {
    // We no longer need to check if the email is valid here!
    // The type system guarantees it.
    Ok(())
}

Why This Matters

By encapsulating validation:

  1. Separation of Concerns: Your business logic focuses on "what" the system does, while validation types focus on "what" the data looks like.
  2. Type Safety: By using the "Newtype" pattern (as shown with Email), you transform runtime validation into a compile-time guarantee. Once an Email type exists, you never have to re-validate it elsewhere in your pipeline.
  3. Contextual Errors: Your custom error type can carry diagnostic information specific to the failure, making your system’s "invalid state" reports much more actionable for debugging.

Key Takeaway: Encapsulate validation into dedicated types and functions to shift error checking to the boundaries of your application, keeping your core business logic pure and predictable.