Skip to content

Conversation

@jrschumacher
Copy link
Contributor

Closes #5

Copy link
Contributor Author

@jrschumacher jrschumacher left a 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.

Comment on lines +54 to +57
slog.Error("Error loading config")
for _, l := range strings.Split(e.Error(), "\n") {
slog.Error(l)
}
Copy link
Contributor Author

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 2

Copy link
Owner

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

Comment on lines +108 to +118
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"))
Copy link
Contributor Author

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 2

Copy link
Owner

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"`
Copy link
Contributor Author

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.

Copy link
Owner

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"`
Copy link
Contributor Author

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

Copy link
Owner

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

@Fesaa
Copy link
Owner

Fesaa commented Jan 6, 2025

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.

@jrschumacher
Copy link
Contributor Author

No worries. Just wanted to follow up on your request. Best of luck on your exams @Fesaa!

Copy link
Owner

@Fesaa Fesaa 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, 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"`
Copy link
Owner

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"`
Copy link
Owner

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)
Copy link
Owner

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.

Comment on lines +108 to +118
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"))
Copy link
Owner

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"))
Copy link
Owner

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()
Copy link
Owner

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) {
Copy link
Owner

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",
Copy link
Owner

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()
Copy link
Owner

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())

Comment on lines +54 to +57
slog.Error("Error loading config")
for _, l := range strings.Split(e.Error(), "\n") {
slog.Error(l)
}
Copy link
Owner

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

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.

Implement go-playground/validator for validating the config files

2 participants