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

Args with type parameters don't work when not passed #184

Closed
slnt opened this issue Jun 7, 2022 · 6 comments · Fixed by #185
Closed

Args with type parameters don't work when not passed #184

slnt opened this issue Jun 7, 2022 · 6 comments · Fixed by #185

Comments

@slnt
Copy link

slnt commented Jun 7, 2022

So I have this struct I've defined:

type JSONValue[T any] struct {
	val T
}

func (v *JSONValue[T]) Get() T {
	return v.val
}

func (v *JSONValue[T]) UnmarshalText(data []byte) error {
	return json.Unmarshal(data, &v.val)
}

I am trying to use it my args like this:

type Args struct {
    // ...
    Environment JSONValue[map[string]string] `arg:"--env"`
    // ....
}

(It has to be this way for Reasons™)

When I pass --env it works just fine, however if I omit it then I get the following error:

error: error processing default value for --environment: invalid character 'm' looking for beginning of object key string
@alexflint
Copy link
Owner

alexflint commented Jun 7, 2022

Do you have a default value configured, perhaps with a struct tag like default:"..."?

type Args struct {
    // ...
    Environment JSONValue[map[string]string] `arg:"--env" default:"something"`
    // ....
}

If so then go-arg will use that default value and pass it to your UnmarshalText. If you have a default tag and its value causes an error when passed to JSONValue[map[string]string].UnmarshalText, then you might see this error. Perhaps this is the source of your problem?

@slnt
Copy link
Author

slnt commented Jun 8, 2022

I do not have any default value configured. :(

I'm currently on version v1.4.2 if that'd affect things.

The full entry is:

type Args struct {
    Environment JSONValue[map[string]string] `arg:"ENVIRONMENT" help:"a JSON blob containing a dict of NAME=VALUE environment variables"`
}

@slnt
Copy link
Author

slnt commented Jun 8, 2022

Is this perhaps related to #160? I was expecting that the arg not being passed would have just left the zero value in place.

@alexflint
Copy link
Owner

Very interesting, I was able to reproduce this in a unit test! I don't know exactly what's happening but a short-term fix seems to be to make your argument a pointer, like this:

type Args struct {
    Environment *JSONValue[map[string]string] `arg:"ENVIRONMENT" help:"a JSON blob containing a dict of NAME=VALUE environment variables"`
}

Nevertheless, this is still definitely a bug and I will continue to investigate.

@alexflint
Copy link
Owner

OK I have a fix but it will require a bit more work to merge it. A second short-term fix for you is to implement encoding.TextMarshaler on your type, as in:

func (v *JSONValue[T]) MarshalText() ([]byte, error) {
	return json.Marshal(v.val)
}

@alexflint
Copy link
Owner

The basic problem here is that when a struct has a non-zero value, we take its value and turn it into a string to use as a default value in help text. Then, later, when a struct supports encoding.TextUnmarshaler we decode its default value from string. However, the stringified version of JSONValue looks like map[string]string{...} whereas the UnmarshalText function on the same struct expects json, not go-style structs. In the end it was a mistake to assume that we could turn structs into strings and then back into structs, and we shouldn't assume that we can do so.

The fix should be to store default values both as strings and structs, and do the decoding from strings to structs in cmdFromStruct rather than in Parse.

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