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

xload: Struct level validation #42

Open
ajatprabha opened this issue May 12, 2024 · 5 comments
Open

xload: Struct level validation #42

ajatprabha opened this issue May 12, 2024 · 5 comments

Comments

@ajatprabha
Copy link
Contributor

Overview

With xload, I want to see what option(s) makes sense for doing validation post loading the values into struct and/or nested structs.

Example

type A struct {
	B *xloadtype.Listener `env:"B"`
	C *xloadtype.Listener `env:"C"`
}

func (a *A) Validate() error {
	if a.B == nil && a.C == nil {
		return errors.New("both A and B cannot be nil, one must be set")
	}
}

The required keyword from xload isn't enough to handle such use-case, I think having an optional Validator interface implementation can be used to check if the object passes custom validation checks while returning up the call stack(after all nested values are loaded), and fail if the validation errors out.

type Validator interface {
	Validate() error
}

Proposal

Add some mechanism to Validate structs at any level, and propagate errors all the way up, if any.

@sonnes
Copy link
Collaborator

sonnes commented May 13, 2024

Any kind of validation is already possible. Like tag based or function based can be done after loading the values.

What is the advantage of supporting validations inside xload?

@ajatprabha
Copy link
Contributor Author

ajatprabha commented May 13, 2024

I agree, validations can be done after loading the config as well. Currently, doing manual validation in my code. However, that is explicit validation. Baking this as an optional flow into xload itself can offer implicit behaviour. And it scopes the validation into a well defined function similar to SetupSuite(), SetupTest() etc from github.com/stretchr/testify.

It keeps struct validation closer to struct itself and reduces cognitive load when dealing with multiple validations.

This requirement is mostly true for complex structs, made-up example:

//go:embed root.crt
var rootCert []byte

type Credentials struct {
	PrivateCert *x509.Certificate `vault:"ABC_CERT"`
}

func (c *Credentials) Validate() error {
	return c.PrivateCert.CheckSignatureFrom(rootCert)
}

I think traversal is a key part of why I would want this in xload and not outside, because xload visits every struct in the config, it is easier for it to call Validate if it has a method like that. Doing this manually will need careful thought and actually writing that logic. And, for repeating structs, I don't have to also add explicit validation in my code.

@sonnes
Copy link
Collaborator

sonnes commented May 14, 2024

Something like https://github.com/go-ozzo/ozzo-validation helps with defining and invoking validations, even for nested structs and complex multi value validation.

So the code will look like this:

cfg := Config{ }

err := xload.Load(...)

err = cfg.Validate()

@sonnes
Copy link
Collaborator

sonnes commented May 14, 2024

I’m inclined to not add invoking to xload because it would only work for single key value validation, but might be complex for multi value validation.

@ajatprabha
Copy link
Contributor Author

Makes sense, let me give github.com/go-ozzo/ozzo-validation a go.

I’m inclined to not add invoking to xload because it would only work for single key value validation, but might be complex for multi value validation.

I don't get it, can you give an example?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants