github.com/yoogoc/kratos-scaffold@v0.0.0-20240402032722-a538b3c18955/internal/merge/merge.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package merge
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"io"
    27  
    28  	"gopkg.in/yaml.v2"
    29  )
    30  
    31  type (
    32  	// YAML has three fundamental types. When unmarshaled into interface{},
    33  	// they're represented like this.
    34  	mapping  = map[interface{}]interface{}
    35  	sequence = []interface{}
    36  	scalar   = interface{}
    37  )
    38  
    39  // YAML deep-merges any number of YAML sources, with later sources taking
    40  // priority over earlier ones.
    41  //
    42  // Maps are deep-merged. For example,
    43  //   {"one": 1, "two": 2} + {"one": 42, "three": 3}
    44  //   == {"one": 42, "two": 2, "three": 3}
    45  // Sequences are replaced. For example,
    46  //   {"foo": [1, 2, 3]} + {"foo": [4, 5, 6]}
    47  //   == {"foo": [4, 5, 6]}
    48  //
    49  // In non-strict mode, duplicate map keys are allowed within a single source,
    50  // with later values overwriting previous ones. Attempting to merge
    51  // mismatched types (e.g., merging a sequence into a map) replaces the old
    52  // value with the new.
    53  //
    54  // Enabling strict mode returns errors in both of the above cases.
    55  func YAML(sources [][]byte, strict bool) (*bytes.Buffer, error) {
    56  	var merged interface{}
    57  	var hasContent bool
    58  	for _, r := range sources {
    59  		d := yaml.NewDecoder(bytes.NewReader(r))
    60  		d.SetStrict(strict)
    61  
    62  		var contents interface{}
    63  		if err := d.Decode(&contents); err == io.EOF {
    64  			// Skip empty and comment-only sources, which we should handle
    65  			// differently from explicit nils.
    66  			continue
    67  		} else if err != nil {
    68  			return nil, fmt.Errorf("couldn't decode source: %v", err)
    69  		}
    70  
    71  		hasContent = true
    72  		pair, err := merge(merged, contents, strict)
    73  		if err != nil {
    74  			return nil, err // error is already descriptive enough
    75  		}
    76  		merged = pair
    77  	}
    78  
    79  	buf := &bytes.Buffer{}
    80  	if !hasContent {
    81  		// No sources had any content. To distinguish this from a source with just
    82  		// an explicit top-level null, return an empty buffer.
    83  		return buf, nil
    84  	}
    85  	enc := yaml.NewEncoder(buf)
    86  	if err := enc.Encode(merged); err != nil {
    87  		return nil, fmt.Errorf("couldn't re-serialize merged YAML: %v", err)
    88  	}
    89  	return buf, nil
    90  }
    91  
    92  func merge(into, from interface{}, strict bool) (interface{}, error) {
    93  	// It's possible to handle this with a mass of reflection, but we only need
    94  	// to merge whole YAML files. Since we're always unmarshaling into
    95  	// interface{}, we only need to handle a few types. This ends up being
    96  	// cleaner if we just handle each case explicitly.
    97  	if into == nil {
    98  		return from, nil
    99  	}
   100  	if from == nil {
   101  		// Allow higher-priority YAML to explicitly nil out lower-priority entries.
   102  		return nil, nil
   103  	}
   104  	if IsScalar(into) && IsScalar(from) {
   105  		return from, nil
   106  	}
   107  	if IsSequence(into) && IsSequence(from) {
   108  		return from, nil
   109  	}
   110  	if IsMapping(into) && IsMapping(from) {
   111  		return mergeMapping(into.(mapping), from.(mapping), strict)
   112  	}
   113  	// YAML types don't match, so no merge is possible. For backward
   114  	// compatibility, ignore mismatches unless we're in strict mode and return
   115  	// the higher-priority value.
   116  	if !strict {
   117  		return from, nil
   118  	}
   119  	return nil, fmt.Errorf("can't merge a %s into a %s", describe(from), describe(into))
   120  }
   121  
   122  func mergeMapping(into, from mapping, strict bool) (mapping, error) {
   123  	merged := make(mapping, len(into))
   124  	for k, v := range into {
   125  		merged[k] = v
   126  	}
   127  	for k := range from {
   128  		m, err := merge(merged[k], from[k], strict)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		merged[k] = m
   133  	}
   134  	return merged, nil
   135  }
   136  
   137  // IsMapping reports whether a type is a mapping in YAML, represented as a
   138  // map[interface{}]interface{}.
   139  func IsMapping(i interface{}) bool {
   140  	_, is := i.(mapping)
   141  	return is
   142  }
   143  
   144  // IsSequence reports whether a type is a sequence in YAML, represented as an
   145  // []interface{}.
   146  func IsSequence(i interface{}) bool {
   147  	_, is := i.(sequence)
   148  	return is
   149  }
   150  
   151  // IsScalar reports whether a type is a scalar value in YAML.
   152  func IsScalar(i interface{}) bool {
   153  	return !IsMapping(i) && !IsSequence(i)
   154  }
   155  
   156  func describe(i interface{}) string {
   157  	if IsMapping(i) {
   158  		return "mapping"
   159  	}
   160  	if IsSequence(i) {
   161  		return "sequence"
   162  	}
   163  	return "scalar"
   164  }