2017-12-12 15:00:34 +07:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2017 eschao <esc.chao@gmail.com>
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2017-12-08 20:02:26 +07:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2024-10-21 00:15:51 +07:00
|
|
|
"errors"
|
2017-12-11 16:14:53 +07:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-12-08 20:02:26 +07:00
|
|
|
"reflect"
|
2017-12-11 16:14:53 +07:00
|
|
|
|
2024-10-19 22:48:50 +07:00
|
|
|
"gopkg.in/yaml.v3"
|
2017-12-11 16:14:53 +07:00
|
|
|
)
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// Default configuration file
|
2017-12-11 16:14:53 +07:00
|
|
|
const (
|
|
|
|
DefaultJSONConfig = "config.json"
|
|
|
|
DefaultYamlConfig = "config.yaml"
|
|
|
|
DefaultPropConfig = "config.properties"
|
2017-12-08 20:02:26 +07:00
|
|
|
)
|
|
|
|
|
2017-12-11 16:14:53 +07:00
|
|
|
const (
|
|
|
|
JSONConfigType = "json"
|
|
|
|
YamlConfigType = "yaml"
|
|
|
|
PropConfigType = "properties"
|
|
|
|
)
|
|
|
|
|
2024-10-21 18:32:24 +07:00
|
|
|
var (
|
|
|
|
errExpectStructPointerInsteadOf = errors.New("expect a structure pointer type instead of")
|
|
|
|
errFileNotFound = errors.New("file not found")
|
|
|
|
errNotImplemented = errors.New("not implemented")
|
|
|
|
errUnsupportedConfigFile = errors.New("unsupported config file")
|
|
|
|
errUnsupportedType = errors.New("unsupported type")
|
|
|
|
errValueCannotBeChanged = errors.New("value cannot be changed")
|
|
|
|
)
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// ParseDefault parses the given structure, extract default value from its tag
|
|
|
|
// and set structure with these values.
|
|
|
|
// Normally, ParseDefault should be called before any other parsing functions
|
|
|
|
// to set default values for structure.
|
2017-12-11 16:14:53 +07:00
|
|
|
func ParseDefault(i interface{}) error {
|
|
|
|
ptrRef := reflect.ValueOf(i)
|
|
|
|
|
|
|
|
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
|
2024-10-21 18:32:24 +07:00
|
|
|
return fmt.Errorf("%w: %s",
|
|
|
|
errExpectStructPointerInsteadOf, ptrRef.Kind().String())
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
valueOfStruct := ptrRef.Elem()
|
|
|
|
if valueOfStruct.Kind() != reflect.Struct {
|
2024-10-21 18:32:24 +07:00
|
|
|
return fmt.Errorf("%w: %s",
|
|
|
|
errExpectStructPointerInsteadOf, valueOfStruct.Kind().String())
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return parseValue(valueOfStruct)
|
|
|
|
}
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
func parseValue(value reflect.Value) error {
|
2017-12-11 16:14:53 +07:00
|
|
|
var err error
|
2024-10-21 00:15:51 +07:00
|
|
|
|
|
|
|
typeOfStruct := value.Type()
|
|
|
|
|
|
|
|
for i := 0; i < value.NumField() && err == nil; i++ {
|
|
|
|
valueOfField := value.Field(i)
|
2017-12-11 16:14:53 +07:00
|
|
|
kindOfField := valueOfField.Kind()
|
|
|
|
structOfField := typeOfStruct.Field(i)
|
|
|
|
|
|
|
|
if kindOfField == reflect.Ptr {
|
|
|
|
if !valueOfField.IsNil() && valueOfField.CanSet() {
|
|
|
|
err = ParseDefault(valueOfField.Interface())
|
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else if kindOfField == reflect.Struct {
|
|
|
|
err = parseValue(valueOfField)
|
|
|
|
}
|
|
|
|
|
|
|
|
defValue, ok := structOfField.Tag.Lookup("default")
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
err = setValue(valueOfField, defValue, structOfField)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
2017-12-11 16:14:53 +07:00
|
|
|
|
2024-10-21 18:32:24 +07:00
|
|
|
func setValue(value reflect.Value, defValue string, field reflect.StructField) error {
|
2024-10-24 16:56:21 +07:00
|
|
|
if setUnmarshalTextValue(value, defValue) {
|
|
|
|
return nil
|
2024-10-23 14:47:10 +07:00
|
|
|
}
|
|
|
|
|
2024-10-24 16:56:21 +07:00
|
|
|
if ok, err := setDurationValue(value, defValue); ok {
|
|
|
|
return err
|
2024-10-23 20:13:43 +07:00
|
|
|
}
|
|
|
|
|
2024-10-24 16:56:21 +07:00
|
|
|
var err error
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
switch value.Kind() {
|
|
|
|
case reflect.Bool:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithBool(value, defValue)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.String:
|
|
|
|
value.SetString(defValue)
|
|
|
|
case reflect.Int8:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithIntX(value, defValue, Int8Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Int16:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithIntX(value, defValue, Int16Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Int, reflect.Int32:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithIntX(value, defValue, Int32Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Int64:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithIntX(value, defValue, Int64Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Uint8:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithUintX(value, defValue, Uint8Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Uint16:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithUintX(value, defValue, Uint16Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Uint, reflect.Uint32:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithUintX(value, defValue, Uint32Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Uint64:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithUintX(value, defValue, Uint64Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Float32:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithFloatX(value, defValue, Float32Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Float64:
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithFloatX(value, defValue, Float64Size)
|
2024-10-21 00:15:51 +07:00
|
|
|
case reflect.Slice:
|
2024-10-21 18:32:24 +07:00
|
|
|
sp, ok := field.Tag.Lookup("separator")
|
2024-10-21 00:15:51 +07:00
|
|
|
if !ok {
|
|
|
|
sp = ":"
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
2024-10-21 00:15:51 +07:00
|
|
|
|
2024-10-24 16:56:21 +07:00
|
|
|
err = setValueWithSlice(value, defValue, sp)
|
2024-10-21 00:15:51 +07:00
|
|
|
default:
|
2024-10-21 18:32:24 +07:00
|
|
|
err = fmt.Errorf("%w: %s", errUnsupportedType, value.Kind().String())
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
2017-12-08 20:02:26 +07:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// ParseCli parses given structure interface and set it with command line input
|
2024-10-21 00:15:51 +07:00
|
|
|
func ParseCli(out interface{}, name string, args []string) ([]string, error) {
|
|
|
|
cli := NewCLI(name)
|
|
|
|
if err := cli.Init(out); err != nil {
|
|
|
|
return nil, err
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
2024-10-21 00:15:51 +07:00
|
|
|
|
|
|
|
err := cli.Parse(args)
|
|
|
|
|
|
|
|
return cli.Args, err
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
2017-12-08 20:02:26 +07:00
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// ParseConfig parses given structure interface and set it with default
|
|
|
|
// configuration file.
|
2024-10-21 00:15:51 +07:00
|
|
|
// The configFlag is a command line flag to tell where to locate configure file.
|
2017-12-12 15:00:34 +07:00
|
|
|
// If the config file doesn't exist, the default config fill will be searched
|
|
|
|
// under the same folder with the fixed order: config.json, config.yaml and
|
|
|
|
// config.properties
|
2024-10-21 00:15:51 +07:00
|
|
|
func ParseConfig(out interface{}, configFlag string) error {
|
2024-10-19 22:48:50 +07:00
|
|
|
configFile := flag.String(configFlag, "", "Specify configuration file")
|
2018-01-05 12:29:05 +07:00
|
|
|
flag.Parse()
|
2024-10-21 00:15:51 +07:00
|
|
|
|
|
|
|
return ParseConfigFile(out, *configFile)
|
2017-12-08 20:02:26 +07:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// ParseConfigFile parses given structure interface and set its value with
|
|
|
|
// the specified configuration file
|
2024-10-21 00:15:51 +07:00
|
|
|
func ParseConfigFile(out interface{}, configFile string) error {
|
2017-12-11 16:14:53 +07:00
|
|
|
var err error
|
|
|
|
if configFile == "" {
|
|
|
|
configFile, err = getDefaultConfigFile()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
configType, err := getConfigFileType(configFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch configType {
|
|
|
|
case JSONConfigType:
|
2024-10-21 00:15:51 +07:00
|
|
|
err = parseJSON(out, configFile)
|
2017-12-11 16:14:53 +07:00
|
|
|
case YamlConfigType:
|
2024-10-21 00:15:51 +07:00
|
|
|
err = parseYaml(out, configFile)
|
2017-12-11 16:14:53 +07:00
|
|
|
case PropConfigType:
|
2024-10-21 00:15:51 +07:00
|
|
|
err = parseProp(out, configFile)
|
2017-12-11 16:14:53 +07:00
|
|
|
default:
|
2024-10-21 18:32:24 +07:00
|
|
|
err = fmt.Errorf("%w: %s", errUnsupportedConfigFile, configFile)
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
2024-10-19 22:48:50 +07:00
|
|
|
return err
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// parseJSON parses JSON file and set structure with its value
|
2024-10-21 00:15:51 +07:00
|
|
|
func parseJSON(out interface{}, jsonFile string) error {
|
2024-10-19 23:05:20 +07:00
|
|
|
raw, err := os.ReadFile(jsonFile)
|
2017-12-08 20:02:26 +07:00
|
|
|
if err != nil {
|
2024-10-21 18:32:24 +07:00
|
|
|
return fmt.Errorf("open json config file: %w", err)
|
2017-12-08 20:02:26 +07:00
|
|
|
}
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
if err := json.Unmarshal(raw, out); err != nil {
|
|
|
|
return fmt.Errorf("json unmarshal: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// parseYaml parses Yaml file and set structure with its value
|
2024-10-21 00:15:51 +07:00
|
|
|
func parseYaml(out interface{}, yamlFile string) error {
|
2024-10-19 23:05:20 +07:00
|
|
|
raw, err := os.ReadFile(yamlFile)
|
2017-12-08 20:02:26 +07:00
|
|
|
if err != nil {
|
2024-10-21 18:32:24 +07:00
|
|
|
return fmt.Errorf("open yaml config file: %w", err)
|
2017-12-08 20:02:26 +07:00
|
|
|
}
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
if err := yaml.Unmarshal(raw, out); err != nil {
|
|
|
|
return fmt.Errorf("unmarshal yaml: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
2017-12-08 20:02:26 +07:00
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// parseProp parses Properties file and set structure with its value
|
2024-10-21 00:15:51 +07:00
|
|
|
func parseProp(_ interface{}, _ /* The propFile */ string) error {
|
2024-10-21 18:32:24 +07:00
|
|
|
return fmt.Errorf("%w: properties config", errNotImplemented)
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// getDefaultConfigFile returns a existing default config file. The checking
|
|
|
|
// order is fixed with beginning from: config.json to config.yaml and
|
|
|
|
// config.properties
|
2017-12-11 16:14:53 +07:00
|
|
|
func getDefaultConfigFile() (string, error) {
|
|
|
|
exe, err := os.Executable()
|
|
|
|
if err != nil {
|
2024-10-21 18:32:24 +07:00
|
|
|
return "", fmt.Errorf("%w: %w", errFileNotFound, err)
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Dir(exe) + string(filepath.Separator)
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
// Check json config
|
2017-12-11 16:14:53 +07:00
|
|
|
jsonConfig := path + DefaultJSONConfig
|
|
|
|
if _, err := os.Stat(jsonConfig); err == nil {
|
|
|
|
return jsonConfig, nil
|
|
|
|
}
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
// Check yaml config
|
2017-12-11 16:14:53 +07:00
|
|
|
yamlConfig := path + DefaultYamlConfig
|
|
|
|
if _, err := os.Stat(yamlConfig); err == nil {
|
|
|
|
return yamlConfig, nil
|
|
|
|
}
|
|
|
|
|
2024-10-21 00:15:51 +07:00
|
|
|
// Check prop config
|
2017-12-11 16:14:53 +07:00
|
|
|
propConfig := path + DefaultPropConfig
|
|
|
|
if _, err := os.Stat(propConfig); err == nil {
|
|
|
|
return propConfig, nil
|
|
|
|
}
|
|
|
|
|
2024-10-21 18:32:24 +07:00
|
|
|
return "", fmt.Errorf("default config %w in path: %s", errFileNotFound, path)
|
2017-12-11 16:14:53 +07:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:00:34 +07:00
|
|
|
// getConfigFileType analyzes config file extension name and return
|
|
|
|
// corresponding type: json, yaml or properties
|
2017-12-11 16:14:53 +07:00
|
|
|
func getConfigFileType(configFile string) (string, error) {
|
|
|
|
ext := filepath.Ext(configFile)
|
2024-10-21 00:15:51 +07:00
|
|
|
switch ext {
|
|
|
|
case ".json":
|
2017-12-11 16:14:53 +07:00
|
|
|
return JSONConfigType, nil
|
2024-10-21 00:15:51 +07:00
|
|
|
case ".yaml":
|
2017-12-11 16:14:53 +07:00
|
|
|
return YamlConfigType, nil
|
2024-10-21 00:15:51 +07:00
|
|
|
case ".properties", ".prop":
|
2017-12-11 16:14:53 +07:00
|
|
|
return PropConfigType, nil
|
|
|
|
}
|
|
|
|
|
2024-10-21 18:32:24 +07:00
|
|
|
return "", fmt.Errorf("%w: %s", errUnsupportedConfigFile, configFile)
|
2017-12-08 20:02:26 +07:00
|
|
|
}
|