Skip to content

ajlive/godex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

godex

A pattern for validating data with generics based on pydantic (docs)

This is, uh, very early stages. So far it's just 83 lines of primitive, but working, code. But I have already written a PHP version of substantial parts of pydantic, so I'm goint to claim I know what I'm doing and that this is all going to go just fine.

It may turn out to be an experiment demonstrating why you shouldn't do pydantic in Go. It may turn out to be an interesting and even useful pattern.

🤷

go2go playground link: https://go2goplay.golang.org/p/dyx4Ui_ldBc

package main

import (
	"fmt"
	"os"
)

type Contract[T any] struct {
	validators []func(T) (T, string, error)
}

func (c *Contract[T]) validator(vdtr func(T) (T, string, error)) {
	c.validators = append(c.validators, vdtr)
}

func (c *Contract[T]) validData(data T) (validData T, errors map[string][]error) {
	var field string
	var err error
	errors = make(map[string][]error)
	for _, vdtr := range c.validators {
		validData, field, err = vdtr(data)
		if err == nil {
			continue
		}
		if _, ok := errors[field]; !ok {
			errors[field] = make([]error, 0)
		}
		errors[field] = append(errors[field], err)
	}
	return validData, errors
}

type Person struct {
	id   int
	name string
}

func main() {
	// declare contract
	c := Contract[Person]{
		validators: make([]func(Person) (Person, string, error), 0),
	}
	c.validator(func(p Person) (Person, string, error) {
		field := "id"
		if p.id <= 0 {
			return p, field, fmt.Errorf("id must be positive integer; got %v", p.id)
		}
		return p, field, nil
	})
	c.validator(func(p Person) (Person, string, error) {
		field := "name"
		if p.name == "" {
			return p, field, fmt.Errorf("no name for person with id %v", p.id)
		}
		return p, field, nil
	})

	var errors map[string][]error

	// validate valid data
	data := Person{
		id:   1,
		name: "Guy Incognito",
	}
	validData, errors := c.validData(data)
	if len(errors) != 0 {
		fmt.Printf("invalid Person: %+v\nerrors: %+v", validData, errors)
		os.Exit(1)
	}
	fmt.Printf("valid Person: %+v\n", validData)

	fmt.Println("")

	// invalidate bad data
	badData := Person{}
	invalidData, errors := c.validData(badData)
	if len(errors) != 0 {
		fmt.Printf("invalid Person: %+v\nerrors: %+v", invalidData, errors)
		os.Exit(1)
	}
	fmt.Printf("valid Person: %+v\n", invalidData)

}

Output:

valid Person: {id:1 name:Guy Incognito}

invalid Person: {id:0 name:}
errors: map[id:[id must be positive integer; got 0] name:[no name for person with id 0]]
Program exited: status 1.

About

A pattern for validating with generics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published