validate

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package validate provides Validate-step middleware for advanced field rules that cannot be expressed with static mfx struct tags.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CrossFieldValidate

func CrossFieldValidate(fn CrossFieldValidateFunc) maniflex.MiddlewareFunc

CrossFieldValidate runs an arbitrary function against ctx.ParsedBody and returns 422 if it returns an error. Use this for rules that involve multiple fields (e.g. end_date must be after start_date).

server.Pipeline.Validate.Register(validate.CrossFieldValidate(func(body map[string]any) error {
    start, _ := body["start_date"].(string)
    end, _   := body["end_date"].(string)
    if start != "" && end != "" && end <= start {
        return fmt.Errorf("end_date must be after start_date")
    }
    return nil
}), maniflex.ForModel("Event"))

func DateRange

func DateRange(startField, endField string) maniflex.MiddlewareFunc

DateRange validates that endField is not before startField. Both fields must be RFC3339 timestamps or YYYY-MM-DD date strings. If either field is absent, nil, or unparseable, the rule passes silently — pair with the `required` mfx tag or another rule when presence matters.

server.Pipeline.Validate.Register(
    validate.DateRange("start_date", "end_date"),
    maniflex.ForModel("Booking"),
    maniflex.ForOperation(maniflex.OpCreate, maniflex.OpUpdate),
)

func ForbiddenValues

func ForbiddenValues(field string, values ...string) maniflex.MiddlewareFunc

ForbiddenValues rejects the request with 422 if the named field contains any of the given values. Useful for preventing privilege escalation (e.g. a user setting their own role to "superadmin").

server.Pipeline.Validate.Register(
    validate.ForbiddenValues("role", "superadmin", "system"),
    maniflex.ForModel("User"),
    maniflex.ForOperation(maniflex.OpCreate, maniflex.OpUpdate),
)

func NumericPrecision

func NumericPrecision(field string, precision, scale int) maniflex.MiddlewareFunc

NumericPrecision enforces decimal precision and scale on an incoming numeric field. `precision` is the maximum total number of significant digits; `scale` is the maximum number of digits after the decimal point. Either limit can be disabled by passing 0.

The check is a string-parse of the JSON value, so it is independent of how the column is stored (INTEGER, NUMERIC(p,s), TEXT, DECIMAL). Numbers are normalised by trimming a leading "+"/"-", stripping the decimal point, and removing leading zeros from the integer part before counting digits.

Non-numeric strings, absent fields, and nil values are skipped — pair with the `required` mfx tag or another rule when presence matters.

server.Pipeline.Validate.Register(
    validate.NumericPrecision("amount", 19, 4), // up to 19 digits, max 4 after the point
    maniflex.ForModel("Invoice"),
)

func RegexField

func RegexField(field, pattern string) maniflex.MiddlewareFunc

RegexField validates that a field's string value matches the given regular expression. Non-string values and absent fields are silently skipped. Returns 422 if the field is present but does not match.

server.Pipeline.Validate.Register(
    validate.RegexField("phone", `^\+?[0-9\s\-]{7,15}$`),
    maniflex.ForModel("Contact"),
)

func RequireAtLeastOne

func RequireAtLeastOne(fields ...string) maniflex.MiddlewareFunc

RequireAtLeastOne returns 422 if none of the listed fields are present and non-nil in ctx.ParsedBody. Useful for PATCH endpoints where the body must contain at least one meaningful field.

server.Pipeline.Validate.Register(
    validate.RequireAtLeastOne("name", "email", "phone"),
    maniflex.ForModel("Contact"),
    maniflex.ForOperation(maniflex.OpUpdate),
)

func RequireLocale

func RequireLocale(field string, locales ...string) maniflex.MiddlewareFunc

RequireLocale ensures that a maniflex.LocaleString field (mfx:"locale") in the request body contains non-empty values for every required locale key. Returns 422 MISSING_LOCALE with the list of missing keys when any are absent.

server.Pipeline.Validate.Register(
    validate.RequireLocale("name", "en", "ar"),
    maniflex.ForModel("Department"),
    maniflex.ForOperation(maniflex.OpCreate, maniflex.OpUpdate),
)

func RequireWhen

func RequireWhen(targetField string, conditions ...string) maniflex.MiddlewareFunc

RequireWhen makes targetField required when all listed conditions are satisfied. Each condition has the form "field:op:value" where op is one of:

eq  — string equality
ne  — string inequality
gt  — numeric greater-than
gte — numeric greater-than-or-equal
lt  — numeric less-than
lte — numeric less-than-or-equal

Multiple conditions are ANDed: all must hold for the requirement to trigger. If the conditions are met but targetField is absent, nil, or empty string, the request is rejected with 422 VALIDATION_ERROR. Invalid condition syntax panics at registration time (startup), not at request time.

server.Pipeline.Validate.Register(
    validate.RequireWhen("rejection_reason", "status:eq:rejected"),
    maniflex.ForModel("Claim"),
)

server.Pipeline.Validate.Register(
    validate.RequireWhen("shipping_address", "order_type:eq:physical", "region:ne:digital"),
    maniflex.ForModel("Order"),
)

func UniqueField

func UniqueField(db DBQuerier, driver maniflex.DriverType, field string) maniflex.MiddlewareFunc

UniqueField runs a SELECT COUNT(*) before the DB step to verify that the given field's value does not already exist in the table. Returns 422 if a duplicate is found, with a user-friendly error message.

`field` is the JSON field name as it appears in ctx.ParsedBody; the actual DB column is resolved through ctx.Model.FieldByJSONName. If the JSON name has no matching field on the model, `field` is used verbatim as a DB column name (preserves backward-compat for callers that already used DB names).

`driver` selects the SQL dialect for placeholders (`$N` for Postgres, `?` for SQLite) and is required because *sql.DB does not expose its driver. Pass the same driver value that was used to open the maniflex DB adapter.

On update operations, the current record's own row is excluded from the check (so saving without changing the unique field does not produce a false positive).

server.Pipeline.Validate.Register(
    validate.UniqueField(db, maniflex.Postgres, "email"),
    maniflex.ForModel("User"),
)

Types

type CrossFieldValidateFunc

type CrossFieldValidateFunc func(body map[string]any) error

CrossFieldValidateFunc is a user-supplied multi-field validation function. Return a non-nil error to fail validation; the error message is included in the 422 response.

type DBQuerier

type DBQuerier interface {
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

DBQuerier is the minimal interface UniqueField needs to check uniqueness. *sql.DB satisfies this interface; so does any wrapper that forwards QueryRowContext (e.g. a test fake).

Jump to

Keyboard shortcuts

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