Skip to content

Commit

Permalink
feat: implement prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
tmzane committed Nov 2, 2022
0 parents commit ad2a49c
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
64 changes: 64 additions & 0 deletions check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Package check provides convenience helpers to perform validations of any
// kind.
//
// Use That/Thatf to write conditions to check, multiple calls can be chained.
// The last call in the chain must be either FirstError or AllErrors.
package check

import "fmt"

// That checks whether cond is true, and if not, records the error. That panics
// if the error is nil.
func That(cond bool, err error) *check {
return new(check).That(cond, err)
}

// Thatf checks whether cond is true, and if not, creates an error from format
// and args, then records it.
func Thatf(cond bool, format string, args ...any) *check {
return That(cond, fmt.Errorf(format, args...))
}

// check holds the conditions to check and their corresponding errors.
type check struct {
conds []bool
errs []error
}

// That checks whether cond is true, and if not, records the error. That panics
// if the error is nil.
func (ch *check) That(cond bool, err error) *check {
if err == nil {
panic("check: a nil error is provided")
}
ch.conds = append(ch.conds, cond)
ch.errs = append(ch.errs, err)
return ch
}

// Thatf checks whether cond is true, and if not, creates an error from format
// and args, then records it.
func (ch *check) Thatf(cond bool, format string, args ...any) *check {
return ch.That(cond, fmt.Errorf(format, args...))
}

// FirstError returns the error of the first failed condition.
func (ch *check) FirstError() error {
for i := range ch.conds {
if !ch.conds[i] {
return ch.errs[i]
}
}
return nil
}

// AllErrors returns the errors of all failed conditions.
func (ch *check) AllErrors() []error {
var errs []error
for i := range ch.conds {
if !ch.conds[i] {
errs = append(errs, ch.errs[i])
}
}
return errs
}
42 changes: 42 additions & 0 deletions check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package check_test

import (
"errors"
"testing"

"github.com/junk1tm/check"
)

func TestCheck(t *testing.T) {
t.Run("first error", func(t *testing.T) {
err12 := errors.New("1 and 2 are not equal")
err34 := errors.New("3 and 4 are not equal")

err := check.
That(1 == 2, err12).
That(3 == 4, err34).
FirstError()

if !errors.Is(err, err12) {
t.Errorf("got %v; want %v", err, err12)
}
})

t.Run("all errors", func(t *testing.T) {
errs := check.
Thatf("foo" == "baz", "foo and bar are not equal").
Thatf(true == false, "true and false are not equal").
AllErrors()

if len(errs) != 2 {
t.Fatalf("want 2 errors")
}

if errs[0] == nil || errs[1] == nil {
t.Errorf("want all errors to be not nil")
}
})

// TODO(jun1ktm): test That's panic case
// TODO(jun1ktm): test FirstError's nil case
}
39 changes: 39 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package check_test

import (
"errors"
"fmt"

"github.com/junk1tm/check"
)

var user = struct {
Name string
Age int
Email string
}{
Name: "",
Age: 10,
Email: "user@email",
}

func isEmail(string) bool { return false }

var errEmptyName = errors.New("name must not be empty")

func ExampleThat() {
errs := check.
That(user.Name != "", errEmptyName).
Thatf(user.Age >= 18, "%d y.o. is too young", user.Age).
Thatf(isEmail(user.Email), "%s is invalid email", user.Email).
AllErrors() // OR FirstError() to check only the first error

for _, err := range errs {
fmt.Println(err)
}

// Output:
// name must not be empty
// 10 y.o. is too young
// user@email is invalid email
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/junk1tm/check

go 1.19

0 comments on commit ad2a49c

Please sign in to comment.