Add ValidationLevel to field errors

This commit is contained in:
Your Name
2026-02-02 20:00:29 +00:00
parent 92de205642
commit 123ad66688
3 changed files with 83 additions and 5 deletions

View File

@@ -40,11 +40,12 @@ type ErrorMatcher struct {
matchField bool
// TODO(thockin): consider whether value could be assumed - if the
// "want" error has a nil value, don't match on value.
matchValue bool
matchOrigin bool
matchDetail func(want, got string) bool
requireOriginWhenInvalid bool
matchDeclarativeNative bool
matchValue bool
matchOrigin bool
matchDetail func(want, got string) bool
requireOriginWhenInvalid bool
matchDeclarativeNative bool
matchValidationStabilityLevel bool
// normalizationRules holds the pre-compiled regex patterns for path normalization.
normalizationRules []NormalizationRule
}
@@ -90,6 +91,9 @@ func (m ErrorMatcher) Matches(want, got *Error) bool {
if m.matchDeclarativeNative && want.DeclarativeNative != got.DeclarativeNative {
return false
}
if m.matchValidationStabilityLevel && want.ValidationStabilityLevel != got.ValidationStabilityLevel {
return false
}
return true
}
@@ -157,6 +161,10 @@ func (m ErrorMatcher) Render(e *Error) string {
comma()
buf.WriteString(fmt.Sprintf("DeclarativeNative=%t", e.DeclarativeNative))
}
if m.matchValidationStabilityLevel {
comma()
buf.WriteString(fmt.Sprintf("ValidationStabilityLevel=%s", e.ValidationStabilityLevel))
}
return "{" + buf.String() + "}"
}
@@ -240,6 +248,13 @@ func (m ErrorMatcher) ByDeclarativeNative() ErrorMatcher {
return m
}
// ByValidationStabilityLevel returns a derived ErrorMatcher which also matches by the validation stability level
// value of field errors.
func (m ErrorMatcher) ByValidationStabilityLevel() ErrorMatcher {
m.matchValidationStabilityLevel = true
return m
}
// ByDetailExact returns a derived ErrorMatcher which also matches errors by
// the exact detail string.
func (m ErrorMatcher) ByDetailExact() ErrorMatcher {

View File

@@ -437,6 +437,17 @@ func TestErrorMatcher_Test(t *testing.T) {
want: ErrorList{{DeclarativeNative: true}},
got: ErrorList{{DeclarativeNative: false}},
expectedErrors: []string{"expected an error matching:", "unmatched error:"},
}, {
name: "validation level: match",
matcher: ErrorMatcher{}.ByValidationStabilityLevel(),
want: ErrorList{{}}.MarkAlpha(),
got: ErrorList{{}}.MarkAlpha(),
}, {
name: "validation level: no match",
matcher: ErrorMatcher{}.ByValidationStabilityLevel(),
want: ErrorList{{}}.MarkAlpha(),
got: ErrorList{{}}.MarkBeta(),
expectedErrors: []string{"expected an error matching:", "unmatched error:"},
}, {
name: "with origin: single match",
matcher: ErrorMatcher{}.ByField().ByOrigin(),

View File

@@ -59,6 +59,29 @@ type Error struct {
// DeclarativeNative is true when this error originates from a declarative-native validation.
// This field is used to distinguish errors that are exclusively declarative and lack an imperative counterpart.
DeclarativeNative bool
// ValidationStabilityLevel denotes the validation stability level of the declarative validation from this error is returned. This should be used in the declarative validations only.
ValidationStabilityLevel validationStabilityLevel
}
// ValidationLevel denotes the stability level of a validation.
type validationStabilityLevel int
const (
unknown validationStabilityLevel = iota
stabilityLevelAlpha
stabilityLevelBeta
)
func (v validationStabilityLevel) String() string {
switch v {
case stabilityLevelAlpha:
return "alpha"
case stabilityLevelBeta:
return "beta"
default:
return "unknown"
}
}
var _ error = &Error{}
@@ -117,6 +140,7 @@ func (e *Error) ErrorBody() string {
if len(e.Detail) != 0 {
s += fmt.Sprintf(": %s", e.Detail)
}
return s
}
@@ -464,6 +488,34 @@ func (list ErrorList) MarkDeclarativeNative() ErrorList {
return list
}
// MarkAlpha marks the error as an alpha validation error.
func (e *Error) MarkAlpha() *Error {
e.ValidationStabilityLevel = stabilityLevelAlpha
return e
}
// MarkAlpha marks the errors as alpha validation errors.
func (list ErrorList) MarkAlpha() ErrorList {
for _, err := range list {
err.ValidationStabilityLevel = stabilityLevelAlpha
}
return list
}
// MarkBeta marks the error as a beta validation error.
func (e *Error) MarkBeta() *Error {
e.ValidationStabilityLevel = stabilityLevelBeta
return e
}
// MarkBeta marks the errors as beta validation errors.
func (list ErrorList) MarkBeta() ErrorList {
for _, err := range list {
err.ValidationStabilityLevel = stabilityLevelBeta
}
return list
}
// RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation.
func (list ErrorList) RemoveCoveredByDeclarative() ErrorList {
newList := ErrorList{}