depot/packages/networking/ipfs-cluster/config/util.go

220 lines
5.1 KiB
Go

package config
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
)
// Saver implements common functionality useful for ComponentConfigs
type Saver struct {
save chan struct{}
BaseDir string
}
// NotifySave signals the SaveCh() channel in a non-blocking fashion.
func (sv *Saver) NotifySave() {
if sv.save == nil {
sv.save = make(chan struct{}, 10)
}
// Non blocking, in case no one's listening
select {
case sv.save <- struct{}{}:
default:
logger.Warn("configuration save channel full")
}
}
// SaveCh returns a channel which is signaled when a component wants
// to persist its configuration
func (sv *Saver) SaveCh() <-chan struct{} {
if sv.save == nil {
sv.save = make(chan struct{})
}
return sv.save
}
// SetBaseDir is a setter for BaseDir and implements
// part of the ComponentConfig interface.
func (sv *Saver) SetBaseDir(dir string) {
sv.BaseDir = dir
}
// DefaultJSONMarshal produces pretty JSON with 2-space indentation
func DefaultJSONMarshal(v interface{}) ([]byte, error) {
bs, err := json.MarshalIndent(v, "", " ")
if err != nil {
return nil, err
}
return bs, nil
}
// SetIfNotDefault sets dest to the value of src if src is not the default
// value of the type.
// dest must be a pointer.
func SetIfNotDefault(src interface{}, dest interface{}) {
switch src.(type) {
case time.Duration:
t := src.(time.Duration)
if t != 0 {
*dest.(*time.Duration) = t
}
case string:
str := src.(string)
if str != "" {
*dest.(*string) = str
}
case uint64:
n := src.(uint64)
if n != 0 {
*dest.(*uint64) = n
}
case int:
n := src.(int)
if n != 0 {
*dest.(*int) = n
}
case float64:
n := src.(float64)
if n != 0 {
*dest.(*float64) = n
}
case bool:
b := src.(bool)
if b {
*dest.(*bool) = b
}
}
}
// DurationOpt provides a datatype to use with ParseDurations
type DurationOpt struct {
// The duration we need to parse
Duration string
// Where to store the result
Dst *time.Duration
// A variable name associated to it for helpful errors.
Name string
}
// ParseDurations takes a time.Duration src and saves it to the given dst.
func ParseDurations(component string, args ...*DurationOpt) error {
for _, arg := range args {
if arg.Duration == "" {
// don't do anything. Let the destination field
// stay at its default.
continue
}
t, err := time.ParseDuration(arg.Duration)
if err != nil {
return fmt.Errorf(
"error parsing %s.%s: %s",
component,
arg.Name,
err,
)
}
*arg.Dst = t
}
return nil
}
type hiddenField struct{}
func (hf hiddenField) MarshalJSON() ([]byte, error) {
return []byte(`"XXX_hidden_XXX"`), nil
}
func (hf hiddenField) UnmarshalJSON(b []byte) error { return nil }
// DisplayJSON takes pointer to a JSON-friendly configuration struct and
// returns the JSON-encoded representation of it filtering out any struct
// fields marked with the tag `hidden:"true"`, but keeping fields marked
// with `"json:omitempty"`.
func DisplayJSON(cfg interface{}) ([]byte, error) {
cfg = reflect.Indirect(reflect.ValueOf(cfg)).Interface()
origStructT := reflect.TypeOf(cfg)
if origStructT.Kind() != reflect.Struct {
panic("the given argument should be a struct")
}
hiddenFieldT := reflect.TypeOf(hiddenField{})
// create a new struct type with same fields
// but setting hidden fields as hidden.
finalStructFields := []reflect.StructField{}
for i := 0; i < origStructT.NumField(); i++ {
f := origStructT.Field(i)
hidden := f.Tag.Get("hidden") == "true"
if f.PkgPath != "" { // skip unexported
continue
}
if hidden {
f.Type = hiddenFieldT
}
// remove omitempty from tag, ignore other tags except json
var jsonTags []string
for _, s := range strings.Split(f.Tag.Get("json"), ",") {
if s != "omitempty" {
jsonTags = append(jsonTags, s)
}
}
f.Tag = reflect.StructTag(fmt.Sprintf("json:\"%s\"", strings.Join(jsonTags, ",")))
finalStructFields = append(finalStructFields, f)
}
// Parse the original JSON into the new
// struct and re-convert it to JSON.
finalStructT := reflect.StructOf(finalStructFields)
finalValue := reflect.New(finalStructT)
data := finalValue.Interface()
origJSON, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
err = json.Unmarshal(origJSON, data)
if err != nil {
return nil, err
}
return DefaultJSONMarshal(data)
}
// Strings is a helper type that (un)marshals a single string to/from a single
// JSON string and a slice of strings to/from a JSON array of strings.
type Strings []string
// UnmarshalJSON conforms to the json.Unmarshaler interface.
func (o *Strings) UnmarshalJSON(data []byte) error {
if data[0] == '[' {
return json.Unmarshal(data, (*[]string)(o))
}
var value string
if err := json.Unmarshal(data, &value); err != nil {
return err
}
if len(value) == 0 {
*o = []string{}
} else {
*o = []string{value}
}
return nil
}
// MarshalJSON conforms to the json.Marshaler interface.
func (o Strings) MarshalJSON() ([]byte, error) {
switch len(o) {
case 0:
return json.Marshal(nil)
case 1:
return json.Marshal(o[0])
default:
return json.Marshal([]string(o))
}
}
var _ json.Unmarshaler = (*Strings)(nil)
var _ json.Marshaler = (*Strings)(nil)