You've already forked goreleaser
							
							
				mirror of
				https://github.com/goreleaser/goreleaser.git
				synced 2025-10-30 23:58:09 +02:00 
			
		
		
		
	feat(yaml): upgraded from yaml.v2 to yaml.v3 (#3004)
* chore(yaml): upgraded from yaml.v2 to yaml.v3
* provided internal package to take care of backward
  compatible settings:
    * UnmarshalStrict method
    * mute io.EOF unmarshaling errors
    * marshal indenting with 2 chars
* adapted unit tests to new yaml v3
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
* fixed failing tests
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
			
			
This commit is contained in:
		
							
								
								
									
										52
									
								
								internal/yaml/yaml.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								internal/yaml/yaml.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // Package yaml wraps gopkg.in/yaml.v3 and helps transition from v2. | ||||
| package yaml | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"io" | ||||
|  | ||||
| 	yaml "gopkg.in/yaml.v3" | ||||
| ) | ||||
|  | ||||
| // UnmarshalStrict unmarshals a YAML document with strict behavior (only declared fields are tolerated). | ||||
| func UnmarshalStrict(in []byte, out interface{}) error { | ||||
| 	decoder := yaml.NewDecoder(bytes.NewReader(in)) | ||||
| 	decoder.KnownFields(true) | ||||
|  | ||||
| 	return handleErr(decoder.Decode(out)) | ||||
| } | ||||
|  | ||||
| // Unmarshal some struct as a YAML document, without strict behavior. | ||||
| func Unmarshal(in []byte, out interface{}) error { | ||||
| 	decoder := yaml.NewDecoder(bytes.NewReader(in)) | ||||
| 	decoder.KnownFields(false) | ||||
|  | ||||
| 	return handleErr(decoder.Decode(out)) | ||||
| } | ||||
|  | ||||
| // Marshal some struct as a YAML document. | ||||
| func Marshal(in interface{}) ([]byte, error) { | ||||
| 	b := new(bytes.Buffer) | ||||
| 	encoder := yaml.NewEncoder(b) | ||||
| 	encoder.SetIndent(2) // default is 4 | ||||
|  | ||||
| 	if err := encoder.Encode(in); err != nil { | ||||
| 		encoder.Close() | ||||
|  | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	encoder.Close() | ||||
|  | ||||
| 	return b.Bytes(), nil | ||||
| } | ||||
|  | ||||
| // handleErr mutes io.EOF errors for backward-compatibility. | ||||
| func handleErr(err error) error { | ||||
| 	if err != nil && !errors.Is(err, io.EOF) { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										264
									
								
								internal/yaml/yaml_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								internal/yaml/yaml_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | ||||
| package yaml | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func TestYAMLMarshalError(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	type yamlMarshalError struct { | ||||
| 		BoolValue bool   `yaml:"bool_value"` | ||||
| 		FuncValue func() `yaml:"func_value"` | ||||
| 	} | ||||
|  | ||||
| 	v := yamlMarshalError{ | ||||
| 		BoolValue: true, | ||||
| 		FuncValue: func() {}, | ||||
| 	} | ||||
|  | ||||
| 	require.Panics(t, func() { | ||||
| 		_, _ = Marshal(v) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestYAML(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	type ( | ||||
| 		yamlKey struct { | ||||
| 			BoolValue   bool    `yaml:"bool_value"` | ||||
| 			NumberValue float64 `yaml:"number_value"` | ||||
| 			StringValue string  `yaml:"string_value"` | ||||
| 		} | ||||
| 		yamlObject struct { | ||||
| 			Array []string `yaml:"array"` | ||||
| 			Key   yamlKey  `yaml:"key"` | ||||
| 		} | ||||
| 		yamlReceiver struct { | ||||
| 			Object yamlObject `yaml:"object"` | ||||
| 		} | ||||
|  | ||||
| 		interfaceOrObject struct { | ||||
| 			obj   yamlReceiver | ||||
| 			iface interface{} | ||||
| 		} | ||||
| 	) | ||||
|  | ||||
| 	for _, toPin := range []struct { | ||||
| 		Title        string | ||||
| 		InputYAML    []byte | ||||
| 		ExpectedYAML []byte            // optional: when marshaled YAML is expected to differ from raw input (e.g. on bool flags) | ||||
| 		Expected     interfaceOrObject // maybe either untyped interface{} or yamlObject struct with struct tags | ||||
| 		ExpectError  bool | ||||
| 		WantsStrict  bool // apply Strict mode | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Title:     "happy path, untyped", | ||||
| 			InputYAML: testYAMLObject(), | ||||
| 			Expected: interfaceOrObject{ | ||||
| 				iface: map[string]interface{}{ | ||||
| 					"object": map[string]interface{}{ | ||||
| 						"key": map[string]interface{}{ | ||||
| 							"string_value": "This is a doc.\nOn multiple lines.\n", | ||||
| 							"bool_value":   "y", | ||||
| 							"number_value": 10.23, | ||||
| 						}, | ||||
| 						"array": []interface{}{"x", "y"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Title:       "happy path strict, untyped", | ||||
| 			InputYAML:   testYAMLObject(), | ||||
| 			WantsStrict: true, | ||||
| 			Expected: interfaceOrObject{ | ||||
| 				iface: map[string]interface{}{ | ||||
| 					"object": map[string]interface{}{ | ||||
| 						"key": map[string]interface{}{ | ||||
| 							"string_value": "This is a doc.\nOn multiple lines.\n", | ||||
| 							"bool_value":   "y", | ||||
| 							"number_value": 10.23, | ||||
| 						}, | ||||
| 						"array": []interface{}{"x", "y"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Title:        "happy path strict, explicit target", | ||||
| 			InputYAML:    testYAMLObject(), | ||||
| 			ExpectedYAML: testYAMLObjectBool(), | ||||
| 			WantsStrict:  true, | ||||
| 			Expected: interfaceOrObject{ | ||||
| 				iface: nil, | ||||
| 				obj: yamlReceiver{ | ||||
| 					Object: yamlObject{ | ||||
| 						Key: yamlKey{ | ||||
| 							StringValue: "This is a doc.\nOn multiple lines.\n", | ||||
| 							BoolValue:   true, | ||||
| 							NumberValue: 10.23, | ||||
| 						}, | ||||
| 						Array: []string{"x", "y"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Title:        "happy path non-strict, explicit target", | ||||
| 			InputYAML:    testYAMLObjectNonStrict(), | ||||
| 			ExpectedYAML: testYAMLObjectBool(), | ||||
| 			WantsStrict:  false, | ||||
| 			Expected: interfaceOrObject{ | ||||
| 				iface: nil, | ||||
| 				obj: yamlReceiver{ | ||||
| 					Object: yamlObject{ | ||||
| 						Key: yamlKey{ | ||||
| 							StringValue: "This is a doc.\nOn multiple lines.\n", | ||||
| 							BoolValue:   true, | ||||
| 							NumberValue: 10.23, | ||||
| 						}, | ||||
| 						Array: []string{"x", "y"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Title:        "happy path strict, explicit target: unknown field failure", | ||||
| 			InputYAML:    testYAMLObjectNonStrict(), | ||||
| 			ExpectedYAML: testYAMLObjectBool(), | ||||
| 			WantsStrict:  true, | ||||
| 			ExpectError:  true, | ||||
| 			Expected: interfaceOrObject{ | ||||
| 				iface: nil, | ||||
| 				obj: yamlReceiver{ | ||||
| 					Object: yamlObject{ | ||||
| 						Key: yamlKey{ | ||||
| 							StringValue: "This is a doc.\nOn multiple lines.\n", | ||||
| 							BoolValue:   true, | ||||
| 							NumberValue: 10.23, | ||||
| 						}, | ||||
| 						Array: []string{"x", "y"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		testCase := toPin | ||||
|  | ||||
| 		t.Run(testCase.Title, func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
|  | ||||
| 			var ( | ||||
| 				err               error | ||||
| 				b, expectedOutput []byte | ||||
| 			) | ||||
| 			iface := testCase.Expected.iface | ||||
| 			obj := testCase.Expected.obj | ||||
|  | ||||
| 			expectedInput := toPlainYaml(testCase.InputYAML) | ||||
|  | ||||
| 			if testCase.WantsStrict { | ||||
| 				if iface != nil { | ||||
| 					err = UnmarshalStrict(expectedInput, &iface) | ||||
| 				} else { | ||||
| 					err = UnmarshalStrict(expectedInput, &obj) | ||||
| 				} | ||||
| 			} else { | ||||
| 				if iface != nil { | ||||
| 					err = Unmarshal(expectedInput, &iface) | ||||
| 				} else { | ||||
| 					err = Unmarshal(expectedInput, &obj) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if testCase.ExpectError { | ||||
| 				require.Error(t, err) | ||||
|  | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			require.NoError(t, err) | ||||
|  | ||||
| 			if iface != nil { | ||||
| 				require.EqualValues(t, testCase.Expected.iface, iface) | ||||
|  | ||||
| 				b, err = Marshal(iface) | ||||
| 				require.NoError(t, err) | ||||
| 			} else { | ||||
| 				require.EqualValues(t, testCase.Expected.obj, obj) | ||||
|  | ||||
| 				b, err = Marshal(obj) | ||||
| 				require.NoError(t, err) | ||||
| 			} | ||||
|  | ||||
| 			if testCase.ExpectedYAML == nil { | ||||
| 				expectedOutput = expectedInput | ||||
| 			} else { | ||||
| 				expectedOutput = toPlainYaml(testCase.ExpectedYAML) | ||||
| 			} | ||||
|  | ||||
| 			require.EqualValues(t, expectedOutput, b) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func toPlainYaml(in []byte) []byte { | ||||
| 	// ensure we got legit yaml for go strings that may have been reindented using tabs, or leading new CR in source. | ||||
| 	return bytes.ReplaceAll( | ||||
| 		bytes.TrimLeft(in, "\n\r"), | ||||
| 		[]byte("\t"), bytes.Repeat([]byte(" "), 4), | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func testYAMLObject() []byte { | ||||
| 	return []byte(` | ||||
| object: | ||||
|   array: | ||||
|     - x | ||||
|     - "y" | ||||
|   key: | ||||
|     bool_value: "y" | ||||
|     number_value: 10.23 | ||||
|     string_value: | | ||||
|       This is a doc. | ||||
|       On multiple lines. | ||||
| `) | ||||
| } | ||||
|  | ||||
| func testYAMLObjectBool() []byte { | ||||
| 	// same object, but the "y" YAML for bool has been marshaled as "true" | ||||
| 	return []byte(` | ||||
| object: | ||||
|   array: | ||||
|     - x | ||||
|     - "y" | ||||
|   key: | ||||
|     bool_value: true | ||||
|     number_value: 10.23 | ||||
|     string_value: | | ||||
|       This is a doc. | ||||
|       On multiple lines. | ||||
| `) | ||||
| } | ||||
|  | ||||
| func testYAMLObjectNonStrict() []byte { | ||||
| 	// same object, but with an extra unknown value | ||||
| 	return []byte(` | ||||
| object: | ||||
|   array: | ||||
|     - x | ||||
|     - "y" | ||||
|   key: | ||||
|     bool_value: true | ||||
|     number_value: 10.23 | ||||
|     string_value: | | ||||
|       This is a doc. | ||||
|       On multiple lines. | ||||
|   unknown: 'wrong' | ||||
| `) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user