diff --git a/kyaml/yaml/fns.go b/kyaml/yaml/fns.go index 4568d9530..604f3cc96 100644 --- a/kyaml/yaml/fns.go +++ b/kyaml/yaml/fns.go @@ -482,7 +482,7 @@ func (s FieldSetter) Filter(rn *RNode) (*RNode, error) { } // Clear the field if it is empty, or explicitly null - if s.Value == nil || IsNull(s.Value) { + if s.Value == nil || s.Value.IsTaggedNull() { return rn.Pipe(Clear(s.Name)) } @@ -568,7 +568,7 @@ var nodeTypeIndex = map[yaml.Kind]string{ } func ErrorIfInvalid(rn *RNode, kind yaml.Kind) error { - if rn == nil || rn.YNode() == nil || IsNull(rn) { + if rn == nil || rn.YNode() == nil || rn.IsTaggedNull() { // node has no type, pass validation return nil } diff --git a/kyaml/yaml/merge2/merge2.go b/kyaml/yaml/merge2/merge2.go index 2d0dd32de..632798a2b 100644 --- a/kyaml/yaml/merge2/merge2.go +++ b/kyaml/yaml/merge2/merge2.go @@ -56,7 +56,7 @@ func (m Merger) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.R // Add return nodes.Origin(), nil } - if yaml.IsNull(nodes.Origin()) { + if nodes.Origin().IsTaggedNull() { // clear the value return walk.ClearNode, nil } @@ -112,7 +112,7 @@ func (m Merger) VisitList(nodes walk.Sources, s *openapi.ResourceSchema, kind wa return nodes.Origin(), nil } // Clear - if yaml.IsNull(nodes.Origin()) { + if nodes.Origin().IsTaggedNull() { return walk.ClearNode, nil } // Recursively Merge dest diff --git a/kyaml/yaml/merge3/visitor.go b/kyaml/yaml/merge3/visitor.go index 43d62e70e..0d68907b9 100644 --- a/kyaml/yaml/merge3/visitor.go +++ b/kyaml/yaml/merge3/visitor.go @@ -20,7 +20,7 @@ const ( type Visitor struct{} func (m Visitor) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) { - if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) { + if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() { // explicitly cleared from either dest or update return walk.ClearNode, nil } @@ -54,7 +54,7 @@ func (m Visitor) visitAList(nodes walk.Sources, _ *openapi.ResourceSchema) (*yam } func (m Visitor) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) { - if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) { + if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() { // explicitly cleared from either dest or update return nil, nil } @@ -91,7 +91,7 @@ func (m Visitor) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*ya } func (m Visitor) visitNAList(nodes walk.Sources) (*yaml.RNode, error) { - if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) { + if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() { // explicitly cleared from either dest or update return walk.ClearNode, nil } diff --git a/kyaml/yaml/types.go b/kyaml/yaml/types.go index f44aa65e2..cdaa6ecdb 100644 --- a/kyaml/yaml/types.go +++ b/kyaml/yaml/types.go @@ -36,7 +36,7 @@ func MakeNullNode() *RNode { // IsMissingOrNull is true if the RNode is nil or explicitly tagged null. // TODO: make this a method on RNode. func IsMissingOrNull(node *RNode) bool { - return IsNil(node) || node.YNode().Tag == NodeTagNull + return node.IsNil() || node.YNode().Tag == NodeTagNull } // Deprecated. Use IsMissingOrNull instead. @@ -49,15 +49,6 @@ func IsEmptyMap(node *RNode) bool { return IsMissingOrNull(node) || IsYNodeEmptyMap(node.YNode()) } -// IsNil return true if the node is nil, or its underlying YNode is nil. -func IsNil(node *RNode) bool { - return node == nil || node.YNode() == nil -} - -func IsNull(node *RNode) bool { - return !IsNil(node) && node.YNode().Tag == NodeTagNull -} - func IsFieldEmpty(node *MapNode) bool { if node == nil || node.Value == nil || node.Value.YNode() == nil || node.Value.YNode().Tag == NodeTagNull { @@ -68,11 +59,16 @@ func IsFieldEmpty(node *MapNode) bool { } func IsYNodeEmptyMap(n *yaml.Node) bool { - return n.Kind == yaml.MappingNode && len(n.Content) == 0 + return n != nil && n.Kind == yaml.MappingNode && len(n.Content) == 0 +} + +// IsYNodeTaggedNull returns true if the node is explicitly tagged Null. +func IsYNodeTaggedNull(n *yaml.Node) bool { + return n != nil && n.Tag == NodeTagNull } func IsYNodeEmptySeq(n *yaml.Node) bool { - return n.Kind == yaml.SequenceNode && len(n.Content) == 0 + return n != nil && n.Kind == yaml.SequenceNode && len(n.Content) == 0 } // IsYNodeEmptyDoc is true if the node is a Document with no content. @@ -377,6 +373,25 @@ const ( LabelsField = "labels" ) +// IsNil is true if the node is nil, or its underlying YNode is nil. +func (rn *RNode) IsNil() bool { + return rn == nil || rn.YNode() == nil +} + +// IsTaggedNull is true if a non-nil node is explicitly tagged Null. +func (rn *RNode) IsTaggedNull() bool { + return !rn.IsNil() && IsYNodeTaggedNull(rn.YNode()) +} + +// IsNilOrEmpty is true if the node is nil, +// has no YNode, or has YNode that appears empty. +func (rn *RNode) IsNilOrEmpty() bool { + return rn.IsNil() || + IsYNodeTaggedNull(rn.YNode()) || + IsYNodeEmptyMap(rn.YNode()) || + IsYNodeEmptySeq(rn.YNode()) +} + // GetMeta returns the ResourceMeta for an RNode func (rn *RNode) GetMeta() (ResourceMeta, error) { if IsMissingOrNull(rn) { diff --git a/kyaml/yaml/types_test.go b/kyaml/yaml/types_test.go index 53b687e9a..2b3da83a3 100644 --- a/kyaml/yaml/types_test.go +++ b/kyaml/yaml/types_test.go @@ -167,6 +167,61 @@ type: string assert.Equal(t, expected, actual) } +func TestIsYNodeTaggedNull(t *testing.T) { + if IsYNodeTaggedNull(nil) { + t.Fatalf("nil cannot be tagged null") + } + if IsYNodeTaggedNull(&Node{}) { + t.Fatalf("untagged node is not tagged") + } + if IsYNodeTaggedNull(&Node{Tag: NodeTagFloat}) { + t.Fatalf("float tagged node is not tagged") + } + if !IsYNodeTaggedNull(&Node{Tag: NodeTagNull}) { + t.Fatalf("tagged node is tagged") + } +} + +func TestIsYNodeEmptyMap(t *testing.T) { + if IsYNodeEmptyMap(nil) { + t.Fatalf("nil cannot be a map") + } + if IsYNodeEmptyMap(&Node{}) { + t.Fatalf("raw node is not a map") + } + if IsYNodeEmptyMap(&Node{Kind: SequenceNode}) { + t.Fatalf("seq node is not a map") + } + n := &Node{Kind: MappingNode} + if !IsYNodeEmptyMap(n) { + t.Fatalf("empty mapping node is an empty mapping node") + } + n.Content = append(n.Content, &Node{Kind: SequenceNode}) + if IsYNodeEmptyMap(n) { + t.Fatalf("a node with content isn't empty") + } +} + +func TestIsYNodeEmptySeq(t *testing.T) { + if IsYNodeEmptySeq(nil) { + t.Fatalf("nil cannot be a map") + } + if IsYNodeEmptySeq(&Node{}) { + t.Fatalf("raw node is not a map") + } + if IsYNodeEmptySeq(&Node{Kind: MappingNode}) { + t.Fatalf("map node is not a sequence") + } + n := &Node{Kind: SequenceNode} + if !IsYNodeEmptySeq(n) { + t.Fatalf("empty sequence node is an empty sequence node") + } + n.Content = append(n.Content, &Node{Kind: MappingNode}) + if IsYNodeEmptySeq(n) { + t.Fatalf("a node with content isn't empty") + } +} + func TestIsMissingOrNull(t *testing.T) { if !IsMissingOrNull(nil) { t.Fatalf("input: nil") @@ -220,3 +275,83 @@ func TestIsEmptyMap(t *testing.T) { t.Fatalf("input: empty map") } } + +func TestIsNil(t *testing.T) { + var rn *RNode + + if !rn.IsNil() { + t.Fatalf("uninitialized RNode should be nil") + } + + if !NewRNode(nil).IsNil() { + t.Fatalf("missing value YNode should be nil") + } + + if MakeNullNode().IsNil() { + t.Fatalf("value tagged null is not nil") + } + + if NewMapRNode(nil).IsNil() { + t.Fatalf("empty map not nil") + } + + if NewListRNode().IsNil() { + t.Fatalf("empty list not nil") + } +} + +func TestIsTaggedNull(t *testing.T) { + var rn *RNode + + if rn.IsTaggedNull() { + t.Fatalf("nil RNode cannot be tagged") + } + + if NewRNode(nil).IsTaggedNull() { + t.Fatalf("bare RNode should not be tagged") + } + + if !MakeNullNode().IsTaggedNull() { + t.Fatalf("a null node is tagged null by definition") + } + + if NewMapRNode(nil).IsTaggedNull() { + t.Fatalf("empty map should not be tagged null") + } + + if NewListRNode().IsTaggedNull() { + t.Fatalf("empty list should not be tagged null") + } +} + +func TestRNodeIsNilOrEmpty(t *testing.T) { + var rn *RNode + + if !rn.IsNilOrEmpty() { + t.Fatalf("uninitialized RNode should be empty") + } + + if !NewRNode(nil).IsNilOrEmpty() { + t.Fatalf("missing value YNode should be empty") + } + + if !MakeNullNode().IsNilOrEmpty() { + t.Fatalf("value tagged null should be empty") + } + + if !NewMapRNode(nil).IsNilOrEmpty() { + t.Fatalf("empty map should be empty") + } + + if NewMapRNode(&map[string]string{"foo": "bar"}).IsNilOrEmpty() { + t.Fatalf("non-empty map should not be empty") + } + + if !NewListRNode().IsNilOrEmpty() { + t.Fatalf("empty list should be empty") + } + + if NewListRNode("foo").IsNilOrEmpty() { + t.Fatalf("non-empty list should not be empty") + } +}