github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/core/config.go (about) 1 package core 2 3 import ( 4 "io" 5 "strings" 6 7 "github.com/xmplusdev/xmcore/common" 8 "github.com/xmplusdev/xmcore/common/buf" 9 "github.com/xmplusdev/xmcore/common/cmdarg" 10 "github.com/xmplusdev/xmcore/main/confloader" 11 "google.golang.org/protobuf/proto" 12 ) 13 14 // ConfigFormat is a configurable format of Xray config file. 15 type ConfigFormat struct { 16 Name string 17 Extension []string 18 Loader ConfigLoader 19 } 20 21 // ConfigLoader is a utility to load Xray config from external source. 22 type ConfigLoader func(input interface{}) (*Config, error) 23 24 // ConfigBuilder is a builder to build core.Config from filenames and formats 25 type ConfigBuilder func(files []string, formats []string) (*Config, error) 26 27 // ConfigMerger merge multiple json configs into on config 28 type ConfigsMerger func(files []string, formats []string) (string, error) 29 30 var ( 31 configLoaderByName = make(map[string]*ConfigFormat) 32 configLoaderByExt = make(map[string]*ConfigFormat) 33 ConfigBuilderForFiles ConfigBuilder 34 ConfigMergedFormFiles ConfigsMerger 35 ) 36 37 // RegisterConfigLoader add a new ConfigLoader. 38 func RegisterConfigLoader(format *ConfigFormat) error { 39 name := strings.ToLower(format.Name) 40 if _, found := configLoaderByName[name]; found { 41 return newError(format.Name, " already registered.") 42 } 43 configLoaderByName[name] = format 44 45 for _, ext := range format.Extension { 46 lext := strings.ToLower(ext) 47 if f, found := configLoaderByExt[lext]; found { 48 return newError(ext, " already registered to ", f.Name) 49 } 50 configLoaderByExt[lext] = format 51 } 52 53 return nil 54 } 55 56 func GetMergedConfig(args cmdarg.Arg) (string, error) { 57 files := make([]string, 0) 58 formats := make([]string, 0) 59 supported := []string{"json", "yaml", "toml"} 60 for _, file := range args { 61 format := getFormat(file) 62 for _, s := range supported { 63 if s == format { 64 files = append(files, file) 65 formats = append(formats, format) 66 break 67 } 68 } 69 } 70 return ConfigMergedFormFiles(files, formats) 71 } 72 73 func GetFormatByExtension(ext string) string { 74 switch strings.ToLower(ext) { 75 case "pb", "protobuf": 76 return "protobuf" 77 case "yaml", "yml": 78 return "yaml" 79 case "toml": 80 return "toml" 81 case "json", "jsonc": 82 return "json" 83 default: 84 return "" 85 } 86 } 87 88 func getExtension(filename string) string { 89 idx := strings.LastIndexByte(filename, '.') 90 if idx == -1 { 91 return "" 92 } 93 return filename[idx+1:] 94 } 95 96 func getFormat(filename string) string { 97 return GetFormatByExtension(getExtension(filename)) 98 } 99 100 func LoadConfig(formatName string, input interface{}) (*Config, error) { 101 switch v := input.(type) { 102 case cmdarg.Arg: 103 formats := make([]string, len(v)) 104 hasProtobuf := false 105 for i, file := range v { 106 var f string 107 108 if formatName == "auto" { 109 if file != "stdin:" { 110 f = getFormat(file) 111 } else { 112 f = "json" 113 } 114 } else { 115 f = formatName 116 } 117 118 if f == "" { 119 return nil, newError("Failed to get format of ", file).AtWarning() 120 } 121 122 if f == "protobuf" { 123 hasProtobuf = true 124 } 125 formats[i] = f 126 } 127 128 // only one protobuf config file is allowed 129 if hasProtobuf { 130 if len(v) == 1 { 131 return configLoaderByName["protobuf"].Loader(v) 132 } else { 133 return nil, newError("Only one protobuf config file is allowed").AtWarning() 134 } 135 } 136 137 // to avoid import cycle 138 return ConfigBuilderForFiles(v, formats) 139 140 case io.Reader: 141 if f, found := configLoaderByName[formatName]; found { 142 return f.Loader(v) 143 } else { 144 return nil, newError("Unable to load config in", formatName).AtWarning() 145 } 146 } 147 148 return nil, newError("Unable to load config").AtWarning() 149 } 150 151 func loadProtobufConfig(data []byte) (*Config, error) { 152 config := new(Config) 153 if err := proto.Unmarshal(data, config); err != nil { 154 return nil, err 155 } 156 return config, nil 157 } 158 159 func init() { 160 common.Must(RegisterConfigLoader(&ConfigFormat{ 161 Name: "Protobuf", 162 Extension: []string{"pb"}, 163 Loader: func(input interface{}) (*Config, error) { 164 switch v := input.(type) { 165 case cmdarg.Arg: 166 r, err := confloader.LoadConfig(v[0]) 167 common.Must(err) 168 data, err := buf.ReadAllToBytes(r) 169 common.Must(err) 170 return loadProtobufConfig(data) 171 case io.Reader: 172 data, err := buf.ReadAllToBytes(v) 173 common.Must(err) 174 return loadProtobufConfig(data) 175 default: 176 return nil, newError("unknow type") 177 } 178 }, 179 })) 180 }