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  }