github.com/camlistore/go4@v0.0.0-20200104003542-c7e774b10ea0/jsonconfig/jsonconfig.go (about) 1 /* 2 Copyright 2011 The go4 Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package jsonconfig defines a helper type for JSON objects to be 18 // used for configuration. 19 package jsonconfig // import "go4.org/jsonconfig" 20 21 import ( 22 "fmt" 23 "sort" 24 "strconv" 25 "strings" 26 ) 27 28 // Obj is a JSON configuration map. 29 type Obj map[string]interface{} 30 31 // ReadFile reads JSON config data from the specified open file, expanding 32 // all expressions. Use *ConfigParser.ReadFile instead if you 33 // need to set c.IncludeDirs. 34 func ReadFile(configPath string) (Obj, error) { 35 var c ConfigParser 36 return c.ReadFile(configPath) 37 } 38 39 func (jc Obj) RequiredObject(key string) Obj { 40 return jc.obj(key, false) 41 } 42 43 func (jc Obj) OptionalObject(key string) Obj { 44 return jc.obj(key, true) 45 } 46 47 func (jc Obj) obj(key string, optional bool) Obj { 48 jc.noteKnownKey(key) 49 ei, ok := jc[key] 50 if !ok { 51 if optional { 52 return make(Obj) 53 } 54 jc.appendError(fmt.Errorf("Missing required config key %q (object)", key)) 55 return make(Obj) 56 } 57 m, ok := ei.(map[string]interface{}) 58 if !ok { 59 jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei)) 60 return make(Obj) 61 } 62 return m 63 } 64 65 func (jc Obj) RequiredString(key string) string { 66 return jc.string(key, nil) 67 } 68 69 func (jc Obj) OptionalString(key, def string) string { 70 return jc.string(key, &def) 71 } 72 73 func (jc Obj) string(key string, def *string) string { 74 jc.noteKnownKey(key) 75 ei, ok := jc[key] 76 if !ok { 77 if def != nil { 78 return *def 79 } 80 jc.appendError(fmt.Errorf("Missing required config key %q (string)", key)) 81 return "" 82 } 83 s, ok := ei.(string) 84 if !ok { 85 jc.appendError(fmt.Errorf("Expected config key %q to be a string", key)) 86 return "" 87 } 88 return s 89 } 90 91 func (jc Obj) RequiredStringOrObject(key string) interface{} { 92 return jc.stringOrObject(key, true) 93 } 94 95 func (jc Obj) OptionalStringOrObject(key string) interface{} { 96 return jc.stringOrObject(key, false) 97 } 98 99 func (jc Obj) stringOrObject(key string, required bool) interface{} { 100 jc.noteKnownKey(key) 101 ei, ok := jc[key] 102 if !ok { 103 if !required { 104 return nil 105 } 106 jc.appendError(fmt.Errorf("Missing required config key %q (string or object)", key)) 107 return "" 108 } 109 if _, ok := ei.(map[string]interface{}); ok { 110 return ei 111 } 112 if _, ok := ei.(string); ok { 113 return ei 114 } 115 jc.appendError(fmt.Errorf("Expected config key %q to be a string or object", key)) 116 return "" 117 } 118 119 func (jc Obj) RequiredBool(key string) bool { 120 return jc.bool(key, nil) 121 } 122 123 func (jc Obj) OptionalBool(key string, def bool) bool { 124 return jc.bool(key, &def) 125 } 126 127 func (jc Obj) bool(key string, def *bool) bool { 128 jc.noteKnownKey(key) 129 ei, ok := jc[key] 130 if !ok { 131 if def != nil { 132 return *def 133 } 134 jc.appendError(fmt.Errorf("Missing required config key %q (boolean)", key)) 135 return false 136 } 137 switch v := ei.(type) { 138 case bool: 139 return v 140 case string: 141 b, err := strconv.ParseBool(v) 142 if err != nil { 143 jc.appendError(fmt.Errorf("Config key %q has bad boolean format %q", key, v)) 144 } 145 return b 146 default: 147 jc.appendError(fmt.Errorf("Expected config key %q to be a boolean", key)) 148 return false 149 } 150 } 151 152 func (jc Obj) RequiredInt(key string) int { 153 return jc.int(key, nil) 154 } 155 156 func (jc Obj) OptionalInt(key string, def int) int { 157 return jc.int(key, &def) 158 } 159 160 func (jc Obj) int(key string, def *int) int { 161 jc.noteKnownKey(key) 162 ei, ok := jc[key] 163 if !ok { 164 if def != nil { 165 return *def 166 } 167 jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key)) 168 return 0 169 } 170 b, ok := ei.(float64) 171 if !ok { 172 jc.appendError(fmt.Errorf("Expected config key %q to be a number", key)) 173 return 0 174 } 175 return int(b) 176 } 177 178 func (jc Obj) RequiredInt64(key string) int64 { 179 return jc.int64(key, nil) 180 } 181 182 func (jc Obj) OptionalInt64(key string, def int64) int64 { 183 return jc.int64(key, &def) 184 } 185 186 func (jc Obj) int64(key string, def *int64) int64 { 187 jc.noteKnownKey(key) 188 ei, ok := jc[key] 189 if !ok { 190 if def != nil { 191 return *def 192 } 193 jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key)) 194 return 0 195 } 196 b, ok := ei.(float64) 197 if !ok { 198 jc.appendError(fmt.Errorf("Expected config key %q to be a number", key)) 199 return 0 200 } 201 return int64(b) 202 } 203 204 func (jc Obj) RequiredList(key string) []string { 205 return jc.requiredList(key, true) 206 } 207 208 func (jc Obj) OptionalList(key string) []string { 209 return jc.requiredList(key, false) 210 } 211 212 func (jc Obj) requiredList(key string, required bool) []string { 213 jc.noteKnownKey(key) 214 ei, ok := jc[key] 215 if !ok { 216 if required { 217 jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key)) 218 } 219 return nil 220 } 221 eil, ok := ei.([]interface{}) 222 if !ok { 223 jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei)) 224 return nil 225 } 226 sl := make([]string, len(eil)) 227 for i, ei := range eil { 228 s, ok := ei.(string) 229 if !ok { 230 jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei)) 231 return nil 232 } 233 sl[i] = s 234 } 235 return sl 236 } 237 238 func (jc Obj) noteKnownKey(key string) { 239 _, ok := jc["_knownkeys"] 240 if !ok { 241 jc["_knownkeys"] = make(map[string]bool) 242 } 243 jc["_knownkeys"].(map[string]bool)[key] = true 244 } 245 246 func (jc Obj) appendError(err error) { 247 ei, ok := jc["_errors"] 248 if ok { 249 jc["_errors"] = append(ei.([]error), err) 250 } else { 251 jc["_errors"] = []error{err} 252 } 253 } 254 255 // UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls. 256 func (jc Obj) UnknownKeys() []string { 257 ei, ok := jc["_knownkeys"] 258 var known map[string]bool 259 if ok { 260 known = ei.(map[string]bool) 261 } 262 var unknown []string 263 for k, _ := range jc { 264 if ok && known[k] { 265 continue 266 } 267 if strings.HasPrefix(k, "_") { 268 // Permit keys with a leading underscore as a 269 // form of comments. 270 continue 271 } 272 unknown = append(unknown, k) 273 } 274 sort.Strings(unknown) 275 return unknown 276 } 277 278 func (jc Obj) Validate() error { 279 unknown := jc.UnknownKeys() 280 for _, k := range unknown { 281 jc.appendError(fmt.Errorf("Unknown key %q", k)) 282 } 283 284 ei, ok := jc["_errors"] 285 if !ok { 286 return nil 287 } 288 errList := ei.([]error) 289 if len(errList) == 1 { 290 return errList[0] 291 } 292 strs := make([]string, 0) 293 for _, v := range errList { 294 strs = append(strs, v.Error()) 295 } 296 return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", ")) 297 }