datacop

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jan 4, 2025 License: Apache-2.0 Imports: 3 Imported by: 0

README

Datacop

Go Reference Go Report Card

Datacop is a validation library for Go applications that prioritizes type safety and readability over magic.

Features

  • 🔒 Type-safe validations with generics support
  • ⚡ No reflection-based struct tags
  • 🎯 Explicit, readable validation logic
  • 🔗 Chainable validations
  • 🌳 Support for nested validation groups
  • 🎭 Conditional validations
  • ✨ Common set of built-in validation functions

Installation

go get github.com/patrickward/datacop

Quick Start

package main

import (
    "fmt"
    "github.com/patrickward/datacop"
)

func main() {
    v := datacop.New()
    
    // Simple field validation
    username := "john"
    v.Check(is.Required(username), "username", "username is required")
    
    // Chainable validation
    email := "invalid-email"
    v.Field("email", email).
        Check(is.Required(email), "email is required").
        Check(is.Email(email), "invalid email format")
    
    if v.HasErrors() {
        fmt.Println(v.Errors())
        return
    }
}

Design Philosophy

Datacop intentionally favors explicit validation over struct tag-based validation for:

  • Better compile-time type safety
  • Clearer validation logic
  • More flexible conditional validations
  • Easier testing
  • No reflection overhead

Documentation

See the package documentation for detailed usage examples and API reference.

Common Validation Patterns
v := datacop.New()

// Explicit checks 
v.Check(is.Required(username), "username is required")
v.Check(is.Email(email), "invalid email format")
v.CheckStandalone(is.Min(18)(age), "must be 18 or older")

// Password validation
v.Field("password", password).
    Check(is.Required(password), "password is required").
    Check(is.MinLength(8)(password), "password too short").
    Check(is.Match(`[A-Z]`)(password), "must contain uppercase").
    Check(is.Match(`[a-z]`)(password), "must contain lowercase").
    Check(is.Match(`[0-9]`)(password), "must contain number")

// Grouped validation
userGroup := v.Group("user")
userGroup.Field("name", name).
    Check(is.Required(name), "name is required")
userGroup.Field("age", age).
    Check(is.Min(18)(age), "must be 18 or older")

// Conditional validation
v.Field("company", company).
    When(isEmployed).
    Check(is.Required(company), "company required")

Documentation

Overview

Package datacop provides a fluent validation library for Go applications.

Note: The validator is not thread-safe. Each validator instance should be used within a single goroutine, or external synchronization should be used when accessing from multiple goroutines.

Design Philosophy

This package intentionally favors explicit validation over struct tag-based validation for several reasons: - Better compile-time type safety and IDE support - Clearer validation logic that's easier to read and maintain - More flexible support for complex conditional validations - Easier to unit test individual validation rules - No reflection overhead

The package offers two main validation styles: 1. Direct validation using Check methods 2. Chainable validation using Field builder

Basic Usage

The simplest way to use the validator is with direct Check calls:

v := datacop.New()

// Validate username
if !v.Check(Required(username), "username", "username is required") {
	return v.Errors() // returns map[string]string
}

// Validate multiple fields
v.Check(Required(email), "email", "email is required")
v.Check(Email(email), "email", "invalid email format")
v.Check(MinLength(8)(password), "password", "password too short")

if v.HasErrors() {
	return v.Error() // returns formatted error string
}

Chainable Validation

For more complex validations, use the fluent Field builder:

v := datacop.New()

// Chain validations for username
v.Field("username", username).
	Check(is.Required(username), "username is required").
	Check(is.MinLength(3)(username), "username too short").
	Check(is.MaxLength(255)(username), "username too long")

// Chain validations for password
v.Field("password", password).
	Check(is.Required(password), "password is required").
	Check(is.MinLength(8)(password), "password too short").
	Check(is.Match(`[A-Z]`)(password), "must contain uppercase").
	Check(is.Match(`[0-9]`)(password), "must contain number")

Grouped Validation

For nested structures, use Group to namespace validations:

v := datacop.New()

// Validate user details group
userGroup := v.Group("user")
userGroup.Field("name", name).
	Check(is.Required(name), "name is required")
userGroup.Field("age", age).
	Check(is.Min(18)(age), "must be 18 or older")

// Validate address group
addrGroup := v.Group("address")
addrGroup.Field("street", street).
	Check(is.Required(street), "street is required")
addrGroup.Field("city", city).
	Check(is.Required(city), "city is required")

Conditional Validation

Conditional validations using When are evaluated sequentially. When a condition is false, all subsequent checks are skipped until the next When condition:

v := datacop.New()

// Check is skipped because When(false)
v.Field("company", company).
	When(false).
	Check(is.Required(company), "company required")

// Multiple conditions
v.Field("role", role).
	Check(is.Required(role), "role is required").     // Always runs
	When(isAdmin).                                 // Only if isAdmin is true
	Check(is.In("admin", "super")(), "invalid role"). //   will this check run
	When(hasPermission).                           // Only if hasPermission is true
	Check(is.NotZero(), "permission required")        //   will this check run

// When conditions are combined with AND logic
v.Field("department", dept).
	When(isEmployee).
	When(isFullTime).
	Check(is.Required(dept), "department required")    // Runs only if isEmployee AND isFullTime

Note: Each When condition affects only the Check calls that follow it, until another When is encountered. The validation chain is processed sequentially from left to right.

Standalone Errors

For validations not tied to specific fields:

v := datacop.New()

// Add standalone error
if !isValid {
	v.AddStandaloneError("general validation failed")
}

// Check standalone condition
v.CheckStandalone(password == confirmPassword, "passwords do not match")

Custom Validation Functions

Creating custom validation functions is straightforward - any function that returns a bool can be used:

// Simple custom validation
func IsEven(value any) bool {
    v, ok := value.(int)
    if !ok {
        return false
    }
    return v%2 == 0
}

// Usage
v.Check(IsEven(age), "age", "age must be even")

// Custom validation with parameters
func MultipleOf(n int) ValidationFunc {
    return func(value any) bool {
        v, ok := value.(int)
        if !ok {
            return false
        }
        return v%n == 0
    }
}

// Usage
v.Check(MultipleOf(3)(age), "age", "age must be multiple of 3")

Common Validation Functions

The package provides many built-in validation functions:

	// Basic validations
	is.Required(value)              // checks if value is non-empty
 	is.NotZero(value)               // checks if numeric value is not zero
	is.Match(`[a-zA-Z0-9]+`)(value) // regex pattern

	// Comparison validations

	is.Between(1, 100)(value)      // numeric range
	is.Equal(10)(value)            // equal to value
	is.EqualStrings("a", "b")      // equal strings
	is.In("a", "b", "c")(value)   // value in set
	is.AllIn("a", "b", "c")([]string{"a", "b"}) // all values in set
	is.NoDuplicates()([]string{})  // unique values in slice
	is.Min(18)(value)              // minimum value
	is.Max(65)(value)              // maximum value
	is.MinLength(5)(value)         // minimum length
	is.MaxLength(10)(value)        // maximum length
	is.GreaterThan(100)(value)     // greater than value
	is.LessThan(100)(value)        // less than value
	is.GreaterOrEqual(100)(value)  // greater or equal to value
	is.LessOrEqual(100)(value)     // less or equal to value

	// Time-based validations

	is.Before(time.Now())          // time before now
	is.After(time.Now())           // time after now
	is.BetweenTime(start, end)     // time between two values

	// String regex validations
	is.Email(value)                // email format
	is.Phone(value)                // phone number format

Error Handling

Multiple ways to access validation errors:

v.HasErrors()               // returns true if any errors exist
v.HasErrorFor("field")      // checks for field-specific errors
v.ErrorFor("field")         // gets error message for field
v.Errors()                  // returns map[string]string of all errors
v.Error()                   // returns formatted error string
v.ValidationErrors()        // returns full error structs
v.StandaloneErrors()        // returns non-field-specific errors

Common Patterns

Password validation example:

v := datacop.New()
v.Field("password", password).
	Check(is.Required(password), "password is required").
	Check(is.MinLength(8)(password), "password too short").
	Check(is.Match(`[A-Z]`)(password), "must contain uppercase").
	Check(is.Match(`[a-z]`)(password), "must contain lowercase").
	Check(is.Match(`[0-9]`)(password), "must contain number")

Form validation example:

type Form struct {
	Username string
	Email    string
	Age      int
}

func ValidateForm(form Form) error {
	v := datacop.New()

	v.Field("username", form.Username).
		Check(is.Required(form.Username), "username is required").
		Check(is.MinLength(3)(form.Username), "username too short")

	v.Field("email", form.Email).
		Check(is.Required(form.Email), "email is required").
		Check(is.Email(form.Email), "invalid email format")

	v.Field("age", form.Age).
		Check(is.Min(18)(form.Age), "must be 18 or older")

	if v.HasErrors() {
		return v
	}
	return nil
}

Index

Constants

View Source
const StandaloneErrorKey = "__standalone__"

StandaloneErrorKey is the key used for standalone errors, i.e. global errors

Variables

This section is empty.

Functions

This section is empty.

Types

type FieldValidation

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

FieldValidation enables chain validation for a specific field

func (*FieldValidation) Check

func (f *FieldValidation) Check(valid bool, message string) *FieldValidation

Check performs a validation in the chain

Example usage: v := datacop.New() v.Field("username", username).

Check(Required(username), "username is required").
Check(MinLength(3)(username), "username must be at least 3 characters")

func (*FieldValidation) When

func (f *FieldValidation) When(condition bool) *When

When starts a conditional validation

type Group

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

Group represents a group of related validations

func (*Group) Field

func (g *Group) Field(name string, value any) *FieldValidation

Field starts a validation chain for the given field in the group

type ValidationError

type ValidationError struct {
	Field   string `json:"field,omitempty"`
	Message string `json:"message"`
}

type ValidationFunc

type ValidationFunc func(value any) bool

ValidationFunc is a non-generic function type for validation

type Validator

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

func New

func New() *Validator

New creates a new validator instance

Example usage:

func (*Validator) AddError

func (v *Validator) AddError(field, message string)

AddError adds an error for a specific field

func (*Validator) AddStandaloneError

func (v *Validator) AddStandaloneError(message string)

AddStandaloneError adds a standalone error

func (*Validator) Check

func (v *Validator) Check(valid bool, field, message string) bool

Check performs a field validation and adds an error if it fails

func (*Validator) CheckStandalone

func (v *Validator) CheckStandalone(valid bool, message string) bool

CheckStandalone performs a standalone validation and adds an error if it fails

func (*Validator) Clear

func (v *Validator) Clear()

Clear removes all errors from the validator instance

func (*Validator) Error

func (v *Validator) Error() string

Error implements the error interface

func (*Validator) ErrorFor

func (v *Validator) ErrorFor(field string) string

ErrorFor returns the string error message for a field

func (*Validator) Errors

func (v *Validator) Errors() map[string]string

Errors returns a map of field names and their string error messages

func (*Validator) Field

func (v *Validator) Field(name string, value any) *FieldValidation

Field starts a validation chain for the given field

Example usage: v := datacop.New() v.Field("username", username).

Check(Required(username), "username is required").
Check(MinLength(3)(username), "username must be at least 3 characters")
Check(MaxLength(255)(username), "username must be at most 255 characters")

if v.HasErrorFor("username") {
     fmt.Println(v.ErrorFor("username"))
}

func (*Validator) Group

func (v *Validator) Group(name string) *Group

Group starts a validation group

func (*Validator) HasErrorFor

func (v *Validator) HasErrorFor(field string) bool

HasErrorFor returns true if the field has any errors for a field

func (*Validator) HasErrors

func (v *Validator) HasErrors() bool

HasErrors returns true if there are any validation errors

func (*Validator) HasStandaloneErrors

func (v *Validator) HasStandaloneErrors() bool

HasStandaloneErrors returns true if there are any standalone errors

func (*Validator) MarshalJSON

func (v *Validator) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for the Validator type

func (*Validator) Merge

func (v *Validator) Merge(other *Validator)

Merge combines another validator's errors into this one. The other validator is not modified.

func (*Validator) StandaloneErrors

func (v *Validator) StandaloneErrors() []string

StandaloneErrors returns all standalone error messages

func (*Validator) ValidationErrors

func (v *Validator) ValidationErrors() map[string][]ValidationError

ValidationErrors returns all validation errors as a map of field names to their errors

type When

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

When represents a conditional validation

func (*When) Check

func (w *When) Check(valid bool, message string) *When

Check performs a validation in the chain

func (*When) When

func (w *When) When(condition bool) *When

When performs a validation in the chain

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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