// 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" ) const ( MaxIssues = 20 MaxWarnings = 20 MaxDisplayedRunes = 128 MaxMessageBytes = 4096 ) 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 }