Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

required_without and excluded_with issues with nested structs #1325

Open
2 tasks done
dakojohn opened this issue Oct 18, 2024 · 3 comments
Open
2 tasks done

required_without and excluded_with issues with nested structs #1325

dakojohn opened this issue Oct 18, 2024 · 3 comments

Comments

@dakojohn
Copy link

  • I have looked at the documentation here first?
  • I have looked at the examples provided that may showcase my question here?

Package version eg. v9, v10:

v10

Issue, Question or Enhancement:

I am working on some code and trying to make two structs XOR'd with each other. As I understand the validations, I should be able to use required_without and excluded_with to make this happen, but they don't seem to do anything?

Code sample, to showcase or reproduce:

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// OuterStruct structure will hold either the A or the B struct, but not both.
type OuterStruct struct {
	// these validate tags are basically an XOR between A and B
	A A `validate:"required_without=B,excluded_with=B"`
	B B `validate:"required_without=A,excluded_with=A"`
}

// A structure holds a URL.
type A struct {
	MyString string
}

// B structure holds a string.
type B struct {
	MyOtherString string
}

var a = A{
	MyString: "test",
}

var b = B{
	MyOtherString: "123abcd",
}

func main() {
	v := validator.New()

	fmt.Println("Neither set: should fail")
	fmt.Println(v.Struct(&OuterStruct{A: A{}, B: B{}})) // should fail on required_without

	fmt.Println("Only A set: should pass")
	fmt.Println(v.Struct(&OuterStruct{A: a, B: B{}})) // should pass

	fmt.Println("Only B set: should pass")
	fmt.Println(v.Struct(&OuterStruct{A: A{}, B: b})) // should pass

	fmt.Println("Both set: should fail")
	fmt.Println(v.Struct(&OuterStruct{A: a, B: b})) // should fail on excluded_with
}

Output:

Neither set: should fail
<nil>
Only A set: should pass
<nil>
Only B set: should pass
<nil>
Both set: should fail
<nil>
@dakojohn
Copy link
Author

dakojohn commented Oct 22, 2024

I created some more tests to get more data.

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// NoPointer structure will hold either the Fire or the Water struct, but not both.
type NoPointer struct {
	// these validate tags are basically an XOR between Fire and Water
	Fire Fire `validate:"required_without=Water,excluded_with=Water"`
	Water Water `validate:"required_without=Fire,excluded_with=Fire"`
}

type Pointer struct {
	// these validate tags are basically an XOR between Fire and Water
	Fire *Fire `validate:"required_without=Water,excluded_with=Water"`
	Water *Water `validate:"required_without=Fire,excluded_with=Fire"`
}

// Fire structure holds a URL.
type Fire struct {
	MyString string
}

var fire = Fire{
	MyString: "test",
}

// Water structure holds a string.
type Water struct {
	MyOtherString string
}

var water = Water{
	MyOtherString: "123abcd",
}


type NoPointerInnerValidations struct {
	// these validate tags are basically an XOR between Earth and Air
	Earth Earth `validate:"required_without=Air,excluded_with=Air"`
	Air Air `validate:"required_without=Earth,excluded_with=Earth"`
}

type PointerInnerValidations struct {
	// these validate tags are basically an XOR between Earth and Air
	Earth *Earth `validate:"required_without=Air,excluded_with=Air"`
	Air *Air `validate:"required_without=Earth,excluded_with=Earth"`
}

type Earth struct {
	MyURLString string `validate:"required,url"`
}

var earth = Earth{
	MyURLString: "missingProtocol.www.google.com",
}

type Air struct {
	MyUUIDString string `validate:"required,uuid"`
}

var air = Air{
	MyUUIDString: "not a uuid",
}


func main() {
	v := validator.New()

	fmt.Println("========================================")
	fmt.Println("Test No Pointer")
	testNoPointer(v)

	fmt.Println("========================================")
	fmt.Println("Test Pointer")
	testPointer(v)

	fmt.Println("========================================")
	fmt.Println("Test No Pointer with inner validations")
	testNoPointerInnerValidations(v)

	fmt.Println("========================================")
	fmt.Println("Test Pointer with inner validations")
	testPointerInnerValidations(v)
}

func testNoPointer(v *validator.Validate) {
	fmt.Println("Neither set: should fail on required_without")
	fmt.Println(v.Struct(&NoPointer{Fire: Fire{}, Water: Water{}})) // should fail on required_without
	fmt.Println()
	// Apparently required_without doesn't work with zero_value struct, only nil.

	fmt.Println("Only Fire set: should pass")
	fmt.Println(v.Struct(&NoPointer{Fire: fire, Water: Water{}})) // should pass
	fmt.Println()

	fmt.Println("Only Water set: should pass")
	fmt.Println(v.Struct(&NoPointer{Fire: Fire{}, Water: water})) // should pass
	fmt.Println()

	fmt.Println("Both set: should fail on excluded_with")
	fmt.Println(v.Struct(&NoPointer{Fire: fire, Water: water})) // should fail on excluded_with
	fmt.Println()
	// Excluded_with does not seem to work with structs.
}

func testPointer(v *validator.Validate) {
	fmt.Println("Neither set: should fail on required_without")
	fmt.Println(v.Struct(&Pointer{Fire: nil, Water: nil})) // should fail on required_without
	fmt.Println()
	// Required_without works here.

	fmt.Println("Only Fire set: should pass")
	fmt.Println(v.Struct(&Pointer{Fire: &fire, Water: nil})) // should pass
	fmt.Println()

	fmt.Println("Only Water set: should pass")
	fmt.Println(v.Struct(&Pointer{Fire: nil, Water: &water})) // should pass
	fmt.Println()

	fmt.Println("Both set: should fail on excluded_with")
	fmt.Println(v.Struct(&Pointer{Fire: &fire, Water: &water})) // should fail on excluded_with
	fmt.Println()
	// Excluded_with does not seem to work with structs.
}

func testNoPointerInnerValidations(v *validator.Validate) {
	fmt.Println("Neither set: should fail only on required_without")
	fmt.Println(v.Struct(&NoPointerInnerValidations{Earth: Earth{}, Air: Air{}})) // should fail on required_without
	fmt.Println()

	fmt.Println("Only Earth set: should fail only on url validation")
	fmt.Println(v.Struct(&NoPointerInnerValidations{Earth: earth, Air: Air{}})) // should fail only on url validation
	fmt.Println()

	fmt.Println("Only Air set: should fail only on uuid validation")
	fmt.Println(v.Struct(&NoPointerInnerValidations{Earth: Earth{}, Air: air})) // should fail only on uuid validation
	fmt.Println()

	fmt.Println("Both set: should fail only on excluded_with")
	fmt.Println(v.Struct(&NoPointerInnerValidations{Earth: earth, Air: air})) // should fail on excluded_with
	fmt.Println()

	// this whole block fails because inner validations run every time, even for zero value structs.
}

func testPointerInnerValidations(v *validator.Validate) {
	fmt.Println("Neither set: should fail only on required_without")
	fmt.Println(v.Struct(&PointerInnerValidations{Earth: nil, Air: nil})) // should fail on required_without
	fmt.Println()

	fmt.Println("Only Earth set: should fail only on url validation")
	fmt.Println(v.Struct(&PointerInnerValidations{Earth: &earth, Air: nil})) // should fail only on url validation
	fmt.Println()

	fmt.Println("Only Air set: should fail only on uuid validation")
	fmt.Println(v.Struct(&PointerInnerValidations{Earth: nil, Air: &air})) // should fail only on uuid validation
	fmt.Println()

	fmt.Println("Both set: should fail only on excluded_with")
	fmt.Println(v.Struct(&PointerInnerValidations{Earth: &earth, Air: &air})) // should fail on excluded_with
	fmt.Println()
	// Excluded_with does not seem to work with structs.

	// If excluded_with worked with structs this would be correct.
}

Output:

========================================
Test No Pointer
Neither set: should fail on required_without
<nil>

Only Fire set: should pass
<nil>

Only Water set: should pass
<nil>

Both set: should fail on excluded_with
<nil>

========================================
Test Pointer
Neither set: should fail on required_without
Key: 'Pointer.Fire' Error:Field validation for 'Fire' failed on the 'required_without' tag
Key: 'Pointer.Water' Error:Field validation for 'Water' failed on the 'required_without' tag

Only Fire set: should pass
<nil>

Only Water set: should pass
<nil>

Both set: should fail on excluded_with
<nil>

========================================
Test No Pointer with inner validations
Neither set: should fail only on required_without
Key: 'NoPointerInnerValidations.Earth.MyURLString' Error:Field validation for 'MyURLString' failed on the 'required' tag
Key: 'NoPointerInnerValidations.Air.MyUUIDString' Error:Field validation for 'MyUUIDString' failed on the 'required' tag

Only Earth set: should fail only on url validation
Key: 'NoPointerInnerValidations.Earth.MyURLString' Error:Field validation for 'MyURLString' failed on the 'url' tag
Key: 'NoPointerInnerValidations.Air.MyUUIDString' Error:Field validation for 'MyUUIDString' failed on the 'required' tag

Only Air set: should fail only on uuid validation
Key: 'NoPointerInnerValidations.Earth.MyURLString' Error:Field validation for 'MyURLString' failed on the 'required' tag
Key: 'NoPointerInnerValidations.Air.MyUUIDString' Error:Field validation for 'MyUUIDString' failed on the 'uuid' tag

Both set: should fail only on excluded_with
Key: 'NoPointerInnerValidations.Earth.MyURLString' Error:Field validation for 'MyURLString' failed on the 'url' tag
Key: 'NoPointerInnerValidations.Air.MyUUIDString' Error:Field validation for 'MyUUIDString' failed on the 'uuid' tag

========================================
Test Pointer with inner validations
Neither set: should fail only on required_without
Key: 'PointerInnerValidations.Earth' Error:Field validation for 'Earth' failed on the 'required_without' tag
Key: 'PointerInnerValidations.Air' Error:Field validation for 'Air' failed on the 'required_without' tag

Only Earth set: should fail only on url validation
Key: 'PointerInnerValidations.Earth.MyURLString' Error:Field validation for 'MyURLString' failed on the 'url' tag

Only Air set: should fail only on uuid validation
Key: 'PointerInnerValidations.Air.MyUUIDString' Error:Field validation for 'MyUUIDString' failed on the 'uuid' tag

Both set: should fail only on excluded_with
Key: 'PointerInnerValidations.Earth.MyURLString' Error:Field validation for 'MyURLString' failed on the 'url' tag
Key: 'PointerInnerValidations.Air.MyUUIDString' Error:Field validation for 'MyUUIDString' failed on the 'uuid' tag

@dakojohn
Copy link
Author

Results seem to be that required_without doesn't work with empty structs, only nil. excluded_with doesn't seem to work with structs at all.

I'm not sure what the intention is supposed to be, but inner struct validations run even if it's a empty struct, which means I'd have to use pointers to inner struct types, forcing me to make nil checks throughout my code just to get validate tags that are XOR'd.

@dakojohn dakojohn changed the title required_without and excluded_with don't seem to do anything on nested structs required_without and excluded_with issues with nested structs Oct 22, 2024
@dakojohn
Copy link
Author

Related issues seem to be: #608 and #906

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant