Files
notarius/internal/modules/dnd/shared/diagnostics/diagnostics.go

99 lines
3.1 KiB
Go

// Package diagnostics provides bounded, safe text for deterministic D&D
// decisions and warnings.
package diagnostics
import (
"fmt"
"strconv"
"strings"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
frameworkdiagnostics "gitea.maximumdirect.net/eric/notarius/internal/framework/diagnostics"
)
const (
MaxIssues = 20
MaxWarnings = 20
MaxDisplayedRunes = 128
MaxMessageBytes = 4096
)
// DataQualityResult converts accepted source-quality findings into bounded,
// locally grouped advisories. These findings do not indicate process
// degradation.
func DataQualityResult(findings []contracts.Warning) (contracts.ValidationResult, error) {
collector := frameworkdiagnostics.NewCollector()
for _, finding := range findings {
if err := collector.Add(contracts.ProducerDiagnostic{
Disposition: contracts.DiagnosticDispositionAdvisory,
Category: contracts.DiagnosticCategoryDataQuality,
ReasonCode: finding.ReasonCode,
OccurrenceCount: 1,
Samples: []contracts.DiagnosticSample{{Scope: finding.Scope, Message: finding.Message}},
}); err != nil {
return contracts.ValidationResult{}, fmt.Errorf("collect data-quality diagnostic: %w", err)
}
}
return contracts.ValidationResult{Approved: true, Diagnostics: collector.Diagnostics()}, nil
}
func Truncate(value string) string {
runes := []rune(value)
if len(runes) <= MaxDisplayedRunes {
return value
}
return string(runes[:MaxDisplayedRunes-1]) + "…"
}
func Quote(value string) string { return strconv.Quote(Truncate(value)) }
func Aggregate(prefix string, issues []string) string {
displayed := make([]string, 0, min(len(issues), MaxIssues))
for len(displayed) < len(issues) && len(displayed) < MaxIssues {
issue := Truncate(issues[len(displayed)])
candidate := aggregateMessage(prefix, append(displayed, issue), len(issues)-len(displayed)-1)
if len([]byte(candidate)) > MaxMessageBytes {
break
}
displayed = append(displayed, issue)
}
return aggregateMessage(prefix, displayed, len(issues)-len(displayed))
}
// LimitWarnings returns at most MaxWarnings warnings, reserving the final
// position for a deterministic omission summary when truncation is required.
func LimitWarnings(warnings []contracts.Warning, scope, reasonCode string) []contracts.Warning {
if warnings == nil {
return nil
}
if len(warnings) <= MaxWarnings {
bounded := make([]contracts.Warning, len(warnings))
copy(bounded, warnings)
return bounded
}
displayed := MaxWarnings - 1
bounded := make([]contracts.Warning, displayed, MaxWarnings)
copy(bounded, warnings[:displayed])
bounded = append(bounded, contracts.Warning{
Scope: scope,
ReasonCode: reasonCode,
Message: fmt.Sprintf("%d additional warning(s) omitted", len(warnings)-displayed),
})
return bounded
}
func aggregateMessage(prefix string, issues []string, omitted int) string {
message := prefix + ": " + strings.Join(issues, ", ")
if omitted > 0 {
message += fmt.Sprintf("; %d additional issue(s) omitted", omitted)
}
return message
}
func min(left, right int) int {
if left < right {
return left
}
return right
}