Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/a-h/templ v0.2.707
github.com/gorilla/sessions v1.3.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
4 changes: 0 additions & 4 deletions validate/README.md

This file was deleted.

37 changes: 37 additions & 0 deletions validate/boolean.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package validate

import (
"github.com/anthdm/superkit/validate/primitives"
)

type boolValidator struct {
Rules []primitives.Rule
IsOptional bool
}

func Bool() *boolValidator {
return &boolValidator{
Rules: []primitives.Rule{
primitives.IsType[bool]("is not a valid boolean"),
},
}
}

func (v *boolValidator) Validate(fieldValue any) ([]string, bool) {
return primitives.GenericValidator(fieldValue, v.Rules, v.IsOptional)
}

func (v *boolValidator) Optional() *boolValidator {
v.IsOptional = true
return v
}

func (v *boolValidator) True() *boolValidator {
v.Rules = append(v.Rules, primitives.EQ[bool](true, "should be true"))
return v
}

func (v *boolValidator) False() *boolValidator {
v.Rules = append(v.Rules, primitives.EQ[bool](false, "should be false"))
return v
}
69 changes: 69 additions & 0 deletions validate/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package validate

import (
"fmt"
"os"
"strconv"

p "github.com/anthdm/superkit/validate/primitives"
)

// takes a key and a validator and returns the validated and converted environment variable
func Env[T supportedEnvTypes](key string, v fieldValidator, defailtValue ...T) T {
str := os.Getenv(key)

val, err := coerceString[T](str)

if err != nil || p.IsZeroValue(val) {
if len(defailtValue) > 0 {
return defailtValue[0]
} else {
panic(fmt.Errorf("failed to parse env %s: %v", key, err))
}
}

errs, ok := v.Validate(val)
if !ok {
panic(fmt.Errorf("failed to validate env %s: %v", key, errs))
}

return val
}

type supportedEnvTypes interface {
~int | ~float64 | ~bool | ~string
}

func coerceString[T supportedEnvTypes](val string) (T, error) {
var result T

switch any(result).(type) {
case int:
var tmp int
tmp, err := strconv.Atoi(val)
if err != nil {
return result, err
}

result = any(tmp).(T)
case float64:
tmp, err := strconv.ParseFloat(val, 64)
if err != nil {
return result, err
}
result = any(tmp).(T)

case bool:
tmp, err := strconv.ParseBool(val)
if err != nil {
return result, err
}
result = any(tmp).(T)
case string:
result = any(val).(T)
default:
return result, fmt.Errorf("unsupported type: %T", result)
}

return result, nil
}
85 changes: 85 additions & 0 deletions validate/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package validate

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

// fmt.Printf("VALUE: %v | err: %v", val, err)
func TestCoerceToString(t *testing.T) {
val, err := coerceString[string]("123")
assert.Nil(t, err)
assert.Equal(t, "123", val)
}

func TestCoerceToInt(t *testing.T) {
val, err := coerceString[int]("123")
assert.Nil(t, err)
assert.Equal(t, 123, val)
}

func TestCoerceToFloat(t *testing.T) {
val, err := coerceString[float64]("123.25")
assert.Nil(t, err)
assert.Equal(t, 123.25, val)
}

func TestCoerceToBool(t *testing.T) {
val, err := coerceString[bool]("true")
assert.Nil(t, err)
assert.Equal(t, true, val)
}

func TestEmptyEnv(t *testing.T) {
assert.Panics(t, func() {
Env[string]("Test", String().Required())
})

assert.Panics(t, func() {
os.Setenv("TEST", "")
Env[string]("Test", String().Required())
})
}

func TestReturnsValue(t *testing.T) {

os.Setenv("TEST", "value")

val := Env[string]("TEST", String().Required())

assert.Equal(t, val, "value")
}

func TestDefault(t *testing.T) {
val := Env[string]("TEST2", String().Required(), "hello")
assert.Equal(t, "hello", val)

os.Setenv("TEST2", "world")
val = Env[string]("TEST2", String().Required(), "hello")
assert.Equal(t, "world", val)

assert.Panics(t, func() {
os.Setenv("TEST2", "1")
_ = Env[string]("TEST2", String().Min(4))
})
}

func TestInt(t *testing.T) {
os.Setenv("TEST", "1")
val := Env[int]("TEST", Int().LT(2))
assert.Equal(t, 1, val)
}

func TestBool(t *testing.T) {
os.Setenv("TEST", "true")
val := Env[bool]("TEST", Bool().True())
assert.Equal(t, true, val)
}

func TestFloat(t *testing.T) {
os.Setenv("TEST", "1.1")
val := Env[float64]("TEST", Float().GT(1.0))
assert.Equal(t, 1.1, val)
}
88 changes: 88 additions & 0 deletions validate/numbers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package validate

import (
"fmt"

p "github.com/anthdm/superkit/validate/primitives"
)

type Numeric interface {
~int | ~float64
}

type numberValidator[T Numeric] struct {
Rules []p.Rule
IsOptional bool
}

func Float() *numberValidator[float64] {
return &numberValidator[float64]{
Rules: []p.Rule{
p.IsType[float64]("should be a decimal number"),
},
}
}

func Int() *numberValidator[int] {
return &numberValidator[int]{
Rules: []p.Rule{
p.IsType[int]("should be an whole number"),
},
}
}

// GLOBAL METHODS

func (v *numberValidator[T]) Refine(ruleName string, errorMsg string, validateFunc p.RuleValidateFunc) *numberValidator[T] {
v.Rules = append(v.Rules,
p.Rule{
Name: ruleName,
ErrorMessage: errorMsg,
ValidateFunc: validateFunc,
},
)

return v
}

// is equal to one of the values
func (v *numberValidator[T]) In(values []T) *numberValidator[T] {
v.Rules = append(v.Rules, p.In(values, fmt.Sprintf("should be in %v", values)))
return v
}

func (v *numberValidator[Numeric]) Optional() *numberValidator[Numeric] {
v.IsOptional = true
return v
}

func (v *numberValidator[Numeric]) Validate(fieldValue any) ([]string, bool) {
return p.GenericValidator(fieldValue, v.Rules, v.IsOptional)
}

// UNIQUE METHODS

func (v *numberValidator[Numeric]) EQ(n Numeric) *numberValidator[Numeric] {
v.Rules = append(v.Rules, p.EQ(n, fmt.Sprintf("should be equal to %v", n)))
return v
}

func (v *numberValidator[Numeric]) LTE(n Numeric) *numberValidator[Numeric] {
v.Rules = append(v.Rules, p.LTE(n, fmt.Sprintf("should be lesser or equal than %v", n)))
return v
}

func (v *numberValidator[Numeric]) GTE(n Numeric) *numberValidator[Numeric] {
v.Rules = append(v.Rules, p.GTE(n, fmt.Sprintf("should be greater or equal to %v", n)))
return v
}

func (v *numberValidator[Numeric]) LT(n Numeric) *numberValidator[Numeric] {
v.Rules = append(v.Rules, p.LT(n, fmt.Sprintf("should be less than %v", n)))
return v
}

func (v *numberValidator[Numeric]) GT(n Numeric) *numberValidator[Numeric] {
v.Rules = append(v.Rules, p.GT(n, fmt.Sprintf("should be greater than %v", n)))
return v
}
Loading