Skip to content

Commit

Permalink
fix after
Browse files Browse the repository at this point in the history
  • Loading branch information
aymaneallaoui committed Sep 13, 2024
1 parent f2c78d6 commit 60c6a93
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 25 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Go CI

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: "^1.22" # Specify Go version here

- name: Install dependencies
run: go mod tidy

- name: Run unit tests
run: go test ./tests -v

- name: Run benchmarks
run: go test -benchmem -run=^$ -bench . ./benchmarks
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Zod-Go
# Zod-Go: Schema Validation Library for Go

Zod-Go is a Go-based validation library inspired by Zod in TypeScript. It provides an easy way for backend engineers to define schemas and validate complex data structures, such as arrays, maps, objects, and nested objects.
Zod-Go is a Go-based validation library inspired by the popular Zod library in TypeScript(before you say anything yes i'm a TS soy dev, and i don't use go std lib i only use go cuz it's blue like TS). It allows developers to easily define schemas to validate complex data structures, including strings, numbers, arrays(we know it's sLiCeS), maps, and nested objects.

## Features

- **Schema Definitions**: Easily define validation rules for strings, numbers, booleans, arrays, maps, and nested objects.
- **Custom Error Handling**: Get detailed validation errors in both plain text and JSON formats.
- **Concurrent Validation**: Large datasets and nested structures are validated concurrently for performance.
- **Optional Fields and Default Values**: Support for optional fields and default values.
- **Schema Definitions**: Validate strings, numbers, booleans, arrays, and nested objects.
- **Custom Error Handling**: Get detailed validation errors with custom messages.
- **Concurrent Validation**: "Improve" performance for large datasets through concurrent validation.
- **Optional Fields and Default Values**: Handle optional fields gracefully and set defaults where necessary.

## Installation

Expand All @@ -18,3 +18,30 @@ go get github.com/aymaneallaoui/zod-Go/zod
```

## Usage

here's a example of how to use it (it's shit i know but i'm lazy and dumb):

```go
package main

import (
"fmt"
"github.com/aymaneallaoui/zod-Go/zod/validators"
)

func main() {
stringSchema := validators.String().
Min(3).Max(5).Required().
WithMessage("minLength", "This string is too short like arch users 😒!").
WithMessage("maxLength", "This string is too long!")

err := stringSchema.Validate("ab")
if err != nil {
fmt.Println("Validation failed:", err.(*zod.ValidationError).ErrorJSON())
}
}

```

as you can see (i guess) we validate a string with a minimum length of 3 and a maximum length of 5.
Custom error messages (thanks for a friend for suggesting that) are used for both validation rules.
14 changes: 8 additions & 6 deletions examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ func main() {
WithMessage("minLength", "This string is too short!").
WithMessage("maxLength", "This string is too long!")

err := stringSchema.Validate("ab")
err := stringSchema.Validate("arch")
if err != nil {
fmt.Println("Validation failed:", err.(*zod.ValidationError).ErrorJSON())
fmt.Println("Validation failed:", err.(*zod.ValidationError).Error())
} else {
fmt.Println("Validation succeeded")
}

userSchema := validators.Object(map[string]zod.Schema{
Expand All @@ -35,11 +37,11 @@ func main() {
})

userData := map[string]interface{}{
"name": "Jo",
"age": 17,
"name": "aymane",
"age": 21,
"address": map[string]interface{}{
"street": "",
"city": "NY",
"street": "lol idk test ",
"city": "",
},
}

Expand Down
77 changes: 77 additions & 0 deletions tests/schema_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tests

import (
"errors"
"testing"

"github.com/aymaneallaoui/zod-Go/zod"
Expand Down Expand Up @@ -130,3 +131,79 @@ func TestNestedObjectSchema(t *testing.T) {
t.Error("Expected validation error for short street name")
}
}

type MockSchema struct {
isValid bool
}

type DynamicMockSchema struct {
validateFunc func(data interface{}) error
}

func (ms DynamicMockSchema) Validate(data interface{}) error {
return ms.validateFunc(data)
}

func (ms MockSchema) Validate(data interface{}) error {
if ms.isValid {
return nil
}
return errors.New("invalid data")
}

func TestValidateConcurrently_Success(t *testing.T) {
schema := MockSchema{isValid: true}
dataList := []interface{}{"data1", "data2", "data3"}

results := zod.ValidateConcurrently(schema, dataList, 3)

for _, result := range results {
if !result.IsValid || result.Error != nil {
t.Errorf("Expected valid data, got invalid result: %+v", result)
}
}
}

func TestValidateConcurrently_Failure(t *testing.T) {
schema := MockSchema{isValid: false}
dataList := []interface{}{"data1", "data2", "data3"}

results := zod.ValidateConcurrently(schema, dataList, 3)

for _, result := range results {
if result.IsValid || result.Error == nil {
t.Errorf("Expected invalid data, got valid result: %+v", result)
}
}
}

func TestValidateConcurrently_EmptyData(t *testing.T) {
schema := MockSchema{isValid: true}
dataList := []interface{}{}

results := zod.ValidateConcurrently(schema, dataList, 3)

if len(results) != 0 {
t.Errorf("Expected no results, got %d results", len(results))
}
}

func TestValidateConcurrently_LargeDataset(t *testing.T) {
schema := MockSchema{isValid: true}
largeDataList := make([]interface{}, 10000)
for i := range largeDataList {
largeDataList[i] = i
}

results := zod.ValidateConcurrently(schema, largeDataList, 100)

if len(results) != len(largeDataList) {
t.Errorf("Expected %d results, got %d", len(largeDataList), len(results))
}

for _, result := range results {
if !result.IsValid {
t.Errorf("Expected all valid data, got invalid result: %+v", result)
}
}
}
53 changes: 51 additions & 2 deletions zod/schema.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,60 @@
package zod

import (
"sync"
)

type Schema interface {
Validate(data interface{}) error
}

type Validator func(data interface{}) error

func ValidateSchema(schema Schema, data interface{}) error {
return schema.Validate(data)
}

type Result struct {
IsValid bool
Error error
}

func Validator(schema Schema, data interface{}, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()

err := schema.Validate(data)

var isValid bool
if err == nil {
isValid = true
} else {
isValid = false
}

results <- Result{IsValid: isValid, Error: err}
}

func ValidateConcurrently(schema Schema, dataList []interface{}, workerCount int) []Result {

results := make(chan Result, len(dataList))
var wg sync.WaitGroup

sem := make(chan struct{}, workerCount)

for _, data := range dataList {
wg.Add(1)
go func(data interface{}) {
sem <- struct{}{}
defer func() { <-sem }()
Validator(schema, data, results, &wg)
}(data)
}

wg.Wait()
close(results)

var validationResults []Result
for result := range results {
validationResults = append(validationResults, result)
}

return validationResults
}
17 changes: 6 additions & 11 deletions zod/validators/stucts.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,14 @@ func (o *ObjectSchema) Validate(data interface{}) error {
}
}

if subObj, isMap := v.(map[string]interface{}); isMap {
if subErr := s.Validate(subObj); subErr != nil {
if nestedValidationErr, ok := subErr.(*zod.ValidationError); ok {
if err := s.Validate(v); err != nil {
if nestedValidationErr, ok := err.(*zod.ValidationError); ok && len(nestedValidationErr.Details) > 0 {

errChan <- zod.NewNestedValidationError(k, v, "Validation failed", nestedValidationErr.Details)
}
return
errChan <- zod.NewNestedValidationError(k, v, "Validation failed", nestedValidationErr.Details)
} else {
errChan <- zod.NewValidationError(k, v, err.Error())
}
}

if err := s.Validate(v); err != nil {
errChan <- zod.NewValidationError(k, v, err.Error())
return
}
}(key, value, schema, exists)
}
Expand All @@ -99,7 +95,6 @@ func (o *ObjectSchema) Validate(data interface{}) error {
}

if len(combinedErrors) > 0 {

return zod.NewNestedValidationError("object", data, "Validation failed", combinedErrors)
}

Expand Down

0 comments on commit 60c6a93

Please sign in to comment.