Skip to content

Commit 81f251c

Browse files
committed
address issue #787
missing field key is now handled properly.
1 parent a30c668 commit 81f251c

File tree

3 files changed

+129
-54
lines changed

3 files changed

+129
-54
lines changed

functions/core/falsy.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func (f Falsy) GetCategory() string {
3030
}
3131

3232
// RunRule will execute the Falsy rule, based on supplied context and a supplied []*yaml.Node slice.
33+
// If no field is specified, the function checks if the matched node itself is truthy (and reports if so).
34+
// If a field is specified, the function checks if that field within the matched node is truthy.
3335
func (f Falsy) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) []model.RuleFunctionResult {
3436

3537
if len(nodes) <= 0 {
@@ -49,17 +51,37 @@ func (f Falsy) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) []
4951
}
5052

5153
for _, node := range nodes {
54+
// handle document nodes by unwrapping
55+
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
56+
node = node.Content[0]
57+
}
58+
59+
var targetNode *yaml.Node
60+
var fieldNode *yaml.Node
61+
var fieldName string
5262

53-
fieldNode, fieldNodeValue := utils.FindKeyNode(context.RuleAction.Field, node.Content)
54-
if (fieldNode != nil && fieldNodeValue != nil) &&
55-
(fieldNodeValue.Value != "" && fieldNodeValue.Value != "false" && fieldNodeValue.Value != "0" || (fieldNodeValue.Value == "" && fieldNodeValue.Content != nil)) {
63+
if context.RuleAction.Field == "" {
64+
// no field specified - check the matched node itself
65+
targetNode = node
66+
fieldName = "value"
67+
} else {
68+
// field specified - find it within the node
69+
fieldNode, targetNode = utils.FindKeyNode(context.RuleAction.Field, node.Content)
70+
fieldName = context.RuleAction.Field
71+
}
5672

73+
// check if the target is truthy (which means falsy check fails)
74+
if targetNode != nil && isTruthyNode(targetNode) {
5775
var locatedObjects []v3.Foundational
5876
var allPaths []string
5977
var err error
6078
locatedPath := pathValue
6179
if context.DrDocument != nil {
62-
locatedObjects, err = context.DrDocument.LocateModelsByKeyAndValue(fieldNode, fieldNodeValue)
80+
if fieldNode != nil {
81+
locatedObjects, err = context.DrDocument.LocateModelsByKeyAndValue(fieldNode, targetNode)
82+
} else {
83+
locatedObjects, err = context.DrDocument.LocateModel(node)
84+
}
6385
if err == nil && locatedObjects != nil {
6486
for x, obj := range locatedObjects {
6587
if x == 0 {
@@ -70,7 +92,7 @@ func (f Falsy) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) []
7092
}
7193
}
7294
result := model.RuleFunctionResult{
73-
Message: fmt.Sprintf("%s: `%s` must be falsy", ruleMessage, context.RuleAction.Field),
95+
Message: fmt.Sprintf("%s: `%s` must be falsy", ruleMessage, fieldName),
7496
StartNode: node,
7597
EndNode: vacuumUtils.BuildEndNode(node),
7698
Path: locatedPath,
@@ -90,3 +112,20 @@ func (f Falsy) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) []
90112

91113
return results
92114
}
115+
116+
// isTruthyNode checks if a YAML node represents a truthy value.
117+
// A node is truthy if it has content, or has a non-empty/non-false/non-zero value.
118+
func isTruthyNode(node *yaml.Node) bool {
119+
if node == nil {
120+
return false
121+
}
122+
// node with content (map or array) is truthy
123+
if len(node.Content) > 0 {
124+
return true
125+
}
126+
// scalar values: check for falsy values
127+
if node.Value == "" || node.Value == "false" || node.Value == "0" {
128+
return false
129+
}
130+
return true
131+
}

functions/core/truthy.go

Lines changed: 83 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
3234
func (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+
}

motor/inline_ignore_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ paths: {}
355355
`
356356

357357
// Create a custom rule that targets all properties under info (including ignore key)
358+
// Using falsy function which will report violations for truthy values
358359
rulesetYaml := `
359360
extends: []
360361
rules:
@@ -363,7 +364,7 @@ rules:
363364
given: $.info.*
364365
severity: error
365366
then:
366-
function: truthy
367+
function: falsy
367368
`
368369

369370
rc := CreateRuleComposer()

0 commit comments

Comments
 (0)