code.gitea.io/gitea@v1.19.3/modules/migration/file_format.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package migration 5 6 import ( 7 "fmt" 8 "os" 9 "strings" 10 "time" 11 12 "code.gitea.io/gitea/modules/json" 13 "code.gitea.io/gitea/modules/log" 14 15 "github.com/santhosh-tekuri/jsonschema/v5" 16 "gopkg.in/yaml.v3" 17 ) 18 19 // Load project data from file, with optional validation 20 func Load(filename string, data interface{}, validation bool) error { 21 isJSON := strings.HasSuffix(filename, ".json") 22 23 bs, err := os.ReadFile(filename) 24 if err != nil { 25 return err 26 } 27 28 if validation { 29 err := validate(bs, data, isJSON) 30 if err != nil { 31 return err 32 } 33 } 34 return unmarshal(bs, data, isJSON) 35 } 36 37 func unmarshal(bs []byte, data interface{}, isJSON bool) error { 38 if isJSON { 39 return json.Unmarshal(bs, data) 40 } 41 return yaml.Unmarshal(bs, data) 42 } 43 44 func getSchema(filename string) (*jsonschema.Schema, error) { 45 c := jsonschema.NewCompiler() 46 c.LoadURL = openSchema 47 return c.Compile(filename) 48 } 49 50 func validate(bs []byte, datatype interface{}, isJSON bool) error { 51 var v interface{} 52 err := unmarshal(bs, &v, isJSON) 53 if err != nil { 54 return err 55 } 56 if !isJSON { 57 v, err = toStringKeys(v) 58 if err != nil { 59 return err 60 } 61 } 62 63 var schemaFilename string 64 switch datatype := datatype.(type) { 65 case *[]*Issue: 66 schemaFilename = "issue.json" 67 case *[]*Milestone: 68 schemaFilename = "milestone.json" 69 default: 70 return fmt.Errorf("file_format:validate: %T has not a validation implemented", datatype) 71 } 72 73 sch, err := getSchema(schemaFilename) 74 if err != nil { 75 return err 76 } 77 err = sch.Validate(v) 78 if err != nil { 79 log.Error("migration validation with %s failed:\n%#v", schemaFilename, err) 80 } 81 return err 82 } 83 84 func toStringKeys(val interface{}) (interface{}, error) { 85 var err error 86 switch val := val.(type) { 87 case map[string]interface{}: 88 m := make(map[string]interface{}) 89 for k, v := range val { 90 m[k], err = toStringKeys(v) 91 if err != nil { 92 return nil, err 93 } 94 } 95 return m, nil 96 case []interface{}: 97 l := make([]interface{}, len(val)) 98 for i, v := range val { 99 l[i], err = toStringKeys(v) 100 if err != nil { 101 return nil, err 102 } 103 } 104 return l, nil 105 case time.Time: 106 return val.Format(time.RFC3339), nil 107 default: 108 return val, nil 109 } 110 }