Skip to content

Commit

Permalink
fix: properly handle omitempty fields in the validator
Browse files Browse the repository at this point in the history
Add a new option to our custom YAML encoder to disable `omitempty` tags
completely.

Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
(cherry picked from commit 6312f47)
  • Loading branch information
Unix4ever authored and smira committed Aug 27, 2021
1 parent 2e220cb commit 3a38f0d
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 23 deletions.
2 changes: 1 addition & 1 deletion pkg/machinery/config/decoder/decoder.go
Expand Up @@ -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
}
Expand Down
36 changes: 35 additions & 1 deletion pkg/machinery/config/decoder/decoder_test.go
Expand Up @@ -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{}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
`),
},
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/machinery/config/encoder/documentation.go
Expand Up @@ -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 ""
}
Expand All @@ -216,21 +216,21 @@ 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
}

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)
}

Expand Down
30 changes: 16 additions & 14 deletions pkg/machinery/config/encoder/encoder.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -108,17 +108,19 @@ 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
}

// 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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}

Expand All @@ -242,15 +244,15 @@ 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
}

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
}

Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
}
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/machinery/config/encoder/markdown.go
Expand Up @@ -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)
}
Expand Down
13 changes: 11 additions & 2 deletions pkg/machinery/config/encoder/options.go
Expand Up @@ -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 {
Expand All @@ -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
}
}

0 comments on commit 3a38f0d

Please sign in to comment.