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 }