Skip to content

Commit

Permalink
Added IsNull and IsNotNull constraints (#64)
Browse files Browse the repository at this point in the history
Also removed constraint reference from README - now maintained in wiki
  • Loading branch information
marrow16 authored Oct 22, 2022
1 parent 0fb398c commit 165ddc9
Show file tree
Hide file tree
Showing 11 changed files with 576 additions and 6,377 deletions.
5,947 changes: 2 additions & 5,945 deletions README.md

Large diffs are not rendered by default.

115 changes: 16 additions & 99 deletions constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ type Constraint interface {
// Check function signature for custom constraints
type Check func(value interface{}, vcx *ValidatorContext, this *CustomConstraint) (passed bool, message string)

type Conditional interface {
MeetsConditions(vcx *ValidatorContext) bool
}

func isConditional(c Constraint) (Conditional, bool) {
cc, ok := c.(Conditional)
return cc, ok
}

func isCheckRequired(c Constraint, vcx *ValidatorContext) bool {
if cc, ok := isConditional(c); ok {
return cc.MeetsConditions(vcx)
}
return true
}

// CustomConstraint is a constraint that can declared on the fly and implements the Constraint interface
type CustomConstraint struct {
CheckFunc Check
Expand All @@ -34,102 +50,3 @@ func (c *CustomConstraint) Check(v interface{}, vcx *ValidatorContext) (bool, st
func (c *CustomConstraint) GetMessage(tcx I18nContext) string {
return obtainI18nContext(tcx).TranslateMessage(c.Message)
}

// ConstraintSet is a constraint that contains other constraints
//
// The contained constraints are checked sequentially but the overall
// set stops on the first failing constraint
type ConstraintSet struct {
// Constraints is the slice of constraints within the set
Constraints Constraints
// when set to true, OneOf specifies that the constraint set should pass just one of
// the contained constraints (rather than all of them)
OneOf bool
// Message is the violation message to be used if any of the constraints fail
//
// If the message is empty, the message from the first failing contained constraint is used
Message string
// Stop when set to true, prevents further validation checks on the property if this constraint set fails
Stop bool
}

const (
constraintSetName = "ConstraintSet"
constraintSetFieldConstraints = "Constraints"
constraintSetFieldOneOf = "OneOf"
constraintSetFieldMessage = constraintPtyNameMessage
constraintSetFieldStop = constraintPtyNameStop
fmtMsgConstraintSetDefaultAllOf = "Constraint set must pass all of %[1]d undisclosed validations"
fmtMsgConstraintSetDefaultOneOf = "Constraint set must pass one of %[1]d undisclosed validations"
)

// Check implements the Constraint.Check and checks the constraints within the set
func (c *ConstraintSet) Check(v interface{}, vcx *ValidatorContext) (bool, string) {
if c.OneOf {
return c.checkOneOf(v, vcx)
} else {
return c.checkAllOf(v, vcx)
}
}

func (c *ConstraintSet) checkAllOf(v interface{}, vcx *ValidatorContext) (bool, string) {
for _, cc := range c.Constraints {
if isCheckRequired(cc, vcx) {
if ok, msg := cc.Check(v, vcx); !ok {
if c.Message == "" && msg != "" {
vcx.CeaseFurtherIf(c.Stop)
return false, msg
}
vcx.CeaseFurtherIf(c.Stop)
return false, c.GetMessage(vcx)
}
if !vcx.continueAll || !vcx.continuePty() {
break
}
}
}
return true, ""
}

func (c *ConstraintSet) checkOneOf(v interface{}, vcx *ValidatorContext) (bool, string) {
finalOk := false
firstMsg := ""
for _, cc := range c.Constraints {
if isCheckRequired(cc, vcx) {
if ok, msg := cc.Check(v, vcx); ok {
finalOk = true
break
} else if firstMsg == "" {
firstMsg = msg
}
if !vcx.continueAll || !vcx.continuePty() {
break
}
}
}
if finalOk {
return true, ""
}
vcx.CeaseFurtherIf(c.Stop)
if c.Message == "" && firstMsg != "" {
return false, firstMsg
}
return false, c.GetMessage(vcx)
}

// GetMessage implements the Constraint.GetMessage
func (c *ConstraintSet) GetMessage(tcx I18nContext) string {
if c.Message == "" {
for _, sc := range c.Constraints {
if msg := sc.GetMessage(tcx); msg != "" {
return msg
}
}
// if we get here then the message is still empty!...
if c.OneOf {
return obtainI18nContext(tcx).TranslateFormat(fmtMsgConstraintSetDefaultOneOf, len(c.Constraints))
}
return obtainI18nContext(tcx).TranslateFormat(fmtMsgConstraintSetDefaultAllOf, len(c.Constraints))
}
return obtainI18nContext(tcx).TranslateMessage(c.Message)
}
4 changes: 4 additions & 0 deletions constraint_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func defaultConstraints() map[string]Constraint {
"GreaterThanOrEqual": &GreaterThanOrEqual{},
"GreaterThanOrEqualOther": &GreaterThanOrEqualOther{},
"GreaterThanOther": &GreaterThanOther{},
"IsNotNull": &IsNotNull{},
"IsNull": &IsNull{},
"Length": &Length{},
"LengthExact": &LengthExact{},
"LessThan": &LessThan{},
Expand Down Expand Up @@ -189,6 +191,8 @@ func defaultConstraints() map[string]Constraint {
"negz": &NegativeOrZero{},
"neqo": &NotEqualsOther{},
"notempty": &NotEmpty{},
"notnull": &IsNotNull{},
"null": &IsNull{},
"pos": &Positive{},
"posz": &PositiveOrZero{},
"range": &Range{},
Expand Down
2 changes: 1 addition & 1 deletion constraint_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
)

const commonConstraintsCount = 101 // excludes abbreviations (every constraint has an abbreviation)
const commonConstraintsCount = 103 // excludes abbreviations (every constraint has an abbreviation)
const commonSpecialAbbrsCount = 8 // special abbreviations

func TestConstraintsRegistryInitialized(t *testing.T) {
Expand Down
Loading

0 comments on commit 165ddc9

Please sign in to comment.