11package parser
22
33import (
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+
22101func 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+ }
0 commit comments