github.com/phrase/openapi@v0.0.0-20240514140800-49e8a106740e/clients/go/config.go (about)

     1  package phrase
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"gopkg.in/yaml.v2"
    11  )
    12  
    13  // Credentials contains all information to authenticate against phrase.com or a custom host.
    14  type Credentials struct {
    15  	Username string
    16  	Token    string
    17  	TFA      bool
    18  	TFAToken string
    19  	Host     string
    20  }
    21  
    22  // Config contains all information from a .phraseapp.yml config file
    23  type Config struct {
    24  	Credentials
    25  	Debug bool
    26  
    27  	Page    *int
    28  	PerPage *int
    29  
    30  	DefaultProjectID  string
    31  	DefaultFileFormat string
    32  
    33  	Defaults map[string]map[string]interface{}
    34  
    35  	Targets []byte
    36  	Sources []byte
    37  
    38  	UserAgent string
    39  }
    40  
    41  var configNames = []string{".phrase.yml", ".phraseapp.yml"}
    42  
    43  // ReadConfig reads a .phrase.yml config file
    44  func ReadConfig(cfgFilePath string) (*Config, error) {
    45  	rawCfg := map[string]*Config{}
    46  	content, err := configContent(cfgFilePath)
    47  	switch {
    48  	case err != nil:
    49  		return nil, err
    50  	case content == nil:
    51  		return &Config{}, nil
    52  	default:
    53  		err := yaml.Unmarshal(content, rawCfg)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  
    58  		if cfg, found := rawCfg["phrase"]; found {
    59  			return cfg, nil
    60  		}
    61  		if cfg, found := rawCfg["phraseapp"]; found {
    62  			return cfg, nil
    63  		}
    64  
    65  		return nil, errors.New("'phrase' key is missing in config")
    66  	}
    67  }
    68  
    69  func configContent(cfgFilePath string) ([]byte, error) {
    70  	var path string
    71  	var err error
    72  
    73  	if cfgFilePath != "" {
    74  		path = cfgFilePath
    75  		err = nil
    76  	} else {
    77  		path, err = configPath()
    78  	}
    79  
    80  	switch {
    81  	case err != nil:
    82  		return nil, err
    83  	case path == "":
    84  		return nil, nil
    85  	default:
    86  		return ioutil.ReadFile(path)
    87  	}
    88  }
    89  
    90  func configPath() (string, error) {
    91  	if possiblePath := os.Getenv("PHRASEAPP_CONFIG"); possiblePath != "" {
    92  		_, err := os.Stat(possiblePath)
    93  		if err == nil {
    94  			return possiblePath, nil
    95  		}
    96  
    97  		if os.IsNotExist(err) {
    98  			err = fmt.Errorf("file %q (from PHRASEAPP_CONFIG environment variable) doesn't exist", possiblePath)
    99  		}
   100  
   101  		return "", err
   102  	}
   103  
   104  	workingDir, err := os.Getwd()
   105  	if err != nil {
   106  		return "", nil
   107  	}
   108  
   109  	for _, configName := range configNames {
   110  		possiblePath := filepath.Join(workingDir, configName)
   111  		if _, err := os.Stat(possiblePath); err == nil {
   112  			return possiblePath, nil
   113  		}
   114  	}
   115  
   116  	for _, configName := range configNames {
   117  		possiblePath := filepath.Join(defaultConfigDir(), configName)
   118  		if _, err := os.Stat(possiblePath); err == nil {
   119  			return possiblePath, nil
   120  		}
   121  	}
   122  
   123  	return "", nil
   124  }
   125  
   126  func (cfg *Config) UnmarshalYAML(unmarshal func(i interface{}) error) error {
   127  	m := map[string]interface{}{}
   128  	err := ParseYAMLToMap(unmarshal, map[string]interface{}{
   129  		"access_token": &cfg.Credentials.Token,
   130  		"host":         &cfg.Credentials.Host,
   131  		"debug":        &cfg.Debug,
   132  		"page":         &cfg.Page,
   133  		"per_page":     &cfg.PerPage,
   134  		"project_id":   &cfg.DefaultProjectID,
   135  		"file_format":  &cfg.DefaultFileFormat,
   136  		"push":         &cfg.Sources,
   137  		"pull":         &cfg.Targets,
   138  		"defaults":     &m,
   139  	})
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	cfg.Defaults = map[string]map[string]interface{}{}
   145  	for path, rawConfig := range m {
   146  		cfg.Defaults[path], err = ValidateIsRawMap("defaults."+path, rawConfig)
   147  		if err != nil {
   148  			return err
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  const cfgValueErrStr = "configuration key %q has invalid value: %T\nsee https://support.phrase.com/hc/en-us/sections/5784132012828"
   156  const cfgKeyErrStr = "configuration key %q has invalid type: %T\nsee https://support.phrase.com/hc/en-us/sections/5784132012828"
   157  const cfgInvalidKeyErrStr = "configuration key %q unknown\nsee https://support.phrase.com/hc/en-us/sections/5784132012828"
   158  
   159  func ValidateIsString(k string, v interface{}) (string, error) {
   160  	s, ok := v.(string)
   161  	if !ok {
   162  		return "", fmt.Errorf(cfgValueErrStr, k, v)
   163  	}
   164  	return s, nil
   165  }
   166  
   167  func ValidateIsBool(k string, v interface{}) (bool, error) {
   168  	b, ok := v.(bool)
   169  	if !ok {
   170  		return false, fmt.Errorf(cfgValueErrStr, k, v)
   171  	}
   172  	return b, nil
   173  }
   174  
   175  func ValidateIsInt(k string, v interface{}) (int, error) {
   176  	i, ok := v.(int)
   177  	if !ok {
   178  		return 0, fmt.Errorf(cfgValueErrStr, k, v)
   179  	}
   180  	return i, nil
   181  }
   182  
   183  func ValidateIsRawMap(k string, v interface{}) (map[string]interface{}, error) {
   184  	raw, ok := v.(map[interface{}]interface{})
   185  	if !ok {
   186  		return nil, fmt.Errorf(cfgValueErrStr, k, v)
   187  	}
   188  
   189  	ps := map[string]interface{}{}
   190  	for mk, mv := range raw {
   191  		s, ok := mk.(string)
   192  		if !ok {
   193  			return nil, fmt.Errorf(cfgKeyErrStr, fmt.Sprintf("%s.%v", k, mk), mk)
   194  		}
   195  		ps[s] = mv
   196  	}
   197  	return ps, nil
   198  }
   199  
   200  func ConvertToStringMap(raw map[string]interface{}) (map[string]string, error) {
   201  	ps := map[string]string{}
   202  	for mk, mv := range raw {
   203  		switch v := mv.(type) {
   204  		case string:
   205  			ps[mk] = v
   206  		case bool:
   207  			ps[mk] = fmt.Sprintf("%t", v)
   208  		case int:
   209  			ps[mk] = fmt.Sprintf("%d", v)
   210  		default:
   211  			return nil, fmt.Errorf("invalid type of key %q: %T", mk, mv)
   212  		}
   213  	}
   214  	return ps, nil
   215  }
   216  
   217  // Calls the YAML parser function (see yaml.v2/Unmarshaler interface) with a map
   218  // of string to interface. This map is then iterated to match against the given
   219  // map of keys to fields, validates the type and sets the fields accordingly.
   220  func ParseYAMLToMap(unmarshal func(interface{}) error, keysToField map[string]interface{}) error {
   221  	m := map[string]interface{}{}
   222  	if err := unmarshal(m); err != nil {
   223  		return err
   224  	}
   225  
   226  	var err error
   227  	for k, v := range m {
   228  		value, found := keysToField[k]
   229  		if !found {
   230  			return fmt.Errorf(cfgInvalidKeyErrStr, k)
   231  		}
   232  
   233  		switch val := value.(type) {
   234  		case *string:
   235  			*val, err = ValidateIsString(k, v)
   236  		case *int:
   237  			*val, err = ValidateIsInt(k, v)
   238  		case **int:
   239  			*val = new(int)
   240  			**val, err = ValidateIsInt(k, v)
   241  		case *bool:
   242  			*val, err = ValidateIsBool(k, v)
   243  		case *map[string]interface{}:
   244  			*val, err = ValidateIsRawMap(k, v)
   245  		case *[]byte:
   246  			*val, err = yaml.Marshal(v)
   247  		default:
   248  			err = fmt.Errorf(cfgValueErrStr, k, value)
   249  		}
   250  		if err != nil {
   251  			return err
   252  		}
   253  	}
   254  
   255  	return nil
   256  }