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

feat: validation package #7

Merged
merged 6 commits into from
May 9, 2024
Merged

Conversation

tatang26
Copy link
Contributor

@tatang26 tatang26 commented May 1, 2024

This PR adds the validation package with the approach of validating form fields coming from an HTTP request.

How does it work?

The validation process starts in the validation.New variadic function which receives as first parameter the HTTP request form and the second parameter, a variable number Rule arguments.

verrs := validation.New(form, ruleSet...)

Each Rule element holds the form Field key and a set of validations that will be applied to all form field values.

Rule{
    Field: "form_field",
    Validations []Validation{...},
}

These validations are functions will receives the form string values and returns an error if any of the elements fail to meet the validation condition.

type Validation func(...string) error

Simple implementtion

form := url.Values{
    "username": []string{""},
    "email": []string{"jsmith@email.com"},
    "pasword": []string{"1234567"},
}

verrs := New(form,
    Rule{
        Field: "username",
        Validations: []Validation{
            Required(),
        },
    },
    Rule{
        Field: "email",
        Validations: []Validation{
            Required(),
        },
    },
    Rule{
        Field: "password",
        Validations: []Validation{
            MinLength(8, "Password must contain at least 8 characters"),
            func(values ...string) error {
                if strings.Contains(values[0], form.Get("username")) {
                    return errors.New("password must not contain your name.")
                }

                return nil
            },
        },
    },
)

if len(verrs) > 0 {
	fmt.Println("form has error")
}

@paganotoni
Copy link
Contributor

paganotoni commented May 6, 2024

This is good @tatang26! Thanks for sharing it. Regarding the API I want to suggest a few things:

Why don't we make Validation the set of rules for determining whether a field is correct or acceptable?

In that sense we would have:

userChecker := Validations{
    Validation{
        Field: "username",
        Rules: RuleSet{
            Required(),
        },
    },
    Validation{
        Field: "email",
        Rules: RuleSet{
            Required(),
        },
    },
    Validation{
        Field: "password",
        Rules: RuleSet{
            MinLength(8, "Password must contain at least 8 characters"),
            func(values ...string) error {
                if strings.Contains(values[0], form.Get("username")) {
                    return errors.New("password must not contain your name.")
                }

                return nil
            },
        },
    },
}


form := url.Values{
    "username": []string{""},
    "email": []string{"jsmith@email.com"},
    "pasword": []string{"1234567"},
}

// Returning error is needed because some validations may 
// cause error conditions such as database check
verrs, err := userChecker.Validate(form) 
if err != nil {
    return err 
}

if len(verrs) > 0 {
	fmt.Println("form has error")
}

// Form fields will be validated only if there is a rule that indicates they must be validated.
func New(form url.Values, ruleSet ...Rule) map[string][]string {
verrs := make(map[string][]string)
mutex := new(sync.RWMutex)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is needed @tatang26. I say this because the variable you're locking on is specific to the function scope and goes passes by value.


// Validation is a condition that must be satisfied by all values in a specific form field.
// or else an error message is displayed indicating that at least one value is invalid.
type Validation func(...string) error
Copy link
Contributor

Choose a reason for hiding this comment

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

The signature of this type should have both, bool and error so that it can tell if its valid or signal there was an error in checking when there is an external condition to do the check p.e. database.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder if signature can better return a string pointer and an error. So in case that any value is invalid, the pointer can be addressed with a validation error message

}

// Match function validates the form field values with a string.
func Match(value string, message ...string) Validation {
Copy link
Contributor

Choose a reason for hiding this comment

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

we could name this one Matches and name the first variable field so its clear we're matching against a field.

@tatang26
Copy link
Contributor Author

tatang26 commented May 6, 2024

This is good @tatang26! Thanks for sharing it. Regarding the API I want to suggest a few things:

Why don't we make Validation the set of rules for determining whether a field is correct or acceptable?

In that sense we would have:

userChecker := Validations{
    Validation{
        Field: "username",
        Rules: RuleSet{
            Required(),
        },
    },
    Validation{
        Field: "email",
        Rules: RuleSet{
            Required(),
        },
    },
    Validation{
        Field: "password",
        Rules: RuleSet{
            MinLength(8, "Password must contain at least 8 characters"),
            func(values ...string) error {
                if strings.Contains(values[0], form.Get("username")) {
                    return errors.New("password must not contain your name.")
                }

                return nil
            },
        },
    },
}


form := url.Values{
    "username": []string{""},
    "email": []string{"jsmith@email.com"},
    "pasword": []string{"1234567"},
}

// Returning error is needed because some validations may 
// cause error conditions such as database check
verrs, err := userChecker.Validate(form) 
if err != nil {
    return err 
}

if len(verrs) > 0 {
	fmt.Println("form has error")
}

@paganotoni Thank you for this suggestion, It looks good, I think we can follow this way.

@paganotoni paganotoni merged commit 99e5268 into leapkit:main May 9, 2024
1 check passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
2 participants