Docs Request Validation

Request Validation

Every external request is untrusted.

Validation is therefore unavoidable —
but validation placed at the wrong layer is worse than no validation at all.

Plumego intentionally does not provide a built-in, automatic validation framework.

This is not an omission.

It is a recognition that validation strategy is an architectural decision, not a framework default.

This guide explains how to perform request validation in Plumego without violating boundaries or polluting core logic.


Two Fundamentally Different Kinds of Validation

Before writing any validation code, it is critical to distinguish between two conceptually different concerns.

1. Request Validation (Boundary Validation)

Request validation answers the question:

“Can this request be understood by the system?”

It focuses on:

  • Payload format
  • Required fields
  • Basic type correctness
  • Obviously invalid values

This is boundary-level validation.


2. Business Validation (Domain Validation)

Business validation answers a very different question:

“Is this operation allowed?”

It focuses on:

  • Business rules
  • State transitions
  • Invariants
  • Permissions and intent

This is domain and application logic.

Mixing these two forms of validation leads directly to architectural decay.


Validation Layering Principles

Plumego encourages a layered validation model, where each layer validates only what it owns.

Validation Type Proper Layer
JSON syntax Handler
Required fields Handler
Basic type / range checks Handler
Authentication Middleware
Authorization Handler / Usecase
Business rules Domain
Cross-entity rules Usecase

There is no single “validation layer”.

Each layer validates only what it is responsible for.


What Belongs in Handlers

Handlers sit at the HTTP boundary.

Their validation goal is simple:

“Is this request structurally valid enough to enter the system?”

Appropriate Handler-Level Validation

Handlers should validate:

  • Whether the request body can be parsed
  • Whether required fields are present
  • Whether basic values are sane (empty, negative, malformed)

Example:

type CreateUserRequest struct {
	Email string `json:"email"`
	Age   int    `json:"age"`
}

func CreateUserHandler(ctx *plumego.Context) {
	var req CreateUserRequest
	if err := ctx.BindJSON(&req); err != nil {
		ctx.JSON(http.StatusBadRequest, errorResponse("invalid JSON"))
		return
	}

	if req.Email == "" {
		ctx.JSON(http.StatusBadRequest, errorResponse("email is required"))
		return
	}

	if req.Age <= 0 {
		ctx.JSON(http.StatusBadRequest, errorResponse("age must be positive"))
		return
	}

	// Passed boundary validation
}

This validation is:

  • Explicit
  • Local
  • Easy to reason about

What Does Not Belong in Handlers

Handlers must not validate:

  • Whether a user already exists
  • Whether an operation is allowed in the current state
  • Whether a balance is sufficient
  • Whether a rule is violated

Handlers should not know why an operation is forbidden —
only how to translate the result into HTTP.


Validation in the Usecase Layer

Usecases validate process-level and authorization-level rules.

Examples include:

  • Whether a user may perform an action
  • Whether a workflow step is allowed
  • Whether multiple domain objects can interact

Example:

func (u *CreateOrderUsecase) Execute(input Input) error {
	if !u.permission.CanCreateOrder(input.UserID) {
		return ErrForbidden
	}

	// Coordinate domain logic
	return nil
}

Usecase validation is:

  • Context-aware
  • Workflow-driven
  • Explicit in intent

Validation in the Domain Layer

The domain layer enforces invariants — rules that must always hold.

Example:

func (o *Order) Cancel() error {
	if o.Status == Shipped {
		return ErrCannotCancel
	}
	o.Status = Cancelled
	return nil
}

Domain validation characteristics:

  • Independent of HTTP
  • Independent of identity sources
  • Always correct, regardless of caller

If a domain invariant fails, the system has been used incorrectly.


Avoiding “Automatic Validation Frameworks”

In Plumego-style systems, be cautious with:

  • Struct-tag-driven auto validators
  • Reflection-heavy global validation
  • One-line Validate() calls that hide logic

Problems with automatic validation:

  • Validation location becomes implicit
  • Boundary and business validation get mixed
  • Complex rules become awkward or impossible
  • Failure reasons become opaque

Explicit validation may be verbose,
but verbosity is preferable to hidden behavior.


Mapping Validation Failures to HTTP Responses

Recommended conventions:

  • Boundary validation failure → 400 Bad Request
  • Authentication failure → 401 Unauthorized
  • Authorization failure → 403 Forbidden
  • Business rule violation → Domain / Usecase error → mapped by handler

Domain and usecase code must never return HTTP status codes.


Avoiding Duplicate Validation

A common mistake is defensive over-validation:

  • Handler validates
  • Usecase re-validates the same condition
  • Domain validates again

This usually indicates unclear responsibility boundaries.

Correct approach:

  • Each layer validates only what it owns
  • No “just in case” validation
  • No duplicated rules

Validation and Testing

Clear validation boundaries dramatically simplify testing:

  • Handler tests focus on malformed input
  • Usecase tests focus on workflow and permissions
  • Domain tests focus on invariants

Each test suite becomes smaller and more precise.


Common Anti-Patterns

Putting All Validation in Handlers

Results in:

  • Bloated handlers
  • Scattered business rules
  • Poor reusability

Validating HTTP Data Inside Domain Code

This is a severe boundary violation.

Domain logic must not know what a “request” is.


Using One Validator to Rule Them All

This is an attempt to avoid architectural thinking —
and usually creates more problems than it solves.


Summary

In Plumego:

  • Validation is a chain, not a single step
  • Each layer validates its own responsibility
  • Handlers validate structure
  • Usecases validate process
  • Domains validate invariants

Correct validation placement is one of the highest-leverage architectural decisions you can make.


Next

If you now understand:

  • Thin Handlers
  • Middleware
  • Request Validation

The next recommended reading is:

→ Patterns / Explicit Middleware Chains

This elevates middleware from a utility to an architectural building block.