Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validators: add validators #15

Merged
merged 7 commits into from
Jun 7, 2024
Merged

validators: add validators #15

merged 7 commits into from
Jun 7, 2024

Conversation

CaioTeixeira95
Copy link
Contributor

What

This PR adds the validation layer to the project using the go-validator library.

Why

To make the request/object validation easier.

Known limitations

N/A

Issue that this PR addresses

N/A

Checklist

PR Structure

  • It is not possible to break this PR down into smaller PRs.
  • This PR does not mix refactoring changes with feature changes.
  • This PR's title starts with name of package that is most changed in the PR, or all if the changes are broad or impact many packages.

Thoroughness

  • This PR adds tests for the new functionality or fixes.
  • All updated queries have been tested (refer to this check if the data set returned by the updated query is expected to be same as the original one).

Release

  • This is not a breaking change.
  • This is ready to be tested in development.
  • The new functionality is gated with a feature flag if this is not ready for production.

Copy link

@marcelosalloum marcelosalloum left a comment

Choose a reason for hiding this comment

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

LGTM!

I've left a few suggestions, but overall I'm impressed with the utility of this lib, great finding!

internal/serve/httperror/errors.go Outdated Show resolved Hide resolved
internal/serve/httperror/errors.go Show resolved Hide resolved

func NewValidator() *validator.Validate {
validate := validator.New()
_ = validate.RegisterValidation("public_key", publicKeyValidation)

Choose a reason for hiding this comment

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

RegisterValidation returns an error, right? You should handle it accordingly here by wrapping it and sending the wrapped version to the caller.

Or at least log the error here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It only returns an error if the key is empty or the function is nil. That's why I'm not handling the error here, I don't think we should worry about it, wdyt?

Comment on lines 45 to 51
case "gt":
if fieldError.Kind() == reflect.Slice || fieldError.Kind() == reflect.Array {
return "Should have at least 1 element"
}
return fmt.Sprintf("Should be greater than %s", fieldError.Param())
case "gte":
return fmt.Sprintf("Should be greater than or equal %s", fieldError.Param())

Choose a reason for hiding this comment

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

I didn't know gt could be used with slices and arrays. In any case, the get and gt messages re not consistent in case of arrays.

Choose a reason for hiding this comment

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

Also, is Should have at least 1 element what you expect to return when using gt with arrays? Isn't fieldError.Param() relevant for that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for this! I'll change it so both will have the same behavior for slices. Also, I'm going to use the param here.

internal/validators/validate.go Outdated Show resolved Hide resolved
internal/validators/validate.go Outdated Show resolved Hide resolved
Comment on lines +25 to +31
func ParseValidationError(errors validator.ValidationErrors) map[string]interface{} {
fieldErrors := make(map[string]interface{})
for _, err := range errors {
fieldErrors[getFieldName(err)] = msgForFieldError(err)
}
return fieldErrors
}

Choose a reason for hiding this comment

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

There will be cases for when more than one validation fails, like in validate:"required,public_key".

🤔 Do you think it's worth returning a slice of error messages, for each validation that fails in that field? Of would this library only return one error per field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It only returns one error per field, the order of the flags decides the validation order.

Choose a reason for hiding this comment

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

It's fine to do incremental error handling in an API

internal/serve/httphandler/payments_handler.go Outdated Show resolved Hide resolved
Copy link
Member

@daniel-burghardt daniel-burghardt left a comment

Choose a reason for hiding this comment

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

Amazing work! This will be super helpful! 🔥
Left some minor suggestions and questions.

Status: http.StatusMethodNotAllowed,
Error: "The method is not allowed for resource at the url requested.",
}

func BadRequest(message string) errorResponse {
func BadRequest(message string, extras map[string]interface{}) *ErrorResponse {
Copy link
Member

Choose a reason for hiding this comment

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

Why are we changing the response to a pointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's because we are able to do things like:

httpErr := DecodeJSONAndValidate(req, &reqBody)
if httpErr != nil {
        httpErr.Render(rw)
        return
}

Additionally, it's not possible to do something like this in go:

return &httperror.BadRequest(...)

internal/serve/httphandler/request_body_validator.go Outdated Show resolved Hide resolved
internal/serve/httperror/errors_test.go Show resolved Hide resolved
require.Len(t, vErrs, 2)

assert.Equal(t, "publicKey", getFieldName(vErrs[0]))
assert.Equal(t, "children[0].name", getFieldName(vErrs[1]))
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking whether in a case like this it could be useful to have the full path rather than only the two last elements.
Any particular reason why you chose to cut it?

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no particular reason, I can make it to return the entire path except for the Root struct (otherwise we'll leak an internal structure). I thought that only showing the last two would make it more clear.

internal/validators/validate_test.go Outdated Show resolved Hide resolved
internal/validators/validate.go Outdated Show resolved Hide resolved
"github.com/stretchr/testify/require"
)

func TestParseValidationError(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

I think it's amazing and super important to have these more complex test cases with dives and etc., but in addition to these, wdyt about adding separate sub tests for each validation error?
I think that way it might be easier to remember to add tests for new validations and we can better test each one individually. For instance, the Should be greater than or equal case of gt is not tested in this one.

Choose a reason for hiding this comment

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

+1

@CaioTeixeira95 CaioTeixeira95 merged commit 0c2d8f2 into main Jun 7, 2024
5 checks passed
@CaioTeixeira95 CaioTeixeira95 deleted the validators branch June 7, 2024 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants