diff --git a/pkg/machinery/config/decoder/decoder.go b/pkg/machinery/config/decoder/decoder.go index 0726426127..1fdca6b07a 100644 --- a/pkg/machinery/config/decoder/decoder.go +++ b/pkg/machinery/config/decoder/decoder.go @@ -178,7 +178,7 @@ func decode(manifest *yaml.Node) (target interface{}, err error) { //nolint:gocyclo func validate(target interface{}, spec *yaml.Node) error { - node, err := encoder.NewEncoder(target).Marshal() + node, err := encoder.NewEncoder(target, encoder.WithOmitEmpty(false)).Marshal() if err != nil { return err } diff --git a/pkg/machinery/config/decoder/decoder_test.go b/pkg/machinery/config/decoder/decoder_test.go index 44a9919f23..6a71ceb0f0 100644 --- a/pkg/machinery/config/decoder/decoder_test.go +++ b/pkg/machinery/config/decoder/decoder_test.go @@ -23,10 +23,17 @@ type MockV2 struct { Map map[string]Mock `yaml:"map"` } +type MockV3 struct { + Omit bool `yaml:"omit,omitempty"` +} + func init() { config.Register("mock", func(version string) interface{} { - if version == "v1alpha2" { + switch version { + case "v1alpha2": return &MockV2{} + case "v1alpha3": + return &MockV3{} } return &Mock{} @@ -193,6 +200,22 @@ spec: not: working more: extra fields: here +`), + }, + want: nil, + wantErr: true, + }, + { + name: "extra zero fields in map", + fields: fields{ + source: []byte(`--- +kind: mock +version: v1alpha2 +spec: + map: + second: + a: + b: {} `), }, want: nil, @@ -244,6 +267,17 @@ spec: - rshared - rw source: /var/local +`), + }, + }, + { + name: "omit empty test", + fields: fields{ + source: []byte(`--- +kind: mock +version: v1alpha3 +spec: + omit: false `), }, }, diff --git a/pkg/machinery/config/encoder/documentation.go b/pkg/machinery/config/encoder/documentation.go index a3343ada2e..7525d1dcc9 100644 --- a/pkg/machinery/config/encoder/documentation.go +++ b/pkg/machinery/config/encoder/documentation.go @@ -194,7 +194,7 @@ func addComments(node *yaml.Node, doc *Doc, comments ...int) { } //nolint:gocyclo -func renderExample(key string, doc *Doc, flags CommentsFlags) string { +func renderExample(key string, doc *Doc, options *Options) string { if doc == nil { return "" } @@ -216,7 +216,7 @@ func renderExample(key string, doc *Doc, flags CommentsFlags) string { e.Populate(i) - node, err := toYamlNode(defaultValue, flags) + node, err := toYamlNode(defaultValue, options) if err != nil { continue } @@ -224,13 +224,13 @@ func renderExample(key string, doc *Doc, flags CommentsFlags) string { if key != "" { node, err = toYamlNode(map[string]*yaml.Node{ key: node, - }, flags) + }, options) if err != nil { continue } } - if i == 0 && flags.enabled(CommentsDocs) { + if i == 0 && options.Comments.enabled(CommentsDocs) { addComments(node, doc, HeadComment, LineComment) } diff --git a/pkg/machinery/config/encoder/encoder.go b/pkg/machinery/config/encoder/encoder.go index 76869f51a7..4aaadc1c32 100644 --- a/pkg/machinery/config/encoder/encoder.go +++ b/pkg/machinery/config/encoder/encoder.go @@ -28,7 +28,7 @@ func NewEncoder(value interface{}, opts ...Option) *Encoder { // Marshal converts value to YAML-serializable value (suitable for MarshalYAML). func (e *Encoder) Marshal() (*yaml.Node, error) { - node, err := toYamlNode(e.value, e.options.Comments) + node, err := toYamlNode(e.value, e.options) if err != nil { return nil, err } @@ -108,9 +108,11 @@ func isNil(value reflect.Value) bool { } //nolint:gocyclo,cyclop -func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { +func toYamlNode(in interface{}, options *Options) (*yaml.Node, error) { node := &yaml.Node{} + flags := options.Comments + // do not wrap yaml.Node into yaml.Node if n, ok := in.(*yaml.Node); ok { return n, nil @@ -118,7 +120,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { // if input implements yaml.Marshaler we should use that marshaller instead // same way as regular yaml marshal does - if m, ok := in.(yaml.Marshaler); ok { + if m, ok := in.(yaml.Marshaler); ok && !isNil(reflect.ValueOf(in)) { res, err := m.MarshalYAML() if err != nil { return nil, err @@ -181,7 +183,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { ) for _, part := range parts { - if part == "omitempty" && empty { + if part == "omitempty" && empty && options.OmitEmpty { skip = true } @@ -218,7 +220,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { if empty && flags.enabled(CommentsExamples) && fieldDoc != nil { if skip { // render example to be appended to the end of the rendered struct - example := renderExample(fieldName, fieldDoc, flags) + example := renderExample(fieldName, fieldDoc, options) if example != "" { examples = append(examples, example) @@ -228,7 +230,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { fieldDocCopy := *fieldDoc fieldDocCopy.Comments = [3]string{} - inlineExample = renderExample("", &fieldDocCopy, flags) + inlineExample = renderExample("", &fieldDocCopy, options) } } @@ -242,7 +244,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { } if inline { - child, err := toYamlNode(value, flags) + child, err := toYamlNode(value, options) if err != nil { return nil, err } @@ -250,7 +252,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { if child.Kind == yaml.MappingNode || child.Kind == yaml.SequenceNode { appendNodes(node, child.Content...) } - } else if err := addToMap(node, fieldDoc, fieldName, value, style, flags); err != nil { + } else if err := addToMap(node, fieldDoc, fieldName, value, style, options); err != nil { return nil, err } @@ -287,7 +289,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { element := v.MapIndex(k) value := element.Interface() - if err := addToMap(node, nil, k.Interface(), value, 0, flags); err != nil { + if err := addToMap(node, nil, k.Interface(), value, 0, options); err != nil { return nil, err } } @@ -300,7 +302,7 @@ func toYamlNode(in interface{}, flags CommentsFlags) (*yaml.Node, error) { var err error - nodes[i], err = toYamlNode(element.Interface(), flags) + nodes[i], err = toYamlNode(element.Interface(), options) if err != nil { return nil, err } @@ -323,20 +325,20 @@ func appendNodes(dest *yaml.Node, nodes ...*yaml.Node) { dest.Content = append(dest.Content, nodes...) } -func addToMap(dest *yaml.Node, doc *Doc, fieldName, in interface{}, style yaml.Style, flags CommentsFlags) error { - key, err := toYamlNode(fieldName, flags) +func addToMap(dest *yaml.Node, doc *Doc, fieldName, in interface{}, style yaml.Style, options *Options) error { + key, err := toYamlNode(fieldName, options) if err != nil { return err } - value, err := toYamlNode(in, flags) + value, err := toYamlNode(in, options) if err != nil { return err } value.Style = style - if flags.enabled(CommentsDocs) { + if options.Comments.enabled(CommentsDocs) { addComments(key, doc, HeadComment, FootComment) addComments(value, doc, LineComment) } diff --git a/pkg/machinery/config/encoder/markdown.go b/pkg/machinery/config/encoder/markdown.go index cd1252a68c..4b62c549d6 100644 --- a/pkg/machinery/config/encoder/markdown.go +++ b/pkg/machinery/config/encoder/markdown.go @@ -180,7 +180,7 @@ func encodeYaml(in interface{}, name string) string { } } - node, err := toYamlNode(in, CommentsAll) + node, err := toYamlNode(in, newOptions(WithComments(CommentsAll))) if err != nil { return fmt.Sprintf("yaml encoding failed %s", err) } diff --git a/pkg/machinery/config/encoder/options.go b/pkg/machinery/config/encoder/options.go index e73b2317f1..456d84f1ae 100644 --- a/pkg/machinery/config/encoder/options.go +++ b/pkg/machinery/config/encoder/options.go @@ -24,12 +24,14 @@ const ( // Options defines encoder config. type Options struct { - Comments CommentsFlags + Comments CommentsFlags + OmitEmpty bool } func newOptions(opts ...Option) *Options { res := &Options{ - Comments: CommentsAll, + Comments: CommentsAll, + OmitEmpty: true, } for _, o := range opts { @@ -48,3 +50,10 @@ func WithComments(flags CommentsFlags) Option { o.Comments = flags } } + +// WithOmitEmpty toggles omitempty handling. +func WithOmitEmpty(value bool) Option { + return func(o *Options) { + o.OmitEmpty = value + } +}