Skip to content

Commit 74a0fce

Browse files
committed
add location information to invalid config
1 parent 45048ac commit 74a0fce

File tree

11 files changed

+353
-44
lines changed

11 files changed

+353
-44
lines changed

pkg/merger/merger.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ func MergeConfigFiles(
3838

3939
// thresholds
4040

41-
if extraConfig.SeverityThreshold != "" && validator.ValidateSeverityThreshold(extraConfig) {
41+
if extraConfig.SeverityThreshold != "" && len(validator.ValidateSeverityThreshold(extraConfig)) == 0 {
4242
config.SeverityThreshold = extraConfig.SeverityThreshold
4343
}
4444

45-
if extraConfig.PriorityThreshold != "" && validator.ValidateSeverityThreshold(extraConfig) {
45+
if extraConfig.PriorityThreshold != "" && len(validator.ValidateSeverityThreshold(extraConfig)) == 0 {
4646
config.PriorityThreshold = extraConfig.PriorityThreshold
4747
}
4848

pkg/models/models.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package models
22

3+
import "gopkg.in/yaml.v3"
4+
35
type Configuration struct {
46
// git platform options
57
EnableFailBuilds *bool `yaml:"enable_fail_builds,omitempty"`
@@ -25,6 +27,8 @@ type Configuration struct {
2527

2628
// TODO deprecate
2729
SecretsWhitelist []string `yaml:"secrets_whitelist,omitempty"`
30+
31+
LocationInfo map[string]yaml.Node `yaml:"-"`
2832
}
2933

3034
func (c *Configuration) GetEnableFailBuilds() bool {

pkg/parser/parse.go

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,103 @@
11
package parser
22

33
import (
4+
"bytes"
5+
"fmt"
46
"strings"
57

68
"github.com/nullify-platform/config-file-parser/pkg/models"
79
"gopkg.in/yaml.v3"
810
)
911

10-
func ParseConfiguration(data []byte) (*models.Configuration, error) {
12+
type LocationTracker struct {
13+
Locations map[string]yaml.Node
14+
}
15+
16+
type ParseError struct {
17+
Message string
18+
Line int
19+
Column int
20+
}
21+
22+
func ParseConfiguration(data []byte) (*models.Configuration, *ParseError) {
23+
// Handle empty configuration case
24+
if len(bytes.TrimSpace(data)) == 0 {
25+
config := &models.Configuration{
26+
LocationInfo: make(map[string]yaml.Node),
27+
}
28+
sanitizeConfig(config)
29+
return config, nil
30+
}
31+
1132
var config models.Configuration
12-
err := yaml.Unmarshal([]byte(data), &config)
13-
if err != nil {
14-
return nil, err
33+
tracker := &LocationTracker{
34+
Locations: make(map[string]yaml.Node),
35+
}
36+
37+
decoder := yaml.NewDecoder(bytes.NewReader(data))
38+
39+
// First, decode into a Node to preserve location information
40+
var node yaml.Node
41+
if err := decoder.Decode(&node); err != nil {
42+
if yamlErr, ok := err.(*yaml.TypeError); ok {
43+
return nil, &ParseError{
44+
Message: yamlErr.Errors[0],
45+
Line: node.Line,
46+
Column: node.Column,
47+
}
48+
}
49+
return nil, &ParseError{
50+
Message: err.Error(),
51+
Line: node.Line,
52+
Column: node.Column,
53+
}
54+
}
55+
56+
// recursively construct location info
57+
if len(node.Content) > 0 && node.Content[0].Kind == yaml.MappingNode {
58+
walkYAMLNode(*node.Content[0], "", tracker)
59+
}
60+
61+
// decode into the actual configuration
62+
if err := node.Decode(&config); err != nil {
63+
return nil, &ParseError{
64+
Message: err.Error(),
65+
Line: node.Line,
66+
Column: node.Column,
67+
}
1568
}
1669

1770
sanitizeConfig(&config)
1871

72+
config.LocationInfo = tracker.Locations
73+
1974
return &config, nil
2075
}
2176

77+
func walkYAMLNode(node yaml.Node, path string, tracker *LocationTracker) {
78+
if node.Kind != yaml.MappingNode {
79+
return
80+
}
81+
82+
for i := 0; i < len(node.Content); i += 2 {
83+
key := node.Content[i]
84+
value := node.Content[i+1]
85+
86+
newPath := key.Value
87+
if path != "" {
88+
newPath = path + "." + key.Value
89+
}
90+
91+
// Store the location information
92+
tracker.Locations[newPath] = *value
93+
94+
// Recurse into nested structures
95+
if value.Kind == yaml.MappingNode {
96+
walkYAMLNode(*value, newPath, tracker)
97+
}
98+
}
99+
}
100+
22101
func sanitizeConfig(config *models.Configuration) {
23102
config.SeverityThreshold = strings.Trim(config.SeverityThreshold, " ")
24103
if config.SeverityThreshold != "" {
@@ -45,3 +124,8 @@ func sanitizeConfig(config *models.Configuration) {
45124
config.Notifications[name] = n
46125
}
47126
}
127+
128+
// Error implements the error interface for ParseError
129+
func (e *ParseError) Error() string {
130+
return fmt.Sprintf("yaml error at line %d, column %d: %s", e.Line, e.Column, e.Message)
131+
}

pkg/validator/autofix.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,26 @@ import (
44
"github.com/nullify-platform/config-file-parser/pkg/models"
55
)
66

7-
func ValidateAutoFix(config *models.Configuration) bool {
8-
return validateAutoFix(config.Code.AutoFix) && validateAutoFix(config.Dependencies.AutoFix)
7+
func ValidateAutoFix(config *models.Configuration) []ValidationError {
8+
errors := []ValidationError{}
9+
if !validateAutoFix(config.Code.AutoFix) {
10+
errors = append(errors, ValidationError{
11+
Field: "code.auto_fix",
12+
Message: "Invalid auto fix",
13+
Line: config.LocationInfo["code.auto_fix"].Line,
14+
Column: config.LocationInfo["code.auto_fix"].Column,
15+
})
16+
}
17+
18+
if !validateAutoFix(config.Dependencies.AutoFix) {
19+
errors = append(errors, ValidationError{
20+
Field: "dependencies.auto_fix",
21+
Message: "Invalid auto fix",
22+
Line: config.LocationInfo["dependencies.auto_fix"].Line,
23+
Column: config.LocationInfo["dependencies.auto_fix"].Column,
24+
})
25+
}
26+
return errors
927
}
1028

1129
func validateAutoFix(autofix *models.AutoFix) bool {

pkg/validator/notifications.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,55 @@
11
package validator
22

33
import (
4+
"fmt"
45
"net/mail"
56

67
"github.com/nullify-platform/config-file-parser/pkg/models"
78
)
89

9-
func ValidateNotifications(config *models.Configuration) bool {
10+
func ValidateNotifications(config *models.Configuration) []ValidationError {
11+
var errors []ValidationError
1012
if config.Notifications == nil {
11-
return true
13+
return errors
1214
}
1315

14-
for _, notification := range config.Notifications {
16+
line := 0
17+
column := 0
18+
19+
for key, notification := range config.Notifications {
1520
if notification.Targets.Email == nil {
1621
continue
1722
}
1823

24+
if node, exists := config.LocationInfo[fmt.Sprintf("notifications.%s.targets.email.address", key)]; exists {
25+
line = node.Line
26+
column = node.Column
27+
}
28+
1929
if notification.Targets.Email.Address != "" {
2030
_, err := mail.ParseAddress(notification.Targets.Email.Address)
2131
if err != nil {
22-
return false
32+
errors = append(errors, ValidationError{
33+
Field: fmt.Sprintf("notifications.%s.targets.email.address", key),
34+
Message: "Invalid notifications",
35+
Line: line,
36+
Column: column,
37+
})
2338
}
2439
}
2540

2641
for _, email := range notification.Targets.Email.Addresses {
2742
_, err := mail.ParseAddress(email)
2843
if err != nil {
29-
return false
44+
errors = append(errors, ValidationError{
45+
Field: fmt.Sprintf("notifications.%s.targets.email.addresses", key),
46+
Message: "Invalid notifications",
47+
Line: line,
48+
Column: column,
49+
})
3050
}
3151
}
3252
}
3353

34-
return true
54+
return errors
3555
}

pkg/validator/paths.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ import (
55
"github.com/nullify-platform/config-file-parser/pkg/models"
66
)
77

8-
func ValidatePaths(config *models.Configuration) bool {
8+
func ValidatePaths(config *models.Configuration) []ValidationError {
9+
errors := []ValidationError{}
910
if config.IgnorePaths == nil {
10-
return true
11+
return errors
1112
}
1213

1314
for _, pattern := range config.IgnorePaths {
1415
_, err := glob.Compile(pattern)
16+
// log.Printf(">>>>>>>>> pattern: %s, gl: %+v", pattern, gl)
1517
if err != nil {
16-
return false
18+
errors = append(errors, ValidationError{
19+
Field: "ignore_paths",
20+
Message: "Invalid paths",
21+
Line: config.LocationInfo["ignore_paths"].Line,
22+
Column: config.LocationInfo["ignore_paths"].Column,
23+
})
1724
}
1825
}
19-
20-
return true
26+
return errors
2127
}

pkg/validator/scheduled_notifications.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,33 @@ import (
99
"github.com/robfig/cron/v3"
1010
)
1111

12-
func ValidateScheduledNotifications(config *models.Configuration) bool {
12+
func ValidateScheduledNotifications(config *models.Configuration) []ValidationError {
13+
errors := []ValidationError{}
1314
if config.ScheduledNotifications == nil {
14-
return true
15+
return errors
1516
}
1617

1718
for _, notification := range config.ScheduledNotifications {
1819
if !validateScheduledNotificationSchedule(notification.Schedule, notification.Timezone) {
19-
return false
20+
errors = append(errors, ValidationError{
21+
Field: "scheduledNotifications",
22+
Message: "Invalid scheduled notifications",
23+
Line: config.LocationInfo["scheduled_notifications"].Line,
24+
Column: config.LocationInfo["scheduled_notifications"].Column,
25+
})
2026
}
2127

2228
if !validateScheduledNotificationEmails(notification) {
23-
return false
29+
errors = append(errors, ValidationError{
30+
Field: "scheduledNotifications",
31+
Message: "Invalid scheduled notifications",
32+
Line: config.LocationInfo["scheduled_notifications"].Line,
33+
Column: config.LocationInfo["scheduled_notifications"].Column,
34+
})
2435
}
2536
}
2637

27-
return true
38+
return errors
2839
}
2940

3041
// validateScheduledNotificationSchedule return true if provided schedule is a valid cron expression.

pkg/validator/severity.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,18 @@ var validSeveritites = []string{
2020
// - MEDIUM / medium
2121
// - HIGH / high
2222
// - CRITICAL / critical
23-
func ValidateSeverityThreshold(config *models.Configuration) bool {
24-
return slices.Contains(validSeveritites, config.SeverityThreshold)
23+
func ValidateSeverityThreshold(config *models.Configuration) []ValidationError {
24+
if !slices.Contains(validSeveritites, config.SeverityThreshold) {
25+
return []ValidationError{
26+
{
27+
Field: "severityThreshold",
28+
Message: "Invalid severity threshold",
29+
Line: config.LocationInfo["severity_threshold"].Line,
30+
Column: config.LocationInfo["severity_threshold"].Column,
31+
},
32+
}
33+
}
34+
return []ValidationError{}
2535
}
2636

2737
var validPriorities = []string{
@@ -41,6 +51,16 @@ var validPriorities = []string{
4151
// - MEDIUM / medium
4252
// - IMPORTANT / important
4353
// - URGENT / urgent
44-
func ValidatePriorityThreshold(config *models.Configuration) bool {
45-
return slices.Contains(validPriorities, config.PriorityThreshold)
54+
func ValidatePriorityThreshold(config *models.Configuration) []ValidationError {
55+
if !slices.Contains(validPriorities, config.PriorityThreshold) {
56+
return []ValidationError{
57+
{
58+
Field: "priorityThreshold",
59+
Message: "Invalid priority threshold",
60+
Line: config.LocationInfo["priority_threshold"].Line,
61+
Column: config.LocationInfo["priority_threshold"].Column,
62+
},
63+
}
64+
}
65+
return []ValidationError{}
4666
}

0 commit comments

Comments
 (0)