Created
March 10, 2025 05:03
-
-
Save aaron-prindle/1148eafa1b782bccbf09d79e60b75513 to your computer and use it in GitHub Desktop.
gatherDeclarativeValidationMismatches with `1:many` matching more similar to matcher.Test (modified from 1:1 initially)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// gatherDeclarativeValidationMismatches compares imperative and declarative validation errors | |
// using logic that more closely mirrors the approach in ErrorMatcher.Test(). | |
// | |
// Specifically: | |
// - Imperative errors are treated as "want" errors. | |
// - Declarative errors are treated as "got" errors. | |
// - Each "want" error will match all "got" errors that satisfy matcher.Matches(...) and | |
// remove them from the unmatched list. | |
// - 0 matches is treated as an "unmatched imperative" problem. | |
// - More than 1 match is treated as a "multiple matched" problem (instead of picking | |
// the first match). | |
// - Any remaining unmatched declarative errors are "extra." | |
func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field.ErrorList) []string { | |
var mismatchDetails []string | |
// Short circuit: no mismatches if both are empty. | |
if len(imperativeErrs) == 0 && len(declarativeErrs) == 0 { | |
return mismatchDetails | |
} | |
// Provide a recommendation depending on whether takeover is enabled. | |
recommendation := "This difference should not affect system operation since hand-written validation is authoritative." | |
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover) { | |
recommendation = "Consider disabling the DeclarativeValidationTakeover feature gate." | |
} | |
// The matcher used to compare field.Error objects by type, field, origin, etc. | |
matcher := fldtest.ErrorMatcher{}. | |
ByType(). | |
ByField(). | |
ByOrigin(). | |
RequireOriginWhenInvalid() | |
// Make a copy of the declarativeErrs to track which are still unmatched. | |
remaining := append(field.ErrorList(nil), declarativeErrs...) | |
// For each imperative error, see how many declarative errors match it. | |
for _, iErr := range imperativeErrs { | |
// Skip if this imperative error is not intended to be compared | |
// (if your code relies on iErr.CoveredByDeclarative). | |
if !iErr.CoveredByDeclarative { | |
continue | |
} | |
matchedCount := 0 | |
tmp := make(field.ErrorList, 0, len(remaining)) | |
// Collect how many times iErr matches among remaining declarative errors. | |
for _, dErr := range remaining { | |
if matcher.Matches(iErr, dErr) { | |
matchedCount++ | |
} else { | |
tmp = append(tmp, dErr) | |
} | |
} | |
// No matches? | |
if matchedCount == 0 { | |
mismatchDetails = append(mismatchDetails, | |
fmt.Sprintf( | |
"Unexpected difference: no declarative error matched the imperative error %s. %s", | |
matcher.Render(iErr), | |
recommendation, | |
), | |
) | |
// More than 1 match? | |
} else if matchedCount > 1 { | |
mismatchDetails = append(mismatchDetails, | |
fmt.Sprintf( | |
"Multiple declarative errors matched a single imperative error %s. %s", | |
matcher.Render(iErr), | |
recommendation, | |
), | |
) | |
} | |
// Remove all matched errors from remaining (they're considered "consumed"). | |
remaining = tmp | |
} | |
// Any errors left in `remaining` are considered "extra" declarative errors. | |
for _, dErr := range remaining { | |
mismatchDetails = append(mismatchDetails, | |
fmt.Sprintf( | |
"Unexpected difference: extra declarative error %s. %s", | |
matcher.Render(dErr), | |
recommendation, | |
), | |
) | |
} | |
return mismatchDetails | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment