spf13 / pflag

Drop-in replacement for Go's flag package, implementing POSIX/GNU-style --flags.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow to return flag value as original type

howkey666 opened this issue · comments

Hello,
currently the Value interface always returns flag value as string:

type Value interface {
	String() string
	Set(string) error
	Type() string
}

But sometimes would be handy to return value as original type. In the example bellow I would like to set value directly as zerolog.Level type to avoid, later in the code, second conversion of the same string by calling zerolog.ParseLevel(level) again.

// LogLevel struct allows custom validation of log-level flag
type LogLevel struct {
	Level zerolog.Level
	DefValue string
}

func (l LogLevel) String() string {
	return l.Level.String()
}

func (l *LogLevel) Set(level string) error {
	// zerolog.ParseLevel returns true for custom levels, but we don't support them. Custom level is defined as
	// a number from some range stored in string.
	_, err := strconv.Atoi(level)
	if err == nil {
		return fmt.Errorf("unknown log-level value: '%s'", level)
	}

	if validLevel, err = zerolog.ParseLevel(level); err != nil  {
		fmt.Errorf("unknown log-level value: '%s'", level)
	}

	l.Level = validLevel
	return nil
}

func (l *LogLevel) Type() string {
	return "zerolog.Level"
}

It would be nice to see Value interface as this:

type Value interface {
        Get() {}interface
	String() string
	Set(string) error
	Type() string
}

I know that this is not a simple change as it may looks, because anyone will need to update their Value interface implementation. So, maybe there is a better option? :-)

Why not simply add another method (or a function) that allows you to set the value directly? An interface determines what methods a type requires, but that does not mean you are limited to the method set of the interface.

And a Get() interface{} method would be expected to return a copy of the value, not a pointer to the value, as documented in the Getter interface of the standard flag package. That means any modification to the value returned would have no effect.

Why not simply add another method (or a function) that allows you to set the value directly?
The problem is not with setting a value, but how to get the value as original type.

An interface determines what methods a type requires
You are right. My point is to have declared the name (so it's unified) and that anyone can rely that the method exists. It can be then used in other package like viper.

The method can be named as GetOriginal, OriginalType or just Original...

The problem is not with setting a value, but how to get the value as original type.

I apologize for misinterpreting what you meant by "set zerolog.Level directly", but you can still declare a Get() interface{} method to return a copy of the log level, even if it is not in the method set of Value.

My point is to have declared the name (so it's unified) and that anyone can rely that the method exists. It can be then used in other package like viper.

// Get returns the log level as a zerolog.Level value.
func (l *LogLevel) Get() interface{} {
	return l.Level
}

And any other package that wants to use the Get method can now use it.
The method does not need to be in the method set of Value.

But what about packages using this package already?

Adding a new method will not affect packages that use this package. For example, adding a Get method (or whatever you call it) does not suddenly make it incompatible with Value: all of the methods required by the Value interface are still implemented, so existing functionality is not impacted.

Removing or changing behavior of existing functionality breaks compatibility, and that is when other packages start having trouble.

Edit: When i was talking about adding a new method, i was specifically referring to adding it to an implemented type. Adding a method to the method set of an interface such as Value adds a requirement that will break compatibility because all custom Value implementations now must add a Get method. This is not always feasible, as seen in the standard flag package's Func function, so adding such a method to Value is not a good idea.

Adding a new method will not affect packages that use this package. For example, adding a Get method (or whatever you call it) does not suddenly make it incompatible with Value: all of the methods required by the Value interface are still implemented, so existing functionality is not impacted.

I agree with you. From my point of view, the ideal way is to have this method part of this package so it is somehow "standardized". Your suggestion is not applicable to this package, or am I missing something?

What about update the Flag struct and add attribute OrigValue {}interface? There is already DefValue 🤔

I guess what i am wondering is why you believe it is important that pflag add such functionality. You used the word "standardized", but it can just as easily be a "standard" part of your own package without adding another requirement to the pflag package.

It's not a problem to create own package, but the same would be needed also for viper package for example. So, because this package is used by other packages like viper, cobra, ... it makes sense to me to have it here. It simplifies the work in other packages and it comes handy when you do custom validation. It's not uncommon to have a validation method that returns a particular struct.

And a Get() interface{} method would be expected to return a copy of the value, not a pointer to the value, as documented in the Getter interface of the standard flag package. That means any modification to the value returned would have no effect.

I am sorry I somehow missed this info. But this is exactly what I am looking for. The point here is to get the original type. So it should be enough to implement Getter interface:

// Getter is an interface that allows the contents of a Value to be retrieved.
// It wraps the Value interface, rather than being part of it, because it
// appeared after Go 1 and its compatibility rules. All Value types provided
// by this package satisfy the Getter interface, except the type used by Func.
type Getter interface {
	Value
	Get() any
}

Btw we can close this issue in favor of this #320. It describes one of the needs for this change.

Btw we can close this issue in favor of this #320. It describes one of the needs for this change.

I agree #320 is a better place to discuss this. Feel free to close this issue.