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 }