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

Validation of config struct and slice #210

Closed
BowlOfSoup opened this issue Jun 20, 2024 · 1 comment
Closed

Validation of config struct and slice #210

BowlOfSoup opened this issue Jun 20, 2024 · 1 comment
Labels
question Further information is requested

Comments

@BowlOfSoup
Copy link
Contributor

BowlOfSoup commented Jun 20, 2024

This is part question, part proposal. First off, thanks for releasing v5 for Goyave. I saw the project on Reddit and I started to rewrite an app built with loosely coupled packages into Goyave.

I've added my own custom configuration, for example:

"some_config_list_of_endpoints": [
    {
        "name": "dummy entry",
        "api_key": "value123",
        "url": "https://example.com",
    },
    {
        "name": "yet another entry",
        "api_key": "uuid-something-something",
        "url": "https://some-url.com",
    }
],
"custom_auth_service": {
    "url": "http://localhost-for-testing:1234",
    "realm": "development",
    "keys_refresh_minutes": 15,
    "verify_with_issuer": false
}

The values in the object custom_auth_service are validated successfully. I'm defining that part like:

config.Register("custom_auth_service.url", config.Entry{
	Value: "",
	Type:  reflect.String,
})

config.Register("custom_auth_service.keys_refresh_minutes", config.Entry{
	Value: "",
	Type:  reflect.Int,
})

// etc.

Question 1; I would like all values to be mandatory. How can I achieve that? Should I build a custom validator for that?

Question 2; I would like my config to be parsed into a struct instead of having to do server.Config().Get("custom_auth_service.keys_refresh_minutes") somewhere, I would like to fetch custom_auth_service directly and get a struct. Is this possible? (I know currently it's not according to documentation and trial)

Then I've created a slice of configs, the some_config_list_of_endpoints category.

Question 3; Does Goyave support this configuration? I can fetch the entries by doing server.Config().Get("some_config_list_of_endpoints").([]interface{}) but I can't seem to be able to 'init' or register the configuration without getting errors:

config.Register("some_config_list_of_endpoints", config.Entry{
	Value:   []ListOfEndpoints{},
	Type:    reflect.Slice,
	IsSlice: true,
})

config.Register("some_config_list_of_endpoints.name", config.Entry{
	Value: "",
	Type:  reflect.String,
})

This will give me the error: panic: attempted to add an entry to non-category "some_config_list_of_endpoints" (on the .name Register().

The proposal part would be to:

  1. Support mandatory values, as in, values can not be empty or nil
  2. Be able to parse the config into a defined struct, as in, unmarshall the config JSON

Thanks in advance :)

@System-Glitch
Copy link
Member

System-Glitch commented Jun 20, 2024

Hello and thank you for your interest in the framework!

Question 1: There is currently no way to make a config entry required at load time. A workaround would be to call config.Has() after loading the config, but this is inconvenient.

This could be an improvement quite easy to implement actually. We could add a Required field in config.Entry and simply check the value isn't nil in Entry.validate().

Question 2: configuration is not designed with this use-case in mind, so no it's not possible. I would advise embedding your custom config and unmarshalling it on initialization. You could even use the validation package for this and have all the flexibility and granularity you want. Quick test I made:

//go:embed customConfig.json
var customConfig []byte

func main() {
	var customCfg any
	err := json.Unmarshal(customConfig, &customCfg)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	rules := validation.RuleSet{
		{Path: validation.CurrentElement, Rules: validation.List{validation.Required(), validation.Object()}},
		{Path: "entry", Rules: validation.List{validation.Required(), validation.Int(), validation.Min(0)}},
	}

	validationOpts := &validation.Options{
		Data:     customCfg,
		Rules:    rules,
		Language: lang.New().GetDefault(),
	}
	validationErrs, errs := validation.Validate(validationOpts)
	if len(errs) > 0 {
		fmt.Fprintln(os.Stderr, errs)
		os.Exit(1)
	}

	if validationErrs != nil {
		message, _ := json.MarshalIndent(validationErrs, "", "  ")
		fmt.Fprintln(os.Stderr, string(message))
		os.Exit(1)
	}

	structConfig := typeutil.MustConvert[*CustomConfig](customCfg)
	//...
}

Question 3: No, Goyave does not support slices of structures/objects in the current implementation. The best you could do is the following:

config.Register("test", config.Entry{
	Value:   []any{},
	Type:    reflect.Interface,
	IsSlice: true,
})
{
    "test": [
        {"a": 1},
        {"b": 2, "c": 3}
    ]
}

And you will get a []any{map[string]any{a: 1}, map[string]any{b:2, c:3}}.

In short, only slice of basic types are properly supported. A slice cannot have a child entry.

Conclusion: this raises an interesting point to think about for future improvements of the config package.

@System-Glitch System-Glitch added the question Further information is requested label Jun 20, 2024
System-Glitch pushed a commit that referenced this issue Jun 21, 2024
Add Required property to indicate a config registration can't be nil
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants