Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save aaron-prindle/1148eafa1b782bccbf09d79e60b75513 to your computer and use it in GitHub Desktop.
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)
// 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