Skip to content

add location information to invalid config #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ toolchain go1.23.4

require (
github.com/gobwas/glob v0.2.3
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
Expand Down
4 changes: 2 additions & 2 deletions pkg/merger/merger.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ func MergeConfigFiles(

// thresholds

if extraConfig.SeverityThreshold != "" && validator.ValidateSeverityThreshold(extraConfig) {
if extraConfig.SeverityThreshold != "" && len(validator.ValidateSeverityThreshold(extraConfig)) == 0 {
config.SeverityThreshold = extraConfig.SeverityThreshold
}

if extraConfig.PriorityThreshold != "" && validator.ValidateSeverityThreshold(extraConfig) {
if extraConfig.PriorityThreshold != "" && len(validator.ValidateSeverityThreshold(extraConfig)) == 0 {
config.PriorityThreshold = extraConfig.PriorityThreshold
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package models

import "gopkg.in/yaml.v3"

type Configuration struct {
// git platform options
EnableFailBuilds *bool `yaml:"enable_fail_builds,omitempty"`
Expand All @@ -25,6 +27,8 @@ type Configuration struct {

// TODO deprecate
SecretsWhitelist []string `yaml:"secrets_whitelist,omitempty"`

LocationInfo map[string]yaml.Node `yaml:"-"`
}

func (c *Configuration) GetEnableFailBuilds() bool {
Expand Down
92 changes: 88 additions & 4 deletions pkg/parser/parse.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,103 @@
package parser

import (
"bytes"
"fmt"
"strings"

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

func ParseConfiguration(data []byte) (*models.Configuration, error) {
type LocationTracker struct {
Locations map[string]yaml.Node
}

type ParseError struct {
Message string
Line int
Column int
}

func ParseConfiguration(data []byte) (*models.Configuration, *ParseError) {
// Handle empty configuration case
if len(bytes.TrimSpace(data)) == 0 {
config := &models.Configuration{
LocationInfo: make(map[string]yaml.Node),
}
sanitizeConfig(config)
return config, nil
}

var config models.Configuration
err := yaml.Unmarshal([]byte(data), &config)
if err != nil {
return nil, err
tracker := &LocationTracker{
Locations: make(map[string]yaml.Node),
}

decoder := yaml.NewDecoder(bytes.NewReader(data))

// First, decode into a Node to preserve location information
var node yaml.Node
if err := decoder.Decode(&node); err != nil {
if yamlErr, ok := err.(*yaml.TypeError); ok {
return nil, &ParseError{
Message: yamlErr.Errors[0],
Line: node.Line,
Column: node.Column,
}
}
return nil, &ParseError{
Message: err.Error(),
Line: node.Line,
Column: node.Column,
}
}

// recursively construct location info
if len(node.Content) > 0 && node.Content[0].Kind == yaml.MappingNode {
walkYAMLNode(*node.Content[0], "", tracker)
}

// decode into the actual configuration
if err := node.Decode(&config); err != nil {
return nil, &ParseError{
Message: err.Error(),
Line: node.Line,
Column: node.Column,
}
}

sanitizeConfig(&config)

config.LocationInfo = tracker.Locations

return &config, nil
}

func walkYAMLNode(node yaml.Node, path string, tracker *LocationTracker) {
if node.Kind != yaml.MappingNode {
return
}

for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
value := node.Content[i+1]

newPath := key.Value
if path != "" {
newPath = path + "." + key.Value
}

// Store the location information
tracker.Locations[newPath] = *value

// Recurse into nested structures
if value.Kind == yaml.MappingNode {
walkYAMLNode(*value, newPath, tracker)
}
}
}

func sanitizeConfig(config *models.Configuration) {
config.SeverityThreshold = strings.Trim(config.SeverityThreshold, " ")
if config.SeverityThreshold != "" {
Expand All @@ -45,3 +124,8 @@ func sanitizeConfig(config *models.Configuration) {
config.Notifications[name] = n
}
}

// Error implements the error interface for ParseError
func (e *ParseError) Error() string {
return fmt.Sprintf("yaml error at line %d, column %d: %s", e.Line, e.Column, e.Message)
}
22 changes: 20 additions & 2 deletions pkg/validator/autofix.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@ import (
"github.com/nullify-platform/config-file-parser/pkg/models"
)

func ValidateAutoFix(config *models.Configuration) bool {
return validateAutoFix(config.Code.AutoFix) && validateAutoFix(config.Dependencies.AutoFix)
func ValidateAutoFix(config *models.Configuration) []ValidationError {
errors := []ValidationError{}
if !validateAutoFix(config.Code.AutoFix) {
errors = append(errors, ValidationError{
Field: "code.auto_fix",
Message: "Invalid auto fix",
Line: config.LocationInfo["code.auto_fix"].Line,
Column: config.LocationInfo["code.auto_fix"].Column,
})
}

if !validateAutoFix(config.Dependencies.AutoFix) {
errors = append(errors, ValidationError{
Field: "dependencies.auto_fix",
Message: "Invalid auto fix",
Line: config.LocationInfo["dependencies.auto_fix"].Line,
Column: config.LocationInfo["dependencies.auto_fix"].Column,
})
}
return errors
}

func validateAutoFix(autofix *models.AutoFix) bool {
Expand Down
32 changes: 26 additions & 6 deletions pkg/validator/notifications.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
package validator

import (
"fmt"
"net/mail"

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

func ValidateNotifications(config *models.Configuration) bool {
func ValidateNotifications(config *models.Configuration) []ValidationError {
var errors []ValidationError
if config.Notifications == nil {
return true
return errors
}

for _, notification := range config.Notifications {
line := 0
column := 0

for key, notification := range config.Notifications {
if notification.Targets.Email == nil {
continue
}

if node, exists := config.LocationInfo[fmt.Sprintf("notifications.%s.targets.email.address", key)]; exists {
line = node.Line
column = node.Column
}

if notification.Targets.Email.Address != "" {
_, err := mail.ParseAddress(notification.Targets.Email.Address)
if err != nil {
return false
errors = append(errors, ValidationError{
Field: fmt.Sprintf("notifications.%s.targets.email.address", key),
Message: "Invalid notifications",
Line: line,
Column: column,
})
}
}

for _, email := range notification.Targets.Email.Addresses {
_, err := mail.ParseAddress(email)
if err != nil {
return false
errors = append(errors, ValidationError{
Field: fmt.Sprintf("notifications.%s.targets.email.addresses", key),
Message: "Invalid notifications",
Line: line,
Column: column,
})
}
}
}

return true
return errors
}
16 changes: 11 additions & 5 deletions pkg/validator/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ import (
"github.com/nullify-platform/config-file-parser/pkg/models"
)

func ValidatePaths(config *models.Configuration) bool {
func ValidatePaths(config *models.Configuration) []ValidationError {
errors := []ValidationError{}
if config.IgnorePaths == nil {
return true
return errors
}

for _, pattern := range config.IgnorePaths {
_, err := glob.Compile(pattern)
// log.Printf(">>>>>>>>> pattern: %s, gl: %+v", pattern, gl)
if err != nil {
return false
errors = append(errors, ValidationError{
Field: "ignore_paths",
Message: "Invalid paths",
Line: config.LocationInfo["ignore_paths"].Line,
Column: config.LocationInfo["ignore_paths"].Column,
})
}
}

return true
return errors
}
21 changes: 16 additions & 5 deletions pkg/validator/scheduled_notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,33 @@ import (
"github.com/robfig/cron/v3"
)

func ValidateScheduledNotifications(config *models.Configuration) bool {
func ValidateScheduledNotifications(config *models.Configuration) []ValidationError {
errors := []ValidationError{}
if config.ScheduledNotifications == nil {
return true
return errors
}

for _, notification := range config.ScheduledNotifications {
if !validateScheduledNotificationSchedule(notification.Schedule, notification.Timezone) {
return false
errors = append(errors, ValidationError{
Field: "scheduledNotifications",
Message: "Invalid scheduled notifications",
Line: config.LocationInfo["scheduled_notifications"].Line,
Column: config.LocationInfo["scheduled_notifications"].Column,
})
}

if !validateScheduledNotificationEmails(notification) {
return false
errors = append(errors, ValidationError{
Field: "scheduledNotifications",
Message: "Invalid scheduled notifications",
Line: config.LocationInfo["scheduled_notifications"].Line,
Column: config.LocationInfo["scheduled_notifications"].Column,
})
}
}

return true
return errors
}

// validateScheduledNotificationSchedule return true if provided schedule is a valid cron expression.
Expand Down
28 changes: 24 additions & 4 deletions pkg/validator/severity.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@ var validSeveritites = []string{
// - MEDIUM / medium
// - HIGH / high
// - CRITICAL / critical
func ValidateSeverityThreshold(config *models.Configuration) bool {
return slices.Contains(validSeveritites, config.SeverityThreshold)
func ValidateSeverityThreshold(config *models.Configuration) []ValidationError {
if !slices.Contains(validSeveritites, config.SeverityThreshold) {
return []ValidationError{
{
Field: "severityThreshold",
Message: "Invalid severity threshold",
Line: config.LocationInfo["severity_threshold"].Line,
Column: config.LocationInfo["severity_threshold"].Column,
},
}
}
return []ValidationError{}
}

var validPriorities = []string{
Expand All @@ -41,6 +51,16 @@ var validPriorities = []string{
// - MEDIUM / medium
// - IMPORTANT / important
// - URGENT / urgent
func ValidatePriorityThreshold(config *models.Configuration) bool {
return slices.Contains(validPriorities, config.PriorityThreshold)
func ValidatePriorityThreshold(config *models.Configuration) []ValidationError {
if !slices.Contains(validPriorities, config.PriorityThreshold) {
return []ValidationError{
{
Field: "priorityThreshold",
Message: "Invalid priority threshold",
Line: config.LocationInfo["priority_threshold"].Line,
Column: config.LocationInfo["priority_threshold"].Column,
},
}
}
return []ValidationError{}
}
Loading
Loading