-
Notifications
You must be signed in to change notification settings - Fork 1
feat: use go-playground/validator #8
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
base: master
Are you sure you want to change the base?
Conversation
jrschumacher
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Fesaa There might be some additional checks you want to make, but I think this is a good start. The validator is good, but its a bit confusing if you don't know Go since it will format the errors like the struct not the yaml.
| slog.Error("Error loading config") | ||
| for _, l := range strings.Split(e.Error(), "\n") { | ||
| slog.Error(l) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will give error messages like:
2025/01/05 10:07:38 ERROR Error loading config
2025/01/05 10:07:38 ERROR config validation errors:
2025/01/05 10:07:38 ERROR Config.Sources[1].Heartbeat is required
2025/01/05 10:07:38 ERROR Config.Sources[1].Name is required
2025/01/05 10:07:38 ERROR Config.Sources[1].Info is required
panic: config validation errors:
Config.Sources[1].Heartbeat is required
Config.Sources[1].Name is required
Config.Sources[1].Info is required
goroutine 1 [running]:
main.main()
/Users/ryan/Projects/iCalMerger/main.go:58 +0x4d4
exit status 2There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, I like the weird splitting on new line here.
After you've made the changes to []error slice, and using errors.Join. Can you show me what slog.Error("Error loading config", "error", e) looks like?
If it looks better by calling slog on each error, we should return a slice of errors instead of splitting on newline
| errs := make([]string, len(verr)) | ||
| for i, e := range verr { | ||
| if e.StructNamespace() == "Config.Sources" { | ||
| if e.Tag() == "unique" { | ||
| errs[i] = fmt.Sprintf("%s.%s is not unique", e.Namespace(), e.Param()) | ||
| } | ||
| } else { | ||
| errs[i] = fmt.Sprintf("%s is %s", e.Namespace(), e.Tag()) | ||
| } | ||
| } | ||
| return nil, fmt.Errorf("config validation errors:\n%v", strings.Join(errs, "\n")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required since the unique=EndPoint tag will not return a good error unlike the other validation tags. There is still no way to denote the location of the infringing config line (i.e. Sources[1].EndPoint) so the best we can do is
2025/01/05 10:00:41 ERROR Error loading config
2025/01/05 10:00:41 ERROR validation error: Config.Sources.EndPoint is not unique
panic: validation error: Config.Sources.EndPoint is not unique
goroutine 1 [running]:
main.main()
/Users/ryan/Projects/iCalMerger/main.go:58 +0x4d4
exit status 2There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good, clear enough
| Name string `yaml:"name,omitempty"` | ||
| Component string `yaml:"component,omitempty"` | ||
| Check string `yaml:"check"` | ||
| Check string `yaml:"check" validate:"required"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Fesaa I'm not sure how the FirstOfYear, FirstOfMonth, and FirstOfDay work exactly. If Rules and modifiers can both use them then we can add a oneof.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the filters inside a modifier can use all the rules avaible.
|
|
||
| type Rule struct { | ||
| Name string `yaml:"name,omitempty"` | ||
| Component string `yaml:"component,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Fesaa can this really be empty? All the checks are expecting the component to be defined https://github.com/Fesaa/iCalMerger/blob/master/ical/checks.go#L71
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The firstOfX rules do not need a name, or component. They can stay as omitempty
|
To keep you in the loop, my exams have/are starting so it may be a while before I can properly review this. But from a glance it looks fine, but I haven't had the chance to pull it down and test yet. |
|
No worries. Just wanted to follow up on your request. Best of luck on your exams @Fesaa! |
Fesaa
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work, thanks!
Left some comments here and there that will need to be resolved before merging. I will look into the TODO's you've left in the tests (firstOf) after my exams and resolve them, thanks. Thanks for bringing these up, looks like test are amazing!
|
|
||
| type Rule struct { | ||
| Name string `yaml:"name,omitempty"` | ||
| Component string `yaml:"component,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The firstOfX rules do not need a name, or component. They can stay as omitempty
| Name string `yaml:"name,omitempty"` | ||
| Component string `yaml:"component,omitempty"` | ||
| Check string `yaml:"check"` | ||
| Check string `yaml:"check" validate:"required"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the filters inside a modifier can use all the rules avaible.
| } | ||
| validate := validator.New(validator.WithRequiredStructEnabled()) | ||
| if err := validate.Struct(config); err != nil { | ||
| verr := err.(validator.ValidationErrors) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion on errors fails on wrapped errors - my IDE
I don't know if it could ever return a wrapped error, but worth following.
| errs := make([]string, len(verr)) | ||
| for i, e := range verr { | ||
| if e.StructNamespace() == "Config.Sources" { | ||
| if e.Tag() == "unique" { | ||
| errs[i] = fmt.Sprintf("%s.%s is not unique", e.Namespace(), e.Param()) | ||
| } | ||
| } else { | ||
| errs[i] = fmt.Sprintf("%s is %s", e.Namespace(), e.Tag()) | ||
| } | ||
| } | ||
| return nil, fmt.Errorf("config validation errors:\n%v", strings.Join(errs, "\n")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good, clear enough
| errs[i] = fmt.Sprintf("%s is %s", e.Namespace(), e.Tag()) | ||
| } | ||
| } | ||
| return nil, fmt.Errorf("config validation errors:\n%v", strings.Join(errs, "\n")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use errors.Join instead of strings.Join.
And make errs a slice of errors, and then fmt.Errorf. Thanks
| } | ||
|
|
||
| err := cfg.Validate() | ||
| validate := validator.New() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the same options (validator.WithRequiredStructEnabled()) as in the software
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestLoadConfig(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testing.T provides a t.TempDir() method, I would opt to use this as we're in a testing env.
| }, | ||
| { | ||
| EndPoint: "http://example.com/endpoint2", | ||
| EndPoint: "/endpoint2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the test fail without these changes? If so, great! If not, we'll have to make sure it does. And add a test for it.
Or change the code to allow it. But I believe it would break at this moment.
| } | ||
|
|
||
| err := info.Validate() | ||
| validate := validator.New() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, make sure to use the same options for the validator (validator.WithRequiredStructEnabled())
| slog.Error("Error loading config") | ||
| for _, l := range strings.Split(e.Error(), "\n") { | ||
| slog.Error(l) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, I like the weird splitting on new line here.
After you've made the changes to []error slice, and using errors.Join. Can you show me what slog.Error("Error loading config", "error", e) looks like?
If it looks better by calling slog on each error, we should return a slice of errors instead of splitting on newline
Closes #5