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  }