-
Notifications
You must be signed in to change notification settings - Fork 24
/
dynjson.go
134 lines (118 loc) · 4.2 KB
/
dynjson.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright 2015 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package flagz
import (
"encoding/json"
"reflect"
"sync/atomic"
"unsafe"
flag "github.com/spf13/pflag"
)
// DynJSON creates a `Flag` that is backed by an arbitrary JSON which is safe to change dynamically at runtime.
// The `value` must be a pointer to a struct that is JSON (un)marshallable.
// New values based on the default constructor of `value` type will be created on each update.
func DynJSON(flagSet *flag.FlagSet, name string, value interface{}, usage string) *DynJSONValue {
reflectVal := reflect.ValueOf(value)
if reflectVal.Kind() != reflect.Ptr || reflectVal.Elem().Kind() != reflect.Struct {
panic("DynJSON value must be a pointer to a struct")
}
dynValue := &DynJSONValue{
ptr: unsafe.Pointer(reflectVal.Pointer()),
structType: reflectVal.Type().Elem(),
flagSet: flagSet,
flagName: name,
}
f := flagSet.VarPF(dynValue, name, "", usage)
f.DefValue = dynValue.usageString()
MarkFlagDynamic(f)
return dynValue
}
// DynJSONValue is a flag-related JSON struct value wrapper.
type DynJSONValue struct {
structType reflect.Type
ptr unsafe.Pointer
validator func(interface{}) error
notifier func(oldValue interface{}, newValue interface{})
flagName string
flagSet *flag.FlagSet
}
// Get retrieves the value in its original JSON struct type in a thread-safe manner.
func (d *DynJSONValue) Get() interface{} {
return d.unsafeToStoredType(atomic.LoadPointer(&d.ptr))
}
// Set updates the value from a string representation in a thread-safe manner.
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
// optional validator.
// If a notifier is set on the value, it will be invoked in a separate go-routine.
func (d *DynJSONValue) Set(input string) error {
someStruct := reflect.New(d.structType).Interface()
if err := json.Unmarshal([]byte(input), someStruct); err != nil {
return err
}
if d.validator != nil {
if err := d.validator(someStruct); err != nil {
return err
}
}
oldPtr := atomic.SwapPointer(&d.ptr, unsafe.Pointer(reflect.ValueOf(someStruct).Pointer()))
if d.notifier != nil {
go d.notifier(d.unsafeToStoredType(oldPtr), someStruct)
}
return nil
}
// WithValidator adds a function that checks values before they're set.
// Any error returned by the validator will lead to the value being rejected.
// Validators are executed on the same go-routine as the call to `Set`.
func (d *DynJSONValue) WithValidator(validator func(interface{}) error) *DynJSONValue {
d.validator = validator
return d
}
// WithNotifier adds a function is called every time a new value is successfully set.
// Each notifier is executed in a new go-routine.
func (d *DynJSONValue) WithNotifier(notifier func(oldValue interface{}, newValue interface{})) *DynJSONValue {
d.notifier = notifier
return d
}
// WithFileFlag adds an companion <name>_path flag that allows this value to be read from a file with flagz.ReadFileFlags.
//
// This is useful for reading large JSON files as flags. If the companion flag's value (whether default or overwritten)
// is set to empty string, nothing is read.
//
// Flag value reads are subject to notifiers and validators.
func (d *DynJSONValue) WithFileFlag(defaultPath string) *DynJSONValue {
FileReadFlag(d.flagSet, d.flagName, defaultPath)
return d
}
// Type is an indicator of what this flag represents.
func (d *DynJSONValue) Type() string {
return "dyn_json"
}
// PrettyString returns a nicely structured representation of the type.
// In this case it returns a pretty-printed JSON.
func (d *DynJSONValue) PrettyString() string {
out, err := json.MarshalIndent(d.Get(), "", " ")
if err != nil {
return "ERR"
}
return string(out)
}
// String returns the canonical string representation of the type.
func (d *DynJSONValue) String() string {
out, err := json.Marshal(d.Get())
if err != nil {
return "ERR"
}
return string(out)
}
func (d *DynJSONValue) usageString() string {
s := d.String()
if len(s) > 128 {
return "{ ... truncated ... }"
} else {
return s
}
}
func (d *DynJSONValue) unsafeToStoredType(p unsafe.Pointer) interface{} {
n := reflect.NewAt(d.structType, p)
return n.Interface()
}