55 "encoding/json"
66 "errors"
77 "fmt"
8+ "slices"
9+ "strings"
10+ "sync"
811 "time"
912
1013 "github.com/fusakla/promruval/v3/pkg/config"
@@ -76,6 +79,8 @@ type ValidationReport struct {
7679 ValidationRules []ValidationRule `json:"validation_rules" yaml:"validation_rules"`
7780
7881 FilesReports []* FileReport `json:"files_reports" yaml:"files_reports"`
82+
83+ mu sync.Mutex `json:"-" yaml:"-"`
7984}
8085
8186func (r * ValidationReport ) NewFileReport (name string ) * FileReport {
@@ -85,17 +90,38 @@ func (r *ValidationReport) NewFileReport(name string) *FileReport {
8590 Errors : []* Error {},
8691 GroupReports : []* GroupReport {},
8792 }
93+ r .mu .Lock ()
8894 r .FilesReports = append (r .FilesReports , & newReport )
95+ r .mu .Unlock ()
8996 return & newReport
9097}
9198
99+ // Sort sorts all reports (files, groups, rules) for predictable output.
100+ func (r * ValidationReport ) Sort () {
101+ slices .SortFunc (r .FilesReports , func (a , b * FileReport ) int {
102+ return strings .Compare (a .Name , b .Name )
103+ })
104+ for _ , fileReport := range r .FilesReports {
105+ slices .SortFunc (fileReport .GroupReports , func (a , b * GroupReport ) int {
106+ return strings .Compare (a .Name , b .Name )
107+ })
108+ for _ , groupReport := range fileReport .GroupReports {
109+ slices .SortFunc (groupReport .RuleReports , func (a , b * RuleReport ) int {
110+ return strings .Compare (a .Name , b .Name )
111+ })
112+ }
113+ }
114+ }
115+
92116type FileReport struct {
93117 Name string `json:"file_name" yaml:"file_name"`
94118 Valid bool `json:"valid" yaml:"valid"`
95119 Excluded bool `json:"excluded" yaml:"excluded"`
96120 Errors []* Error `json:"errors" yaml:"errors"`
97121 HasRuleValidationErrors bool `json:"has_rule_validation_errors" yaml:"has_rule_validation_errors"`
98122 GroupReports []* GroupReport `json:"group_reports" yaml:"group_reports"`
123+
124+ mu sync.Mutex `json:"-" yaml:"-"`
99125}
100126
101127func (r * FileReport ) NewGroupReport (name string ) * GroupReport {
@@ -105,7 +131,9 @@ func (r *FileReport) NewGroupReport(name string) *GroupReport {
105131 RuleReports : []* RuleReport {},
106132 Errors : []* Error {},
107133 }
134+ r .mu .Lock ()
108135 r .GroupReports = append (r .GroupReports , & newReport )
136+ r .mu .Unlock ()
109137 return & newReport
110138}
111139
@@ -129,6 +157,8 @@ type GroupReport struct {
129157 Excluded bool `json:"excluded" yaml:"excluded"`
130158 RuleReports []* RuleReport `json:"rule_reports" yaml:"rule_reports"`
131159 Errors []* Error `json:"errors" yaml:"errors"`
160+
161+ mu sync.Mutex `json:"-" yaml:"-"`
132162}
133163
134164func (r * GroupReport ) NewRuleReport (name string , ruleType config.ValidationScope ) * RuleReport {
@@ -138,7 +168,9 @@ func (r *GroupReport) NewRuleReport(name string, ruleType config.ValidationScope
138168 RuleType : ruleType ,
139169 Errors : []* Error {},
140170 }
171+ r .mu .Lock ()
141172 r .RuleReports = append (r .RuleReports , & newReport )
173+ r .mu .Unlock ()
142174 return & newReport
143175}
144176
@@ -191,6 +223,8 @@ func (r *RuleReport) AsText(output *IndentedOutput) {
191223}
192224
193225func (r * ValidationReport ) AsText (indentationStep int , color bool ) (string , error ) {
226+ r .Sort ()
227+
194228 output := NewIndentedOutput (indentationStep , color )
195229 validationText , err := ValidationDocs (r .ValidationRules , "text" )
196230 if err != nil {
@@ -227,6 +261,8 @@ func renderStatistic(objectType string, total, excluded int) string {
227261}
228262
229263func (r * ValidationReport ) AsJSON () (string , error ) {
264+ r .Sort ()
265+
230266 b , err := json .MarshalIndent (r , "" , " " )
231267 if err != nil {
232268 return "" , err
@@ -236,6 +272,8 @@ func (r *ValidationReport) AsJSON() (string, error) {
236272}
237273
238274func (r * ValidationReport ) AsYaml () (string , error ) {
275+ r .Sort ()
276+
239277 b , err := yaml .Marshal (r )
240278 if err != nil {
241279 return "" , err
0 commit comments