Skip to content

Error Customization

Hetman Pipeline provides flexible error customization through the ERROR_TEMPLATES system. You can customize error messages globally, per handler, or per handler mode.


Understanding ERROR_TEMPLATES

Condition and match handlers have an ERROR_TEMPLATES class variable that defines error messages for different handler modes.

Handler Modes

Mode Description
HandlerMode.ROOT Handler processes the value directly
HandlerMode.ITEM Handler processes each item in a collection
HandlerMode.CONTEXT Handler uses a value from the pipeline context

Basic Error Template Structure

from pipeline.handlers.base_handler.resources.constants import HandlerMode
from pipeline.handlers.base_handler.condition_handler import ConditionHandler

class Condition(ConditionHandler):
    ERROR_TEMPLATES = {
        HandlerMode.ROOT: lambda self: "Error message here"
    }

The lambda function receives self (the handler instance) and returns a error message any type.


Customization Levels

1. Quick Assignment (Most Common)

Directly override the ERROR_TEMPLATES dictionary for a specific handler:

from pipeline.core.pipe.pipe import Pipe
from pipeline.handlers.base_handler.resources.constants import HandlerMode

# Customize the Email validator error message
Pipe.Match.Format.Email.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda _: "Please provide a valid email address."

# Now use it
result = Pipe(
    value="invalid-email",
    type=str,
    matches={Pipe.Match.Format.Email: None}
).run()

print(result.match_errors)
# [{'id': 'email', 'msg': 'Please provide a valid email address.', 'value': 'invalid-email'}]

2. Dynamic Error Messages

Use handler properties to create dynamic error messages:

from pipeline.core.pipe.pipe import Pipe
from pipeline.handlers.base_handler.resources.constants import HandlerMode

# Customize MinLength to show the expected length
Pipe.Condition.MinLength.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda self: (
    f"Value must be at least {self.argument} characters long."
)

result = Pipe(
    value="Hi",
    type=str,
    conditions={Pipe.Condition.MinLength: 10}
).run()

print(result.condition_errors)
# [{'id': 'min_length', 'msg': 'Value must be at least 10 characters long.', 'value': 'Hi'}]

3. Mode-Specific Customization

Customize error messages for different handler modes:

from pipeline.core.pipe.pipe import Pipe
from pipeline.handlers.base_handler.resources.constants import HandlerMode
from pipeline.handlers.base_handler.handler_modifiers import Item

# Customize for both ROOT and ITEM modes
Pipe.Match.Format.Email.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda _: "Invalid email address."
Pipe.Match.Format.Email.ERROR_TEMPLATES[HandlerMode.ITEM] = lambda self: f"Invalid email at position {self._item_index}."

# ROOT mode
result = Pipe(
    value="invalid",
    type=str,
    matches={Pipe.Match.Format.Email: None}
).run()

print(result.match_errors[0]['msg'])
# "Invalid email address."

# ITEM mode
result = Pipe(
    value=["valid@email.com", "invalid", "another@email.com"],
    type=list,
    matches={Item(Pipe.Match.Format.Email): None}
).run()

print(result.match_errors[0][1]['msg'])
# "Invalid email at position 1."

Real-World Examples

Example 1: Localized Error Messages

from contextvars import ContextVar
from pipeline.core.pipe.pipe import Pipe
from pipeline.handlers.base_handler.resources.constants import HandlerMode

locale = ContextVar("locale", default="en")

i18n = {
    "en": "Too short. This must be at least {argument} characters.",
    "pl": "Za krótkie. To musi mieć conajmniej {argument} znaków."
}

Pipe.Condition.MinLength.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda self: (
    i18n.get(locale.get(), i18n["en"]).format(argument=self.argument)
)

# Test
result_en = Pipe(
    value="invalid",
    type=str,
    conditions={Pipe.Condition.MinLength: 100}
).run()

print(result_en.condition_errors)
# [{'id': 'min_length', 'msg': 'Too short. This must be at least 100 characters.', 'value': 'invalid'}]

locale.set("pl")

result_pl = Pipe(
    value="invalid",
    type=str,
    conditions={Pipe.Condition.MinLength: 100}
).run()

print(result_pl.condition_errors)
# [{'id': 'min_length', 'msg': 'Za krótkie. To musi mieć conajmniej 100 znaków.', 'value': 'invalid'}]

Example 2: Context-Aware Error Messages

from pipeline.core.pipe.pipe import Pipe
from pipeline.handlers.base_handler.resources.constants import HandlerMode

# Customize to show both the value and the argument
Pipe.Condition.MinNumber.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda self: (
    f"Value {self.value} is below the minimum of {self.argument}."
)

Pipe.Condition.MaxNumber.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda self: (
    f"Value {self.value} exceeds the maximum of {self.argument}."
)

result = Pipe(
    value=5,
    type=int,
    conditions={
        Pipe.Condition.MinNumber: 10,
        Pipe.Condition.MaxNumber: 100
    }
).run()

print(result.condition_errors[0]['msg'])
# "Value 5 is below the minimum of 10."

Advanced: Custom Error Builder

For complete control over the error structure, you can customize the ERROR_BUILDER function. This allows you to change not just the message, but the entire error object structure.

Per-Handler Error Builder

from pipeline.core.pipe.pipe import Pipe
from pipeline.handlers.base_handler.resources.constants import HandlerMode

def custom_email_error_builder(handler):
    """Custom error builder with detailed information"""
    return {
        "field": handler.id,
        "message": "Invalid email format",
        "value": handler.value,
        "suggestion": "Email must be in format: user@domain.com",
        "examples": ["john@example.com", "jane.doe@company.org"]
    }

# Assign the custom builder to a specific handler
Pipe.Match.Format.Email.ERROR_BUILDER = custom_email_error_builder

Global Error Builder

You can also change the ERROR_BUILDER globally for all condition or match handlers:

from pipeline.handlers.condition_handler.condition_handler import ConditionHandler
from pipeline.handlers.match_handler.match_handler import MatchHandler

def global_condition_error_builder(handler):
    """Global error builder for all condition handlers"""
    return {
        "error_type": "validation_error",
        "handler": handler.id,
        "message": handler.error_msg,
        "value": handler.value,
        "timestamp": datetime.now()
    }

def global_match_error_builder(handler):
    """Global error builder for all match handlers"""
    return {
        "error_type": "format_error",
        "handler": handler.id,
        "message": handler.error_msg,
        "value": handler.value
    }

# Apply globally to all condition handlers
ConditionHandler.ERROR_BUILDER = global_condition_error_builder

# Apply globally to all match handlers
MatchHandler.ERROR_BUILDER = global_match_error_builder

Global vs Per-Handler

When you set ERROR_BUILDER on a base class (like ConditionHandler), it affects all handlers that inherit from it. Setting it on a specific handler (like Pipe.Match.Format.Email) only affects that handler.


Accessing Handler Properties

Inside the error template lambda, you have access to all handler properties:

Property Description
self.value The value being validated
self.argument The handler's argument
self.id Handler identifier (snake_case)
self._item_index Current item index (in ITEM mode)
self.context Pipeline context
self.metadata Pipe metadata

Best Practices

Keep Messages User-Friendly

Write error messages for end users, not developers:

# Bad
lambda _: "Regex pattern mismatch"

# Good
lambda _: "Please enter a valid phone number (e.g., +1234567890)"

Include Expected Values

Help users understand what's expected:

lambda self: f"Must be between {self.argument} and 100 characters"

Don't Expose Sensitive Data

Be careful not to expose sensitive information in error messages:

# Bad - exposes password
lambda self: f"Password '{self.value}' is too weak"

# Good
lambda self: "Password does not meet security requirements"

Consistency Across Modes

If you customize HandlerMode.ROOT, consider customizing HandlerMode.ITEM too:

ERROR_TEMPLATES = {
    HandlerMode.ROOT: lambda _: "Invalid email address",
    HandlerMode.ITEM: lambda self: f"Invalid email at position {self._item_index}"
}

Resetting to Defaults

If you need to reset error messages to their defaults, you can reload the handler class or manually restore the original templates.

# Save original before customizing
original_template = Pipe.Match.Format.Email.ERROR_TEMPLATES.copy()

# Customize
Pipe.Match.Format.Email.ERROR_TEMPLATES[HandlerMode.ROOT] = lambda _: "Custom message"

# Restore later
Pipe.Match.Format.Email.ERROR_TEMPLATES = original_template

Next Steps