blob: e62e11c327a3a8bea0c406364bfd01e40af740e3 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package flagenum is a utility package which facilitates implementation of
// flag.Value, json.Marshaler, and json.Unmarshaler interfaces via a string-to-
// value mapping.
package flagenum
import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
)
// Enum is a mapping of enumeration key strings to values that can be used as
// flags.
//
// Strings can be mapped to any value type that is comparable via
// reflect.DeepEqual.
type Enum map[string]interface{}
// GetKey performs reverse lookup of the enumeration value, returning the
// key that corresponds to the value.
//
// If multiple keys correspond to the same value, the result is undefined.
func (e Enum) GetKey(value interface{}) string {
for k, v := range e {
if reflect.DeepEqual(v, value) {
return k
}
}
return ""
}
// GetValue returns the mapped enumeration value associated with a key.
func (e Enum) GetValue(key string) (interface{}, error) {
for k, v := range e {
if k == key {
return v, nil
}
}
return nil, fmt.Errorf("flagenum: Invalid value; must be one of [%s]", e.Choices())
}
// Choices returns a comma-separated string listing sorted enumeration choices.
func (e Enum) Choices() string {
keys := make([]string, 0, len(e))
for k := range e {
if k == "" {
k = `""`
}
keys = append(keys, k)
}
sort.Strings(keys)
return strings.Join(keys, ", ")
}
// Sets the value v to the enumeration value mapped to the supplied key.
func (e Enum) setValue(v interface{}, key string) error {
i, err := e.GetValue(key)
if err != nil {
return err
}
vValue := reflect.ValueOf(v).Elem()
if !vValue.CanSet() {
panic(fmt.Errorf("flagenum: Cannot set supplied value, %v", vValue))
}
iValue := reflect.ValueOf(i)
if !vValue.Type().AssignableTo(iValue.Type()) {
panic(fmt.Errorf("flagenum: Enumeration type (%v) is incompatible with supplied value (%v)",
vValue.Type(), iValue.Type()))
}
vValue.Set(iValue)
return nil
}
// FlagSet implements flag.Value's Set semantics. It identifies the mapped value
// associated with the supplied key and stores it in the supplied interface.
//
// The interface, v, must be a valid pointer to the mapped enumeration type.
func (e Enum) FlagSet(v interface{}, key string) error {
return e.setValue(v, key)
}
// FlagString implements flag.Value's String semantics.
func (e Enum) FlagString(v interface{}) string {
return e.GetKey(v)
}
// JSONUnmarshal implements json.Unmarshaler's UnmarshalJSON semantics. It
// parses data containing a quoted string, identifies the enumeration value
// associated with that string, and stores it in the supplied interface.
//
// The interface, v, must be a valid pointer to the mapped enumeration type.
// a string corresponding to one of the enum's keys.
func (e Enum) JSONUnmarshal(v interface{}, data []byte) error {
s := ""
if err := json.Unmarshal(data, &s); err != nil {
return err
}
return e.FlagSet(v, s)
}
// JSONMarshal implements json.Marshaler's MarshalJSON semantics. It marshals
// the value in the supplied interface to its associated key and emits a quoted
// string containing that key.
//
// The interface, v, must be a valid pointer to the mapped enumeration type.
func (e Enum) JSONMarshal(v interface{}) ([]byte, error) {
key := e.GetKey(v)
data, err := json.Marshal(&key)
if err != nil {
return nil, err
}
return data, nil
}