Skip to content

Latest commit

 

History

History
106 lines (87 loc) · 2.57 KB

README.md

File metadata and controls

106 lines (87 loc) · 2.57 KB

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.