@@ -29,6 +29,8 @@ func (t Truthy) GetCategory() string {
2929}
3030
3131// RunRule will execute the Truthy rule, based on supplied context and a supplied []*yaml.Node slice.
32+ // If no field is specified, the function checks if the matched node itself is falsy (and reports if so).
33+ // If a field is specified, the function checks if that field within the matched node is falsy.
3234func (t * Truthy ) RunRule (nodes []* yaml.Node , context model.RuleFunctionContext ) []model.RuleFunctionResult {
3335
3436 if len (nodes ) <= 0 {
@@ -56,75 +58,108 @@ func (t *Truthy) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext)
5658 node = node .Content [0 ]
5759 }
5860
59- fieldNode , fieldNodeValue := utils .FindKeyNodeTop (context .RuleAction .Field , node .Content )
60- if fieldNode == nil && fieldNodeValue == nil || fieldNodeValue .Value == "false" ||
61- fieldNodeValue .Value == "0" || fieldNodeValue .Value == "" {
61+ var targetNode * yaml.Node
62+ var fieldNode * yaml.Node
63+ var fieldName string
64+
65+ if context .RuleAction .Field == "" {
66+ // no field specified - check the matched node itself
67+ targetNode = node
68+ fieldName = "value"
69+ } else {
70+ // field specified - find it within the node
71+ fieldNode , targetNode = utils .FindKeyNodeTop (context .RuleAction .Field , node .Content )
72+ fieldName = context .RuleAction .Field
73+ }
6274
75+ // check if the target is falsy (which means truthy check fails)
76+ if isFalsyNode (targetNode ) {
6377 if isArray {
6478 pathValue = model .GetStringTemplates ().BuildArrayPath (pathValue , x )
6579 }
6680
67- if ! utils .IsNodeMap (fieldNode ) && ! utils .IsNodeArray (fieldNodeValue ) && ! utils .IsNodeMap (fieldNodeValue ) {
68- if context .Index != nil {
69- origin := context .Index .FindNodeOrigin (node )
81+ // skip if target is a complex type (map or array with content)
82+ if targetNode != nil && (utils .IsNodeMap (targetNode ) || utils .IsNodeArray (targetNode )) && len (targetNode .Content ) > 0 {
83+ continue
84+ }
7085
71- if origin != nil && origin .Line > 1 {
72- nm := context .Index .GetNodeMap ()
73- var keys []int
74- for k := range nm {
75- keys = append (keys , k )
76- }
86+ if context .Index != nil {
87+ origin := context .Index .FindNodeOrigin (node )
7788
78- // Sort the keys slice.
79- sort .Ints (keys )
89+ if origin != nil && origin .Line > 1 {
90+ nm := context .Index .GetNodeMap ()
91+ var keys []int
92+ for k := range nm {
93+ keys = append (keys , k )
94+ }
8095
81- np := nm [origin .Line - 1 ][keys [0 ]]
96+ // Sort the keys slice.
97+ sort .Ints (keys )
8298
99+ if len (keys ) > 0 {
100+ np := nm [origin .Line - 1 ][keys [0 ]]
83101 if np != nil {
84102 node = np
85103 }
86104 }
87105 }
106+ }
88107
89- var locatedObjects []v3.Foundational
90- var allPaths []string
91- var err error
92- locatedPath := pathValue
93- if context .DrDocument != nil {
94- if fieldNode == nil {
95- locatedObjects , err = context .DrDocument .LocateModel (node )
96- } else {
97- locatedObjects , err = context .DrDocument .LocateModelsByKeyAndValue (fieldNode , fieldNodeValue )
98- }
99- if err == nil && locatedObjects != nil {
100- for x , obj := range locatedObjects {
101- p := model .GetStringTemplates ().BuildJSONPath (obj .GenerateJSONPath (), context .RuleAction .Field )
102- if x == 0 {
103- locatedPath = p
104- }
105- allPaths = append (allPaths , p )
108+ var locatedObjects []v3.Foundational
109+ var allPaths []string
110+ var err error
111+ locatedPath := pathValue
112+ if context .DrDocument != nil {
113+ if fieldNode == nil {
114+ locatedObjects , err = context .DrDocument .LocateModel (node )
115+ } else {
116+ locatedObjects , err = context .DrDocument .LocateModelsByKeyAndValue (fieldNode , targetNode )
117+ }
118+ if err == nil && locatedObjects != nil {
119+ for i , obj := range locatedObjects {
120+ p := model .GetStringTemplates ().BuildJSONPath (obj .GenerateJSONPath (), context .RuleAction .Field )
121+ if i == 0 {
122+ locatedPath = p
106123 }
124+ allPaths = append (allPaths , p )
107125 }
108126 }
109- result := model. RuleFunctionResult {
110- Message : vacuumUtils . SuppliedOrDefault ( message ,
111- model . GetStringTemplates (). BuildFieldValidationMessage ( ruleMessage , context . RuleAction . Field , "set" )) ,
112- StartNode : node ,
113- EndNode : vacuumUtils . BuildEndNode ( node ) ,
114- Path : locatedPath ,
115- Rule : context . Rule ,
116- }
117- if len ( allPaths ) > 1 {
118- result . Paths = allPaths
119- }
120- results = append ( results , result )
121- if len ( locatedObjects ) > 0 {
122- if arr , ok := locatedObjects [ 0 ].(v3. AcceptsRuleResults ); ok {
123- arr . AddRuleFunctionResult (v3 .ConvertRuleResult ( & result ))
124- }
127+ }
128+ result := model. RuleFunctionResult {
129+ Message : vacuumUtils . SuppliedOrDefault ( message ,
130+ model . GetStringTemplates (). BuildFieldValidationMessage ( ruleMessage , fieldName , "set" )) ,
131+ StartNode : node ,
132+ EndNode : vacuumUtils . BuildEndNode ( node ) ,
133+ Path : locatedPath ,
134+ Rule : context . Rule ,
135+ }
136+ if len ( allPaths ) > 1 {
137+ result . Paths = allPaths
138+ }
139+ results = append ( results , result )
140+ if len ( locatedObjects ) > 0 {
141+ if arr , ok := locatedObjects [ 0 ]. (v3.AcceptsRuleResults ); ok {
142+ arr . AddRuleFunctionResult ( v3 . ConvertRuleResult ( & result ))
125143 }
126144 }
127145 }
128146 }
129147 return results
130148}
149+
150+ // isFalsyNode checks if a YAML node represents a falsy value.
151+ // A node is falsy if it's nil, has no content, or has an empty/false/0 value.
152+ func isFalsyNode (node * yaml.Node ) bool {
153+ if node == nil {
154+ return true
155+ }
156+ // node with content (map or array) is truthy, not falsy
157+ if len (node .Content ) > 0 {
158+ return false
159+ }
160+ // scalar values: check for falsy values
161+ if node .Value == "" || node .Value == "false" || node .Value == "0" {
162+ return true
163+ }
164+ return false
165+ }
0 commit comments