github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/jsonconfig/jsonconfig.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     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
    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  // Reads json config data from the specified open file, expanding
    32  // all expressions
    33  func ReadFile(configPath string) (Obj, error) {
    34  	var c ConfigParser
    35  	return c.ReadFile(configPath)
    36  }
    37  
    38  func (jc Obj) RequiredObject(key string) Obj {
    39  	return jc.obj(key, false)
    40  }
    41  
    42  func (jc Obj) OptionalObject(key string) Obj {
    43  	return jc.obj(key, true)
    44  }
    45  
    46  func (jc Obj) obj(key string, optional bool) Obj {
    47  	jc.noteKnownKey(key)
    48  	ei, ok := jc[key]
    49  	if !ok {
    50  		if optional {
    51  			return make(Obj)
    52  		}
    53  		jc.appendError(fmt.Errorf("Missing required config key %q (object)", key))
    54  		return make(Obj)
    55  	}
    56  	m, ok := ei.(map[string]interface{})
    57  	if !ok {
    58  		jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei))
    59  		return make(Obj)
    60  	}
    61  	return Obj(m)
    62  }
    63  
    64  func (jc Obj) RequiredString(key string) string {
    65  	return jc.string(key, nil)
    66  }
    67  
    68  func (jc Obj) OptionalString(key, def string) string {
    69  	return jc.string(key, &def)
    70  }
    71  
    72  func (jc Obj) string(key string, def *string) string {
    73  	jc.noteKnownKey(key)
    74  	ei, ok := jc[key]
    75  	if !ok {
    76  		if def != nil {
    77  			return *def
    78  		}
    79  		jc.appendError(fmt.Errorf("Missing required config key %q (string)", key))
    80  		return ""
    81  	}
    82  	s, ok := ei.(string)
    83  	if !ok {
    84  		jc.appendError(fmt.Errorf("Expected config key %q to be a string", key))
    85  		return ""
    86  	}
    87  	return s
    88  }
    89  
    90  func (jc Obj) RequiredStringOrObject(key string) interface{} {
    91  	return jc.stringOrObject(key, true)
    92  }
    93  
    94  func (jc Obj) OptionalStringOrObject(key string) interface{} {
    95  	return jc.stringOrObject(key, false)
    96  }
    97  
    98  func (jc Obj) stringOrObject(key string, required bool) interface{} {
    99  	jc.noteKnownKey(key)
   100  	ei, ok := jc[key]
   101  	if !ok {
   102  		if !required {
   103  			return nil
   104  		}
   105  		jc.appendError(fmt.Errorf("Missing required config key %q (string or object)", key))
   106  		return ""
   107  	}
   108  	if _, ok := ei.(map[string]interface{}); ok {
   109  		return ei
   110  	}
   111  	if _, ok := ei.(string); ok {
   112  		return ei
   113  	}
   114  	jc.appendError(fmt.Errorf("Expected config key %q to be a string or object", key))
   115  	return ""
   116  }
   117  
   118  func (jc Obj) RequiredBool(key string) bool {
   119  	return jc.bool(key, nil)
   120  }
   121  
   122  func (jc Obj) OptionalBool(key string, def bool) bool {
   123  	return jc.bool(key, &def)
   124  }
   125  
   126  func (jc Obj) bool(key string, def *bool) bool {
   127  	jc.noteKnownKey(key)
   128  	ei, ok := jc[key]
   129  	if !ok {
   130  		if def != nil {
   131  			return *def
   132  		}
   133  		jc.appendError(fmt.Errorf("Missing required config key %q (boolean)", key))
   134  		return false
   135  	}
   136  	switch v := ei.(type) {
   137  	case bool:
   138  		return v
   139  	case string:
   140  		b, err := strconv.ParseBool(v)
   141  		if err != nil {
   142  			jc.appendError(fmt.Errorf("Config key %q has bad boolean format %q", key, v))
   143  		}
   144  		return b
   145  	default:
   146  		jc.appendError(fmt.Errorf("Expected config key %q to be a boolean", key))
   147  		return false
   148  	}
   149  }
   150  
   151  func (jc Obj) RequiredInt(key string) int {
   152  	return jc.int(key, nil)
   153  }
   154  
   155  func (jc Obj) OptionalInt(key string, def int) int {
   156  	return jc.int(key, &def)
   157  }
   158  
   159  func (jc Obj) int(key string, def *int) int {
   160  	jc.noteKnownKey(key)
   161  	ei, ok := jc[key]
   162  	if !ok {
   163  		if def != nil {
   164  			return *def
   165  		}
   166  		jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
   167  		return 0
   168  	}
   169  	b, ok := ei.(float64)
   170  	if !ok {
   171  		jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
   172  		return 0
   173  	}
   174  	return int(b)
   175  }
   176  
   177  func (jc Obj) RequiredInt64(key string) int64 {
   178  	return jc.int64(key, nil)
   179  }
   180  
   181  func (jc Obj) OptionalInt64(key string, def int64) int64 {
   182  	return jc.int64(key, &def)
   183  }
   184  
   185  func (jc Obj) int64(key string, def *int64) int64 {
   186  	jc.noteKnownKey(key)
   187  	ei, ok := jc[key]
   188  	if !ok {
   189  		if def != nil {
   190  			return *def
   191  		}
   192  		jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
   193  		return 0
   194  	}
   195  	b, ok := ei.(float64)
   196  	if !ok {
   197  		jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
   198  		return 0
   199  	}
   200  	return int64(b)
   201  }
   202  
   203  func (jc Obj) RequiredList(key string) []string {
   204  	return jc.requiredList(key, true)
   205  }
   206  
   207  func (jc Obj) OptionalList(key string) []string {
   208  	return jc.requiredList(key, false)
   209  }
   210  
   211  func (jc Obj) requiredList(key string, required bool) []string {
   212  	jc.noteKnownKey(key)
   213  	ei, ok := jc[key]
   214  	if !ok {
   215  		if required {
   216  			jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key))
   217  		}
   218  		return nil
   219  	}
   220  	eil, ok := ei.([]interface{})
   221  	if !ok {
   222  		jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei))
   223  		return nil
   224  	}
   225  	sl := make([]string, len(eil))
   226  	for i, ei := range eil {
   227  		s, ok := ei.(string)
   228  		if !ok {
   229  			jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei))
   230  			return nil
   231  		}
   232  		sl[i] = s
   233  	}
   234  	return sl
   235  }
   236  
   237  func (jc Obj) noteKnownKey(key string) {
   238  	_, ok := jc["_knownkeys"]
   239  	if !ok {
   240  		jc["_knownkeys"] = make(map[string]bool)
   241  	}
   242  	jc["_knownkeys"].(map[string]bool)[key] = true
   243  }
   244  
   245  func (jc Obj) appendError(err error) {
   246  	ei, ok := jc["_errors"]
   247  	if ok {
   248  		jc["_errors"] = append(ei.([]error), err)
   249  	} else {
   250  		jc["_errors"] = []error{err}
   251  	}
   252  }
   253  
   254  // UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls.
   255  func (jc Obj) UnknownKeys() []string {
   256  	ei, ok := jc["_knownkeys"]
   257  	var known map[string]bool
   258  	if ok {
   259  		known = ei.(map[string]bool)
   260  	}
   261  	var unknown []string
   262  	for k, _ := range jc {
   263  		if ok && known[k] {
   264  			continue
   265  		}
   266  		if strings.HasPrefix(k, "_") {
   267  			// Permit keys with a leading underscore as a
   268  			// form of comments.
   269  			continue
   270  		}
   271  		unknown = append(unknown, k)
   272  	}
   273  	sort.Strings(unknown)
   274  	return unknown
   275  }
   276  
   277  func (jc Obj) Validate() error {
   278  	unknown := jc.UnknownKeys()
   279  	for _, k := range unknown {
   280  		jc.appendError(fmt.Errorf("Unknown key %q", k))
   281  	}
   282  
   283  	ei, ok := jc["_errors"]
   284  	if !ok {
   285  		return nil
   286  	}
   287  	errList := ei.([]error)
   288  	if len(errList) == 1 {
   289  		return errList[0]
   290  	}
   291  	strs := make([]string, 0)
   292  	for _, v := range errList {
   293  		strs = append(strs, v.Error())
   294  	}
   295  	return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", "))
   296  }