github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/cli/linter/linter.go (about) 1 package linter 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net" 7 "os" 8 9 schema "github.com/xeipuuv/gojsonschema" 10 11 "github.com/TykTechnologies/tyk/config" 12 ) 13 14 // Run will lint the configuration file. It will return the path to the 15 // config file that was checked, a list of warnings and an error, if any 16 // happened. 17 func Run(schm string, paths []string) (string, []string, error) { 18 addFormats(&schema.FormatCheckers) 19 var conf config.Config 20 if err := config.Load(paths, &conf); err != nil { 21 return "", nil, err 22 } 23 schemaLoader := schema.NewBytesLoader([]byte(schm)) 24 25 var orig map[string]interface{} 26 f, err := os.Open(conf.OriginalPath) 27 if err != nil { 28 return "", nil, err 29 } 30 defer f.Close() 31 if err := json.NewDecoder(f).Decode(&orig); err != nil { 32 return "", nil, err 33 } 34 if v, ok := orig["Monitor"]; ok { 35 // As the old confs wrongly capitalized this key. Would 36 // be fixed by WriteConf below, but we want the JSON 37 // schema to not flag this error. 38 orig["monitor"] = v 39 delete(orig, "Monitor") 40 } 41 42 fileLoader := schema.NewGoLoader(orig) 43 result, err := schema.Validate(schemaLoader, fileLoader) 44 if err != nil { 45 return "", nil, err 46 } 47 48 // ensure it's well formatted and the keys are all lowercase 49 if err := config.WriteConf(conf.OriginalPath, &conf); err != nil { 50 return "", nil, err 51 } 52 53 return conf.OriginalPath, resultWarns(result), nil 54 } 55 56 type stringFormat func(string) bool 57 58 func (f stringFormat) IsFormat(v interface{}) bool { 59 s := v.(string) 60 if s == "" { 61 return true // empty string is ok 62 } 63 return f(s) 64 } 65 66 func addFormats(chain *schema.FormatCheckerChain) { 67 chain.Add("path", stringFormat(func(path string) bool { 68 _, err := os.Stat(path) 69 return err == nil // must be accessible 70 })) 71 chain.Add("host-no-port", stringFormat(func(host string) bool { 72 _, port, err := net.SplitHostPort(host) 73 if a, ok := err.(*net.AddrError); ok && a.Err == "missing port in address" { 74 // port being missing is ok 75 err = nil 76 } 77 return err == nil && port == "" // valid host with no port 78 })) 79 } 80 81 func resultWarns(result *schema.Result) []string { 82 warns := result.Errors() 83 strs := make([]string, len(warns)) 84 for i, warn := range warns { 85 ferr, ok := warn.(*schema.DoesNotMatchFormatError) 86 if !ok { 87 strs[i] = warn.String() 88 continue 89 } 90 // We need this since formats can only return bools, not 91 // custom errors/messages. 92 var desc string 93 switch format := ferr.Details()["format"].(string); format { 94 case "path": 95 desc = "Path does not exist or is not accessible" 96 case "host-no-port": 97 desc = "Address should be a host without port" 98 default: 99 panic(fmt.Sprintf("unexpected format type: %q", format)) 100 } 101 ferr.SetDescription(desc) 102 strs[i] = ferr.String() 103 } 104 return strs 105 }