github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/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) RequiredList(key string) []string {
   178  	return jc.requiredList(key, true)
   179  }
   180  
   181  func (jc Obj) OptionalList(key string) []string {
   182  	return jc.requiredList(key, false)
   183  }
   184  
   185  func (jc Obj) requiredList(key string, required bool) []string {
   186  	jc.noteKnownKey(key)
   187  	ei, ok := jc[key]
   188  	if !ok {
   189  		if required {
   190  			jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key))
   191  		}
   192  		return nil
   193  	}
   194  	eil, ok := ei.([]interface{})
   195  	if !ok {
   196  		jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei))
   197  		return nil
   198  	}
   199  	sl := make([]string, len(eil))
   200  	for i, ei := range eil {
   201  		s, ok := ei.(string)
   202  		if !ok {
   203  			jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei))
   204  			return nil
   205  		}
   206  		sl[i] = s
   207  	}
   208  	return sl
   209  }
   210  
   211  func (jc Obj) noteKnownKey(key string) {
   212  	_, ok := jc["_knownkeys"]
   213  	if !ok {
   214  		jc["_knownkeys"] = make(map[string]bool)
   215  	}
   216  	jc["_knownkeys"].(map[string]bool)[key] = true
   217  }
   218  
   219  func (jc Obj) appendError(err error) {
   220  	ei, ok := jc["_errors"]
   221  	if ok {
   222  		jc["_errors"] = append(ei.([]error), err)
   223  	} else {
   224  		jc["_errors"] = []error{err}
   225  	}
   226  }
   227  
   228  // UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls.
   229  func (jc Obj) UnknownKeys() []string {
   230  	ei, ok := jc["_knownkeys"]
   231  	var known map[string]bool
   232  	if ok {
   233  		known = ei.(map[string]bool)
   234  	}
   235  	var unknown []string
   236  	for k, _ := range jc {
   237  		if ok && known[k] {
   238  			continue
   239  		}
   240  		if strings.HasPrefix(k, "_") {
   241  			// Permit keys with a leading underscore as a
   242  			// form of comments.
   243  			continue
   244  		}
   245  		unknown = append(unknown, k)
   246  	}
   247  	sort.Strings(unknown)
   248  	return unknown
   249  }
   250  
   251  func (jc Obj) Validate() error {
   252  	unknown := jc.UnknownKeys()
   253  	for _, k := range unknown {
   254  		jc.appendError(fmt.Errorf("Unknown key %q", k))
   255  	}
   256  
   257  	ei, ok := jc["_errors"]
   258  	if !ok {
   259  		return nil
   260  	}
   261  	errList := ei.([]error)
   262  	if len(errList) == 1 {
   263  		return errList[0]
   264  	}
   265  	strs := make([]string, 0)
   266  	for _, v := range errList {
   267  		strs = append(strs, v.Error())
   268  	}
   269  	return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", "))
   270  }