Skip to content

Commit

Permalink
Merge pull request #26 from Rhyanz46/avoidnullfield
Browse files Browse the repository at this point in the history
requiredWithout
  • Loading branch information
Rhyanz46 authored Mar 9, 2024
2 parents 9b34201 + 46f4b27 commit 31b0ec1
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 60 deletions.
76 changes: 39 additions & 37 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,48 @@ name: Go

on:
pull_request:
types:
- closed
branches: [ "main" ]

jobs:

build:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...

- name: Get Latest Tag
id: latest-tag
run: echo "tag=$(git tag -l | sort -V | tail -1 | sed 's/-dev$//')" >> "$GITHUB_OUTPUT"

- name: Generate New Bumped Version
uses: actions-ecosystem/action-bump-semver@v1
id: bump-semver
with:
current_version: ${{ steps.latest-tag.outputs.tag }}
level: patch

# - name: Create Release Notes
# id: create-release
# run: |
# curl -f -X POST \
# -H "Accept: application/vnd.github.v3+json" \
# -H "authorization: Bearer ${{ secrets.TOKEN }}" \
# -H "X-GitHub-Api-Version: 2022-11-28" \
# https://api.github.com/repos/${{ github.repository }}/releases \
# -d '{"tag_name":"${{ steps.bump-semver.outputs.new_version }}", "target_commitish": "${{ github.ref_name }}", "generate_release_notes":true}'

- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...

- name: Get Latest Tag
id: latest-tag
run: echo "tag=$(git tag -l | sort -V | tail -1 | sed 's/-dev$//')" >> "$GITHUB_OUTPUT"

- name: Generate New Bumped Version
uses: actions-ecosystem/action-bump-semver@v1
id: bump-semver
with:
current_version: ${{ steps.latest-tag.outputs.tag }}
level: patch

- name: Create Release Notes
id: create-release
run: |
curl -f -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "authorization: Bearer ${{ secrets.TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/releases \
-d '{"tag_name":"${{ steps.bump-semver.outputs.new_version }}", "target_commitish": "${{ github.ref_name }}", "generate_release_notes":true}'
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ go get github.com/Rhyanz46/go-map-validator/map_validator
- support file upload
- Unique Value
- ex case : `old_password` and `new_password` cant be using same value
- RequiredWithout check
- ex case : the field `flavor` is required if `custom_flavor` is null
- enum value check
- min/max length data check
- email field check
Expand All @@ -36,6 +38,7 @@ go get github.com/Rhyanz46/go-map-validator/map_validator
- on unique values error : ⌛ not ready
- on max data message : ⌛ not ready
- on enum value not match : ⌛ not ready
- on `RequiredWithout` error : ⌛ not ready

## On Progress

Expand Down
83 changes: 66 additions & 17 deletions map_validator/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func buildMessage(msg string, meta MessageMeta) error {
}

func validateRecursive(wrapper *RulesWrapper, key string, data map[string]interface{}, rule Rules, loadedFrom loadFromType) (interface{}, error) {
var endOfLoop bool
if wrapper != nil && wrapper.Setting.Strict {
var allowedKeys []string
keys := getAllKeys(data)
Expand All @@ -101,30 +102,77 @@ func validateRecursive(wrapper *RulesWrapper, key string, data map[string]interf
}
}
}

res, err := validate(key, data, rule, loadedFrom)
if err != nil {
return nil, err
}

if res != nil && len(rule.Unique) > 0 {
// check unique values
if wrapper != nil && res != nil && len(rule.Unique) > 0 {
for _, unique := range rule.Unique {
newUniqueValues := make(map[string]map[string]interface{})
if wrapper.uniqueValues == nil {
wrapper.uniqueValues = &newUniqueValues
} else {
newUniqueValues = *wrapper.uniqueValues
wrapper.uniqueValues = &map[string]map[string]interface{}{}
}

if _, exists := (*wrapper.uniqueValues)[unique]; !exists {
(*wrapper.uniqueValues)[unique] = make(map[string]interface{})
}
for keyX, val := range newUniqueValues[unique] {

for keyX, val := range (*wrapper.uniqueValues)[unique] {
if val == res {
return nil, errors.New(fmt.Sprintf("value of '%s' and '%s' fields must be different", keyX, key))
return nil, fmt.Errorf("value of '%s' and '%s' fields must be different", keyX, key)
}
}
if newUniqueValues[unique] != nil {
newUniqueValues[unique][key] = res
} else {
newUniqueValues[unique] = map[string]interface{}{key: res}
(*wrapper.uniqueValues)[unique][key] = res
}
}

// put filled and null fields
if wrapper != nil {
if wrapper.filledField == nil {
wrapper.filledField = &[]string{}
}
if wrapper.nullFields == nil {
wrapper.nullFields = &[]string{}
}

if res != nil {
*wrapper.filledField = append(*wrapper.filledField, key)
} else {
*wrapper.nullFields = append(*wrapper.nullFields, key)
}

if len(wrapper.Rules) == len(*wrapper.nullFields)+len(*wrapper.filledField) {
endOfLoop = true
}
}

// put required without values
if wrapper != nil && len(rule.RequiredWithout) > 0 {
for _, unique := range rule.RequiredWithout {
if wrapper.requiredWithout == nil {
wrapper.requiredWithout = &map[string][]string{}
}

if _, exists := (*wrapper.requiredWithout)[unique]; !exists {
(*wrapper.requiredWithout)[unique] = []string{}
}
(*wrapper.requiredWithout)[unique] = append((*wrapper.requiredWithout)[unique], key)
}
}

if endOfLoop && wrapper.requiredWithout != nil {
for _, field := range *wrapper.nullFields {
dependenciesField := (*wrapper.requiredWithout)[field]
if len(dependenciesField) == 0 {
continue
}
for _, XField := range dependenciesField {
if isDataInList(XField, *wrapper.nullFields) {
return nil, errors.New(fmt.Sprintf("if field '%s' is null you need to put value in this %v field", field, dependenciesField))
}
}
wrapper.uniqueValues = &newUniqueValues
}
}

Expand All @@ -133,11 +181,8 @@ func validateRecursive(wrapper *RulesWrapper, key string, data map[string]interf
for keyX, ruleX := range rule.Object.Rules {
_, err = validateRecursive(rule.Object, keyX, res.(map[string]interface{}), ruleX, fromJSONEncoder)
if err != nil {
//if rule.CustomMsg != nil #TODO: custom message for nested object
return nil, err
}
//filledFields = append(filledFields, newFilledFields...) #TODO: get children fields fill or not filled
//nullFields = append(nullFields, newNullFields...)
}
}

Expand All @@ -163,6 +208,10 @@ func validate(field string, dataTemp map[string]interface{}, validator Rules, da
var sliceData []interface{}
//var isListObject bool

if len(validator.RequiredWithout) > 0 {
validator.Null = true
}

// null validation
if !validator.Null && data == nil {
return nil, errors.New("we need '" + field + "' field")
Expand Down Expand Up @@ -195,7 +244,7 @@ func validate(field string, dataTemp map[string]interface{}, validator Rules, da
validator.Enum == nil &&
validator.Object == nil &&
validator.ListObject == nil &&
!validator.IsMapInterface &&
!validator.AnonymousObject &&
!validator.File &&
!validator.IPV4Network &&
validator.RegexString == "")
Expand Down Expand Up @@ -392,7 +441,7 @@ func validate(field string, dataTemp map[string]interface{}, validator Rules, da
data = sliceDataX
}

if validator.IsMapInterface || validator.Object != nil {
if validator.AnonymousObject || validator.Object != nil {
res, err := toMapStringInterface(data)
if err != nil {
return nil, errors.New("field '" + field + "' is not valid object")
Expand Down
14 changes: 9 additions & 5 deletions map_validator/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,20 @@ type Setting struct {
}

type RulesWrapper struct {
Rules map[string]Rules
Setting Setting
uniqueValues *map[string]map[string]interface{}
Rules map[string]Rules
Setting Setting
uniqueValues *map[string]map[string]interface{}
filledField *[]string
nullFields *[]string
requiredWithout *map[string][]string
}

type Rules struct {
Null bool
NilIfNull bool
IsMapInterface bool
AnonymousObject bool
Email bool
Enum *EnumField[any] // new 🔥🔥🔥
Enum *EnumField[any]
Type reflect.Kind
Max *int
Min *int
Expand All @@ -53,6 +56,7 @@ type Rules struct {
File bool
RegexString string
Unique []string
RequiredWithout []string
Object *RulesWrapper
ListObject *RulesWrapper

Expand Down
2 changes: 1 addition & 1 deletion test/modul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func TestInterfaceFieldBinding(t *testing.T) {
"jenis_kelamin": {Enum: &map_validator.EnumField[any]{Items: []string{"laki-laki", "perempuan"}}},
"hoby": {Type: reflect.String, Null: true},
"menikah": {Type: reflect.Bool, Null: false},
"list_data": {IsMapInterface: true},
"list_data": {AnonymousObject: true},
},
}

Expand Down
61 changes: 61 additions & 0 deletions test/required_without_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package test

import (
"github.com/Rhyanz46/go-map-validator/map_validator"
"reflect"
"strings"
"testing"
)

func TestRequiredWithout(t *testing.T) {
check, err := map_validator.NewValidateBuilder().SetRules(map_validator.RulesWrapper{
Rules: map[string]map_validator.Rules{
"name": {Type: reflect.String},
"flavor": {Type: reflect.String, RequiredWithout: []string{"custom_flavor"}},
"custom_flavor": {Type: reflect.String, RequiredWithout: []string{"flavor"}},
},
}).Load(map[string]interface{}{
"name": "SSD",
})
if err != nil {
t.Errorf("Expected not have error, but got error : %s", err)
return
}

expected := "is null you need to put value in this"
_, err = check.RunValidate()
if !strings.Contains(err.Error(), expected) {
t.Errorf("Expected error with text at least %s, but we got %s", expected, err)
}
}

func TestChildRequiredWithout(t *testing.T) {
role := map_validator.RulesWrapper{
Rules: map[string]map_validator.Rules{
"data": {Object: &map_validator.RulesWrapper{
Rules: map[string]map_validator.Rules{
"name": {Type: reflect.String},
"expired": {Type: reflect.String, Null: true},
"flavor": {Type: reflect.String, RequiredWithout: []string{"custom_flavor", "size"}},
"custom_flavor": {Type: reflect.String, RequiredWithout: []string{"flavor", "size"}},
"size": {Type: reflect.Int, RequiredWithout: []string{"flavor", "custom_flavor"}},
},
}},
},
}
payload := map[string]interface{}{
"data": map[string]interface{}{
"name": "sabalong",
},
}
check, err := map_validator.NewValidateBuilder().SetRules(role).Load(payload)
if err != nil {
t.Errorf("Expected not have error, but got error : %s", err)
return
}
expected := "is null you need to put value in this"
_, err = check.RunValidate()
if !strings.Contains(err.Error(), expected) {
t.Errorf("Expected error with text at least %s, but we got %s", expected, err)
}
}
1 change: 1 addition & 0 deletions test/unique_values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func TestChildUniqueValue(t *testing.T) {
Rules: map[string]map_validator.Rules{
"data": {Object: &map_validator.RulesWrapper{
Rules: map[string]map_validator.Rules{
"name": {Type: reflect.String, Null: true},
"password": {Type: reflect.String, Unique: []string{"password"}, Null: true},
"new_password": {Type: reflect.String, Unique: []string{"password"}, Null: true},
},
Expand Down

0 comments on commit 31b0ec1

Please sign in to comment.