validator

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package validator provides lightweight, fluent input validation for express handlers, in the spirit of the npm "express-validator" package. Instead of mirroring express-validator's global check()/body()/query() functions and its validationResult() accumulator, this port models validation as a Schema value: an ordered list of per-field rule chains that you build once and reuse as a plain function or as request middleware. Build a Schema of field rules and either validate a map directly with Schema.Validate or mount it with Schema.Body / Schema.Query so that invalid requests are rejected with a 400 JSON response before your handler runs.

schema := validator.Schema{
	validator.Field("email").Required().Email(),
	validator.Field("age").Optional().IsInt().Min(0).Max(120),
	validator.Field("name").Required().MinLen(2).MaxLen(50),
}

app.Post("/users", schema.Body(), createUser)

You reach for this package when an express route needs to guard its body or query string against missing, malformed, or out-of-range fields without pulling in a large dependency. Each Field starts a chain, and the chained methods (Required, Optional, Email, MinLen, MaxLen, Min, Max, IsInt, IsNumber, In, Matches, Custom) append rules that are evaluated in the order they were declared. Rule chains are fluent: every method returns the same *FieldRules pointer, so a whole field can be described in a single readable expression that reads much like the express-validator DSL it emulates.

Mechanically, a rule is a func(value any, present bool) string. It receives the raw field value together with a flag indicating whether the key was present in the incoming map, and returns an empty string when the value is acceptable or a human-readable message when it is not. Schema.Validate walks each field, looks the value up in the supplied map[string]any, and runs the field's rules in sequence, collecting at most one message per field (the first that fails) into an Errors slice. Numeric rules coerce strings via strconv, string-length rules count bytes with len, Email matches a pragmatic regular expression (a local part, an "@", and a dotted domain), and Matches compiles an arbitrary caller-supplied pattern with regexp.MustCompile.

Presence and emptiness have deliberate semantics. A value is "empty" when it is nil or a string that is blank after trimming whitespace. Required fails on a missing or empty value; Optional causes the entire remaining chain to be skipped when the field is absent or empty, so optional fields validate only when the caller actually supplied something. Every other rule treats an absent value as passing, which means order matters: pair a value constraint with Required (or Optional) to decide what happens when the field is not sent. The middleware forms normalize their inputs first, flattening url.Values (from a query string or urlencoded body) so that only the first value of each key is validated as a string.

Compared with the Node original, the surface here is intentionally smaller and synchronous: there are no sanitizers, no async/custom-promise validators, no wildcard or nested-path selectors, and no per-rule custom messages beyond what Custom returns. What does match is the spirit of a declarative, chainable, per-field rule set and the 400-with-errors behavior of the middleware, whose JSON payload is {"errors": [{"field", "message"}, ...]}. For raw single-value string checks (IsEmail, IsURL, IsUUID, and friends) that correspond to validator.js, use the sibling validatorjs package instead.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Errors

type Errors []FieldError

Errors is a collection of validation failures.

Example

ExampleErrors shows how the Errors slice satisfies the error interface. When a schema produces one or more FieldError values, calling Error on the slice joins them into a single summary string prefixed with "validation failed:". Here both required fields are missing, so two failures are collected. The combined message lists each field and its reason separated by semicolons. This is convenient when you want to log or return one flat string rather than iterating the individual failures.

package main

import (
	"fmt"

	"github.com/malcolmston/express/validator"
)

func main() {
	schema := validator.Schema{
		validator.Field("email").Required(),
		validator.Field("name").Required(),
	}

	errs := schema.Validate(map[string]any{})

	fmt.Println(errs.Error())
}
Output:
validation failed: email is required; name is required

func (Errors) Error

func (e Errors) Error() string

Error implements the error interface, summarizing the failures.

type FieldError

type FieldError struct {
	// Field is the name of the field that failed validation.
	Field string `json:"field"`
	// Message is the human-readable reason the field was rejected.
	Message string `json:"message"`
}

FieldError is a single validation failure.

type FieldRules

type FieldRules struct {
	// contains filtered or unexported fields
}

FieldRules accumulates the rules applied to a single named field.

func Field

func Field(name string) *FieldRules

Field starts a rule chain for the named field.

Example

ExampleField demonstrates that a valid input map produces no errors. The chain marks name as required with a minimum length and treats age as an optional integer, so omitting age entirely is acceptable because Optional skips the rest of that field's chain when the value is absent. Every supplied value satisfies its rule, so Schema.Validate returns a nil Errors slice whose length is zero. This mirrors how you would gate a handler: an empty result means the data is safe to use. The example prints the error count to show the happy path.

package main

import (
	"fmt"

	"github.com/malcolmston/express/validator"
)

func main() {
	schema := validator.Schema{
		validator.Field("name").Required().MinLen(2).MaxLen(50),
		validator.Field("age").Optional().IsInt().Min(0),
	}

	errs := schema.Validate(map[string]any{
		"name": "Ada",
	})

	fmt.Println("errors:", len(errs))
}
Output:
errors: 0

func (*FieldRules) Custom

func (f *FieldRules) Custom(fn func(value any) string) *FieldRules

Custom applies a user-supplied validation function. Returning a non-empty string records that message as an error.

func (*FieldRules) Email

func (f *FieldRules) Email() *FieldRules

Email validates that the value is a syntactically valid email address.

func (*FieldRules) In

func (f *FieldRules) In(allowed ...string) *FieldRules

In requires the value to be one of the allowed strings.

func (*FieldRules) IsInt

func (f *FieldRules) IsInt() *FieldRules

IsInt requires the value to be an integer.

func (*FieldRules) IsNumber

func (f *FieldRules) IsNumber() *FieldRules

IsNumber requires the value to be numeric.

func (*FieldRules) Matches

func (f *FieldRules) Matches(pattern string) *FieldRules

Matches requires the string value to match the given regular expression.

func (*FieldRules) Max

func (f *FieldRules) Max(n float64) *FieldRules

Max requires a numeric value <= n.

func (*FieldRules) MaxLen

func (f *FieldRules) MaxLen(n int) *FieldRules

MaxLen requires the string value to be at most n characters.

func (*FieldRules) Min

func (f *FieldRules) Min(n float64) *FieldRules

Min requires a numeric value >= n.

func (*FieldRules) MinLen

func (f *FieldRules) MinLen(n int) *FieldRules

MinLen requires the string value to be at least n characters.

func (*FieldRules) Optional

func (f *FieldRules) Optional() *FieldRules

Optional marks the field as optional: when absent, remaining rules are skipped instead of failing.

func (*FieldRules) Required

func (f *FieldRules) Required() *FieldRules

Required fails when the field is missing or an empty string.

type Schema

type Schema []*FieldRules

Schema is an ordered set of field rules.

func (Schema) Body

func (s Schema) Body() express.Handler

Body returns express middleware that validates the parsed request body. It expects a preceding body-parser (express.JSON or express.URLEncoded). On failure it responds 400 with {"errors": [...]}; on success it calls next.

Example

ExampleSchema_Body mounts a Schema as express request middleware and drives it with httptest to show the rejection path. Schema.Body validates the parsed request body and, on failure, responds with HTTP 400 and a JSON object of the form {"errors": [...]} before the downstream handler ever runs. Here the body is registered directly as a map so the validator sees a missing required field. The recorder captures the 400 status and the JSON payload naming the field. Because the response is deterministic, the Output block asserts both.

package main

import (
	"fmt"
	"net/http/httptest"
	"strings"

	"github.com/malcolmston/express"
	"github.com/malcolmston/express/validator"
)

func main() {
	schema := validator.Schema{
		validator.Field("email").Required().Email(),
	}

	app := express.New()
	app.Use(func(req *express.Request, res *express.Response, next express.Next) {
		req.SetBody(map[string]any{"email": ""})
		next()
	})
	app.Post("/users", schema.Body(), func(req *express.Request, res *express.Response, next express.Next) {
		res.Send("created")
	})

	rec := httptest.NewRecorder()
	app.ServeHTTP(rec, httptest.NewRequest("POST", "/users", strings.NewReader("")))

	fmt.Println("status:", rec.Code)
	fmt.Println("body:", strings.TrimSpace(rec.Body.String()))
}
Output:
status: 400
body: {"errors":[{"field":"email","message":"is required"}]}

func (Schema) Query

func (s Schema) Query() express.Handler

Query returns express middleware that validates the request's query string.

func (Schema) Validate

func (s Schema) Validate(data map[string]any) Errors

Validate runs the schema against a data map, returning all failures (nil when valid).

Example

ExampleSchema_Validate builds a Schema with a required e-mail field and an optional, bounded integer field, then runs it against a map that violates both constraints. Schema.Validate walks the fields in declaration order and records at most one message per field, so the resulting Errors slice is fully deterministic. The e-mail value fails the Email rule and the age value, once coerced from its string form, fails the Max rule. Each FieldError pairs the offending field name with a human-readable message. The Output block below confirms both failures are reported in field order.

package main

import (
	"fmt"

	"github.com/malcolmston/express/validator"
)

func main() {
	schema := validator.Schema{
		validator.Field("email").Required().Email(),
		validator.Field("age").Optional().IsInt().Min(0).Max(120),
	}

	errs := schema.Validate(map[string]any{
		"email": "not-an-email",
		"age":   "200",
	})

	for _, e := range errs {
		fmt.Printf("%s: %s\n", e.Field, e.Message)
	}
}
Output:
email: must be a valid email address
age: must be <= 120

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL