1
1
package clibase
2
2
3
3
import (
4
+ "bytes"
5
+ "encoding/json"
4
6
"os"
5
7
"strings"
6
8
@@ -65,6 +67,20 @@ type Option struct {
65
67
ValueSource ValueSource `json:"value_source,omitempty"`
66
68
}
67
69
70
+ // optionNoMethods is just a wrapper around Option so we can defer to the
71
+ // default json.Unmarshaler behavior.
72
+ type optionNoMethods Option
73
+
74
+ func (o * Option ) UnmarshalJSON (data []byte ) error {
75
+ // If an option has no values, we have no idea how to unmarshal it.
76
+ // So just discard the json data.
77
+ if o .Value == nil {
78
+ o .Value = & DiscardValue
79
+ }
80
+
81
+ return json .Unmarshal (data , (* optionNoMethods )(o ))
82
+ }
83
+
68
84
func (o Option ) YAMLPath () string {
69
85
if o .YAML == "" {
70
86
return ""
@@ -79,15 +95,101 @@ func (o Option) YAMLPath() string {
79
95
// OptionSet is a group of options that can be applied to a command.
80
96
type OptionSet []Option
81
97
98
+ // UnmarshalJSON implements json.Unmarshaler for OptionSets. Options have an
99
+ // interface Value type that cannot handle unmarshalling because the types cannot
100
+ // be inferred. Since it is a slice, instantiating the Options first does not
101
+ // help.
102
+ //
103
+ // However, we typically do instantiate the slice to have the correct types.
104
+ // So this unmarshaller will attempt to find the named option in the existing
105
+ // set, if it cannot, the value is discarded. If the option exists, the value
106
+ // is unmarshalled into the existing option, and replaces the existing option.
107
+ //
108
+ // The value is discarded if it's type cannot be inferred. This behavior just
109
+ // feels "safer", although it should never happen if the correct option set
110
+ // is passed in. The situation where this could occur is if a client and server
111
+ // are on different versions with different options.
112
+ func (optSet * OptionSet ) UnmarshalJSON (data []byte ) error {
113
+ dec := json .NewDecoder (bytes .NewBuffer (data ))
114
+ // Should be a json array, so consume the starting open bracket.
115
+ t , err := dec .Token ()
116
+ if err != nil {
117
+ return xerrors .Errorf ("read array open bracket: %w" , err )
118
+ }
119
+ if t != json .Delim ('[' ) {
120
+ return xerrors .Errorf ("expected array open bracket, got %q" , t )
121
+ }
122
+
123
+ // As long as json elements exist, consume them. The counter is used for
124
+ // better errors.
125
+ var i int
126
+ OptionSetDecodeLoop:
127
+ for dec .More () {
128
+ var opt Option
129
+ // jValue is a placeholder value that allows us to capture the
130
+ // raw json for the value to attempt to unmarshal later.
131
+ var jValue jsonValue
132
+ opt .Value = & jValue
133
+ err := dec .Decode (& opt )
134
+ if err != nil {
135
+ return xerrors .Errorf ("decode %d option: %w" , i , err )
136
+ }
137
+ // This counter is used to contextualize errors to show which element of
138
+ // the array we failed to decode. It is only used in the error above, as
139
+ // if the above works, we can instead use the Option.Name which is more
140
+ // descriptive and useful. So increment here for the next decode.
141
+ i ++
142
+
143
+ // Try to see if the option already exists in the option set.
144
+ // If it does, just update the existing option.
145
+ for optIndex , have := range * optSet {
146
+ if have .Name == opt .Name {
147
+ if jValue != nil {
148
+ err := json .Unmarshal (jValue , & (* optSet )[optIndex ].Value )
149
+ if err != nil {
150
+ return xerrors .Errorf ("decode option %q value: %w" , have .Name , err )
151
+ }
152
+ // Set the opt's value
153
+ opt .Value = (* optSet )[optIndex ].Value
154
+ } else {
155
+ // Hopefully the user passed empty values in the option set. There is no easy way
156
+ // to tell, and if we do not do this, it breaks json.Marshal if we do it again on
157
+ // this new option set.
158
+ opt .Value = (* optSet )[optIndex ].Value
159
+ }
160
+ // Override the existing.
161
+ (* optSet )[optIndex ] = opt
162
+ // Go to the next option to decode.
163
+ continue OptionSetDecodeLoop
164
+ }
165
+ }
166
+
167
+ // If the option doesn't exist, the value will be discarded.
168
+ // We do this because we cannot infer the type of the value.
169
+ opt .Value = DiscardValue
170
+ * optSet = append (* optSet , opt )
171
+ }
172
+
173
+ t , err = dec .Token ()
174
+ if err != nil {
175
+ return xerrors .Errorf ("read array close bracket: %w" , err )
176
+ }
177
+ if t != json .Delim (']' ) {
178
+ return xerrors .Errorf ("expected array close bracket, got %q" , t )
179
+ }
180
+
181
+ return nil
182
+ }
183
+
82
184
// Add adds the given Options to the OptionSet.
83
- func (s * OptionSet ) Add (opts ... Option ) {
84
- * s = append (* s , opts ... )
185
+ func (optSet * OptionSet ) Add (opts ... Option ) {
186
+ * optSet = append (* optSet , opts ... )
85
187
}
86
188
87
189
// Filter will only return options that match the given filter. (return true)
88
- func (s OptionSet ) Filter (filter func (opt Option ) bool ) OptionSet {
190
+ func (optSet OptionSet ) Filter (filter func (opt Option ) bool ) OptionSet {
89
191
cpy := make (OptionSet , 0 )
90
- for _ , opt := range s {
192
+ for _ , opt := range optSet {
91
193
if filter (opt ) {
92
194
cpy = append (cpy , opt )
93
195
}
@@ -96,13 +198,13 @@ func (s OptionSet) Filter(filter func(opt Option) bool) OptionSet {
96
198
}
97
199
98
200
// FlagSet returns a pflag.FlagSet for the OptionSet.
99
- func (s * OptionSet ) FlagSet () * pflag.FlagSet {
100
- if s == nil {
201
+ func (optSet * OptionSet ) FlagSet () * pflag.FlagSet {
202
+ if optSet == nil {
101
203
return & pflag.FlagSet {}
102
204
}
103
205
104
206
fs := pflag .NewFlagSet ("" , pflag .ContinueOnError )
105
- for _ , opt := range * s {
207
+ for _ , opt := range * optSet {
106
208
if opt .Flag == "" {
107
209
continue
108
210
}
@@ -139,8 +241,8 @@ func (s *OptionSet) FlagSet() *pflag.FlagSet {
139
241
140
242
// ParseEnv parses the given environment variables into the OptionSet.
141
243
// Use EnvsWithPrefix to filter out prefixes.
142
- func (s * OptionSet ) ParseEnv (vs []EnvVar ) error {
143
- if s == nil {
244
+ func (optSet * OptionSet ) ParseEnv (vs []EnvVar ) error {
245
+ if optSet == nil {
144
246
return nil
145
247
}
146
248
@@ -154,7 +256,7 @@ func (s *OptionSet) ParseEnv(vs []EnvVar) error {
154
256
envs [v .Name ] = v .Value
155
257
}
156
258
157
- for i , opt := range * s {
259
+ for i , opt := range * optSet {
158
260
if opt .Env == "" {
159
261
continue
160
262
}
@@ -172,7 +274,7 @@ func (s *OptionSet) ParseEnv(vs []EnvVar) error {
172
274
continue
173
275
}
174
276
175
- (* s )[i ].ValueSource = ValueSourceEnv
277
+ (* optSet )[i ].ValueSource = ValueSourceEnv
176
278
if err := opt .Value .Set (envVal ); err != nil {
177
279
merr = multierror .Append (
178
280
merr , xerrors .Errorf ("parse %q: %w" , opt .Name , err ),
@@ -185,14 +287,14 @@ func (s *OptionSet) ParseEnv(vs []EnvVar) error {
185
287
186
288
// SetDefaults sets the default values for each Option, skipping values
187
289
// that already have a value source.
188
- func (s * OptionSet ) SetDefaults () error {
189
- if s == nil {
290
+ func (optSet * OptionSet ) SetDefaults () error {
291
+ if optSet == nil {
190
292
return nil
191
293
}
192
294
193
295
var merr * multierror.Error
194
296
195
- for i , opt := range * s {
297
+ for i , opt := range * optSet {
196
298
// Skip values that may have already been set by the user.
197
299
if opt .ValueSource != ValueSourceNone {
198
300
continue
@@ -212,7 +314,7 @@ func (s *OptionSet) SetDefaults() error {
212
314
)
213
315
continue
214
316
}
215
- (* s )[i ].ValueSource = ValueSourceDefault
317
+ (* optSet )[i ].ValueSource = ValueSourceDefault
216
318
if err := opt .Value .Set (opt .Default ); err != nil {
217
319
merr = multierror .Append (
218
320
merr , xerrors .Errorf ("parse %q: %w" , opt .Name , err ),
@@ -224,9 +326,9 @@ func (s *OptionSet) SetDefaults() error {
224
326
225
327
// ByName returns the Option with the given name, or nil if no such option
226
328
// exists.
227
- func (s * OptionSet ) ByName (name string ) * Option {
228
- for i := range * s {
229
- opt := & (* s )[i ]
329
+ func (optSet * OptionSet ) ByName (name string ) * Option {
330
+ for i := range * optSet {
331
+ opt := & (* optSet )[i ]
230
332
if opt .Name == name {
231
333
return opt
232
334
}
0 commit comments