Skip to content
This repository has been archived by the owner on Jun 29, 2024. It is now read-only.

feature: updating the validations API #8

Merged
merged 8 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
90 changes: 90 additions & 0 deletions docs/features/validations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: Form Validation
index: 3
---

Leapkit provides the `form/validate` package that offers a flexible and reusable way to validate form data by defining a set of validation rules that can be applied to form fields.

### How to Use

Validations are a set of rules stablished for different fields passed in the request. You can define these Validations to be used in your http handlers by and call the `form.Validate` function passing the `req` (*http.Request) and handling the `validate.Errors` returned. Example:

```go
newUserValidation := validate.Form(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tatang26 👀

validate.Field("email", validate.Required("email is required")),
validate.Field("password", validate.Required("password is required")),
)

verrs := form.Validate(req, newUserValidation)
if len(verrs) > 0 {
// handle validation errors...
}
```

### Built-in Rules

You can build your set of rules for each validation by using the package's built-in functions.

```go
// General Rules:
func Required(message ...string) Rule

// String Rules:
func Matches(field string, message ...string) Rule
func MatchRegex(re *regexp.Regexp, message ...string) Rule
func MinLength(min int, message ...string) Rule
func MaxLength(max int, message ...string) Rule
func WithinOptions(options []string, message ...string) Rule

// Number Rules:
func EqualTo(value float64, message ...string) Rule
func LessThan(value float64, message ...string) Rule
func LessThanOrEqualTo(value float64, message ...string) Rule
func GreaterThan(value float64, message ...string) Rule
func GreaterThanOrEqualTo(value float64, message ...string) Rule

// UUID Rule:
func ValidUUID(message ...string) Rule

// Time Rules:
func TimeEqualTo(u time.Time, message ...string) Rule
func TimeBefore(u time.Time, message ...string) Rule
func TimeBeforeOrEqualTo(u time.Time, message ...string) Rule
func TimeAfter(u time.Time, message ...string) Rule
func TimeAfterOrEqualTo(u time.Time, message ...string) Rule
```

### Custom validation Rules

Alternatively, you can create your own validation rules. Example:

```go
func IsUnique(db *sqlx.DB ) func([]string) error {
return func(emails []string) error {
query := "SELECT EXISTS(SELECT 1 FROM users WHERE email = $1)"
stmt, err := db.Prepare(query)
if err != nil {
return err
}

for _, email := range emails {
var exists bool
if err := stmt.QueryRow(email).Scan(&exists); err != nil {
return err
}

if exists {
return fmt.Errorf("email '%s' already exists.", email)
}
}

return nil
}
}

// ...
newUserValidation := validation.Form{
validation.New("email", validate.Required(), IsUnique(db))
}
...
```
5 changes: 2 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: "LeapKit"
title: "About LeapKit"
index: 1
---

LeapKit is a collection of tools to help you build your next application with Go. Its main purpose is to make web development as fast and simple as possible and is targeted at web developers trying to take the leap and launch their next project.
LeapKit is a collection of packages to help you build your next application with Go. Its main purpose is to make web development as fast and simple as possible and is targeted at web developers trying to take the leap and launch their next project.

There are two main components of LeapKit, the LeapKit Core and the LeapKit Template, these two make the use of LeapKit possible.

Expand All @@ -13,5 +13,4 @@ The LeapKit Core contains the Go libraries that facilitate the web development.


## LeapKit Template

The LeapKit Template contains a starting point folder structure using the LeapKit core. It provides some CLI commands to facilitate the web development of your apps, the template can be copied and modified with the `gonew` command.
Empty file added docs/tools/gloves.md
Empty file.
107 changes: 0 additions & 107 deletions docs/validations.md

This file was deleted.

34 changes: 32 additions & 2 deletions form/form.go → form/decode.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package form

import (
"fmt"
"net/http"
"strings"

Expand All @@ -18,8 +19,8 @@ var (
func init() {
// Register custom and common type decoder
// functions.
decoder.RegisterCustomTypeFunc(DecodeUUID, uuid.UUID{})
decoder.RegisterCustomTypeFunc(DecodeUUIDSlice, []uuid.UUID{})
decoder.RegisterCustomTypeFunc(decodeUUID, uuid.UUID{})
decoder.RegisterCustomTypeFunc(decodeUUIDSlice, []uuid.UUID{})
}

// RegisterCustomTypeFunc registers a custom type decoder func for a type.
Expand Down Expand Up @@ -54,3 +55,32 @@ func Decode(r *http.Request, dst interface{}) error {
err := decoder.Decode(dst, r.Form)
return err
}

// decodeUUID a single uuid from a string
// and returns an error if there is a problem
func decodeUUID(vals []string) (interface{}, error) {
uu, err := uuid.FromString(vals[0])
if err != nil {
err = fmt.Errorf("error parsing uuid: %w", err)
}

return uu, err
}

// decodeUUIDSlice decodes a slice of uuids from a string
// and returns an error if there is a problem
func decodeUUIDSlice(vals []string) (interface{}, error) {
var uus []uuid.UUID

for _, val := range vals {
uuid, err := uuid.FromString(val)
if err != nil {
err = fmt.Errorf("error parsing uuid: %w", err)
return nil, err
}

uus = append(uus, uuid)
}

return uus, nil
}
113 changes: 113 additions & 0 deletions form/uuid_test.go → form/decode_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,127 @@
package form_test

import (
"bytes"
"mime/multipart"
"net/http"
"net/url"
"strings"
"testing"
"time"

"github.com/gofrs/uuid/v5"
"github.com/leapkit/core/form"
)

func TestRegisterCustomDecoder(t *testing.T) {
vals := url.Values{
"Ddd": []string{"21-01-01"},
"Sss": []string{"hello"},
}

tr, err := http.NewRequest("POST", "/", strings.NewReader(vals.Encode()))
if err != nil {
t.Fatal(err)
}

tr.Header.Set("Content-Type", "application/x-www-form-urlencoded")

// Registering custom type
form.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return time.Parse("06-01-02", vals[0])
}, time.Time{})

st := struct {
Ddd time.Time `form:"Ddd"`
Sss string `form:"Sss"`
}{}

err = form.Decode(tr, &st)
if err != nil {
t.Fatal(err)
}

if st.Ddd.Format("2006-01-02") != "2021-01-01" {
t.Fatalf("expected 2021-01-01, got %v", st.Ddd.Format("2006-01-02"))
}
}

func TestDecodeGet(t *testing.T) {
vals := url.Values{
"Ddd": []string{"21-01-01"},
"Sss": []string{"hello"},
}

tr, err := http.NewRequest("GET", "/?"+vals.Encode(), nil)
if err != nil {
t.Fatal(err)
}

tr.Header.Set("Content-Type", "application/x-www-form-urlencoded")

// Registering custom type
form.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return time.Parse("06-01-02", vals[0])
}, time.Time{})

st := struct {
Ddd time.Time `form:"Ddd"`
Sss string `form:"Sss"`
}{}

err = form.Decode(tr, &st)
if err != nil {
t.Fatal(err)
}

if st.Ddd.Format("2006-01-02") != "2021-01-01" {
t.Fatalf("expected 2021-01-01, got %v", st.Ddd.Format("2006-01-02"))
}
}

func TestDecodeMultipartForm(t *testing.T) {
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)

err := writer.WriteField("Ddd", "21-01-01")
if err != nil {
t.Fatal(err)
}
err = writer.WriteField("Sss", "hello")
if err != nil {
t.Fatal(err)
}

writer.Close()

tr, err := http.NewRequest("POST", "/", &buf)
if err != nil {
t.Fatal(err)
}

tr.Header.Set("Content-Type", writer.FormDataContentType())

// Registering custom type
form.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return time.Parse("06-01-02", vals[0])
}, time.Time{})

st := struct {
Ddd time.Time `form:"Ddd"`
Sss string `form:"Sss"`
}{}

err = form.Decode(tr, &st)
if err != nil {
t.Fatal(err)
}

if st.Ddd.Format("2006-01-02") != "2021-01-01" {
t.Fatalf("expected 2021-01-01, got %v", st.Ddd.Format("2006-01-02"))
}
}


func TestUUID(t *testing.T) {
// Test that passing a uuid as a string works
t.Run("valid uuid", func(t *testing.T) {
Expand Down
Loading
Loading