//go:build validatedebug

package validate

import (
	"fmt"
	"runtime"
	"sync"
	"testing"

	"github.com/go-openapi/spec"
)

// This version of the pools is to be used for debugging and testing, with build tag "validatedebug".
//
// In this mode, the pools are tracked for allocation and redemption of borrowed objects, so we can
// verify a few behaviors of the validators. The debug pools panic when an invalid usage pattern is detected.

var pools allPools

func init() {
	resetPools()
}

func resetPools() {
	// NOTE: for testing purpose, we might want to reset pools after calling Validate twice.
	// The pool is corrupted in that case: calling Put twice inserts a duplicate in the pool
	// and further calls to Get are mishandled.

	pools = allPools{
		poolOfSchemaValidators: schemaValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &SchemaValidator{}

					return s
				},
			},
			debugMap:  make(map[*SchemaValidator]status),
			allocMap:  make(map[*SchemaValidator]string),
			redeemMap: make(map[*SchemaValidator]string),
		},
		poolOfObjectValidators: objectValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &objectValidator{}

					return s
				},
			},
			debugMap:  make(map[*objectValidator]status),
			allocMap:  make(map[*objectValidator]string),
			redeemMap: make(map[*objectValidator]string),
		},
		poolOfSliceValidators: sliceValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &schemaSliceValidator{}

					return s
				},
			},
			debugMap:  make(map[*schemaSliceValidator]status),
			allocMap:  make(map[*schemaSliceValidator]string),
			redeemMap: make(map[*schemaSliceValidator]string),
		},
		poolOfItemsValidators: itemsValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &itemsValidator{}

					return s
				},
			},
			debugMap:  make(map[*itemsValidator]status),
			allocMap:  make(map[*itemsValidator]string),
			redeemMap: make(map[*itemsValidator]string),
		},
		poolOfBasicCommonValidators: basicCommonValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &basicCommonValidator{}

					return s
				},
			},
			debugMap:  make(map[*basicCommonValidator]status),
			allocMap:  make(map[*basicCommonValidator]string),
			redeemMap: make(map[*basicCommonValidator]string),
		},
		poolOfHeaderValidators: headerValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &HeaderValidator{}

					return s
				},
			},
			debugMap:  make(map[*HeaderValidator]status),
			allocMap:  make(map[*HeaderValidator]string),
			redeemMap: make(map[*HeaderValidator]string),
		},
		poolOfParamValidators: paramValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &ParamValidator{}

					return s
				},
			},
			debugMap:  make(map[*ParamValidator]status),
			allocMap:  make(map[*ParamValidator]string),
			redeemMap: make(map[*ParamValidator]string),
		},
		poolOfBasicSliceValidators: basicSliceValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &basicSliceValidator{}

					return s
				},
			},
			debugMap:  make(map[*basicSliceValidator]status),
			allocMap:  make(map[*basicSliceValidator]string),
			redeemMap: make(map[*basicSliceValidator]string),
		},
		poolOfNumberValidators: numberValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &numberValidator{}

					return s
				},
			},
			debugMap:  make(map[*numberValidator]status),
			allocMap:  make(map[*numberValidator]string),
			redeemMap: make(map[*numberValidator]string),
		},
		poolOfStringValidators: stringValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &stringValidator{}

					return s
				},
			},
			debugMap:  make(map[*stringValidator]status),
			allocMap:  make(map[*stringValidator]string),
			redeemMap: make(map[*stringValidator]string),
		},
		poolOfSchemaPropsValidators: schemaPropsValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &schemaPropsValidator{}

					return s
				},
			},
			debugMap:  make(map[*schemaPropsValidator]status),
			allocMap:  make(map[*schemaPropsValidator]string),
			redeemMap: make(map[*schemaPropsValidator]string),
		},
		poolOfFormatValidators: formatValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &formatValidator{}

					return s
				},
			},
			debugMap:  make(map[*formatValidator]status),
			allocMap:  make(map[*formatValidator]string),
			redeemMap: make(map[*formatValidator]string),
		},
		poolOfTypeValidators: typeValidatorsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &typeValidator{}

					return s
				},
			},
			debugMap:  make(map[*typeValidator]status),
			allocMap:  make(map[*typeValidator]string),
			redeemMap: make(map[*typeValidator]string),
		},
		poolOfSchemas: schemasPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &spec.Schema{}

					return s
				},
			},
			debugMap:  make(map[*spec.Schema]status),
			allocMap:  make(map[*spec.Schema]string),
			redeemMap: make(map[*spec.Schema]string),
		},
		poolOfResults: resultsPool{
			Pool: &sync.Pool{
				New: func() any {
					s := &Result{}

					return s
				},
			},
			debugMap:  make(map[*Result]status),
			allocMap:  make(map[*Result]string),
			redeemMap: make(map[*Result]string),
		},
	}
}

const (
	statusFresh status = iota + 1
	statusRecycled
	statusRedeemed
)

func (s status) String() string {
	switch s {
	case statusFresh:
		return "fresh"
	case statusRecycled:
		return "recycled"
	case statusRedeemed:
		return "redeemed"
	default:
		panic(fmt.Errorf("invalid status: %d", s))
	}
}

type (
	// Debug
	status uint8

	allPools struct {
		// memory pools for all validator objects.
		//
		// Each pool can be borrowed from and redeemed to.
		poolOfSchemaValidators      schemaValidatorsPool
		poolOfObjectValidators      objectValidatorsPool
		poolOfSliceValidators       sliceValidatorsPool
		poolOfItemsValidators       itemsValidatorsPool
		poolOfBasicCommonValidators basicCommonValidatorsPool
		poolOfHeaderValidators      headerValidatorsPool
		poolOfParamValidators       paramValidatorsPool
		poolOfBasicSliceValidators  basicSliceValidatorsPool
		poolOfNumberValidators      numberValidatorsPool
		poolOfStringValidators      stringValidatorsPool
		poolOfSchemaPropsValidators schemaPropsValidatorsPool
		poolOfFormatValidators      formatValidatorsPool
		poolOfTypeValidators        typeValidatorsPool
		poolOfSchemas               schemasPool
		poolOfResults               resultsPool
	}

	schemaValidatorsPool struct {
		*sync.Pool
		debugMap  map[*SchemaValidator]status
		allocMap  map[*SchemaValidator]string
		redeemMap map[*SchemaValidator]string
		mx        sync.Mutex
	}

	objectValidatorsPool struct {
		*sync.Pool
		debugMap  map[*objectValidator]status
		allocMap  map[*objectValidator]string
		redeemMap map[*objectValidator]string
		mx        sync.Mutex
	}

	sliceValidatorsPool struct {
		*sync.Pool
		debugMap  map[*schemaSliceValidator]status
		allocMap  map[*schemaSliceValidator]string
		redeemMap map[*schemaSliceValidator]string
		mx        sync.Mutex
	}

	itemsValidatorsPool struct {
		*sync.Pool
		debugMap  map[*itemsValidator]status
		allocMap  map[*itemsValidator]string
		redeemMap map[*itemsValidator]string
		mx        sync.Mutex
	}

	basicCommonValidatorsPool struct {
		*sync.Pool
		debugMap  map[*basicCommonValidator]status
		allocMap  map[*basicCommonValidator]string
		redeemMap map[*basicCommonValidator]string
		mx        sync.Mutex
	}

	headerValidatorsPool struct {
		*sync.Pool
		debugMap  map[*HeaderValidator]status
		allocMap  map[*HeaderValidator]string
		redeemMap map[*HeaderValidator]string
		mx        sync.Mutex
	}

	paramValidatorsPool struct {
		*sync.Pool
		debugMap  map[*ParamValidator]status
		allocMap  map[*ParamValidator]string
		redeemMap map[*ParamValidator]string
		mx        sync.Mutex
	}

	basicSliceValidatorsPool struct {
		*sync.Pool
		debugMap  map[*basicSliceValidator]status
		allocMap  map[*basicSliceValidator]string
		redeemMap map[*basicSliceValidator]string
		mx        sync.Mutex
	}

	numberValidatorsPool struct {
		*sync.Pool
		debugMap  map[*numberValidator]status
		allocMap  map[*numberValidator]string
		redeemMap map[*numberValidator]string
		mx        sync.Mutex
	}

	stringValidatorsPool struct {
		*sync.Pool
		debugMap  map[*stringValidator]status
		allocMap  map[*stringValidator]string
		redeemMap map[*stringValidator]string
		mx        sync.Mutex
	}

	schemaPropsValidatorsPool struct {
		*sync.Pool
		debugMap  map[*schemaPropsValidator]status
		allocMap  map[*schemaPropsValidator]string
		redeemMap map[*schemaPropsValidator]string
		mx        sync.Mutex
	}

	formatValidatorsPool struct {
		*sync.Pool
		debugMap  map[*formatValidator]status
		allocMap  map[*formatValidator]string
		redeemMap map[*formatValidator]string
		mx        sync.Mutex
	}

	typeValidatorsPool struct {
		*sync.Pool
		debugMap  map[*typeValidator]status
		allocMap  map[*typeValidator]string
		redeemMap map[*typeValidator]string
		mx        sync.Mutex
	}

	schemasPool struct {
		*sync.Pool
		debugMap  map[*spec.Schema]status
		allocMap  map[*spec.Schema]string
		redeemMap map[*spec.Schema]string
		mx        sync.Mutex
	}

	resultsPool struct {
		*sync.Pool
		debugMap  map[*Result]status
		allocMap  map[*Result]string
		redeemMap map[*Result]string
		mx        sync.Mutex
	}
)

func (p *schemaValidatorsPool) BorrowValidator() *SchemaValidator {
	s := p.Get().(*SchemaValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled schema should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *schemaValidatorsPool) RedeemValidator(s *SchemaValidator) {
	// NOTE: s might be nil. In that case, Put is a noop.
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed schema should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed schema should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *objectValidatorsPool) BorrowValidator() *objectValidator {
	s := p.Get().(*objectValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled object should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *objectValidatorsPool) RedeemValidator(s *objectValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed object should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed object should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *sliceValidatorsPool) BorrowValidator() *schemaSliceValidator {
	s := p.Get().(*schemaSliceValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled schemaSliceValidator should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *sliceValidatorsPool) RedeemValidator(s *schemaSliceValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed schemaSliceValidator should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed schemaSliceValidator should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *itemsValidatorsPool) BorrowValidator() *itemsValidator {
	s := p.Get().(*itemsValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled itemsValidator should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *itemsValidatorsPool) RedeemValidator(s *itemsValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed itemsValidator should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed itemsValidator should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *basicCommonValidatorsPool) BorrowValidator() *basicCommonValidator {
	s := p.Get().(*basicCommonValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled basicCommonValidator should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *basicCommonValidatorsPool) RedeemValidator(s *basicCommonValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed basicCommonValidator should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed basicCommonValidator should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *headerValidatorsPool) BorrowValidator() *HeaderValidator {
	s := p.Get().(*HeaderValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled HeaderValidator should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *headerValidatorsPool) RedeemValidator(s *HeaderValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed header should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed header should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *paramValidatorsPool) BorrowValidator() *ParamValidator {
	s := p.Get().(*ParamValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled param should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *paramValidatorsPool) RedeemValidator(s *ParamValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed param should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed param should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *basicSliceValidatorsPool) BorrowValidator() *basicSliceValidator {
	s := p.Get().(*basicSliceValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled basicSliceValidator should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *basicSliceValidatorsPool) RedeemValidator(s *basicSliceValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed basicSliceValidator should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed basicSliceValidator should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *numberValidatorsPool) BorrowValidator() *numberValidator {
	s := p.Get().(*numberValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled number should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *numberValidatorsPool) RedeemValidator(s *numberValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed number should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed number should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *stringValidatorsPool) BorrowValidator() *stringValidator {
	s := p.Get().(*stringValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled string should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *stringValidatorsPool) RedeemValidator(s *stringValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed string should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed string should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *schemaPropsValidatorsPool) BorrowValidator() *schemaPropsValidator {
	s := p.Get().(*schemaPropsValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled param should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *schemaPropsValidatorsPool) RedeemValidator(s *schemaPropsValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed schemaProps should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed schemaProps should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *formatValidatorsPool) BorrowValidator() *formatValidator {
	s := p.Get().(*formatValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled format should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *formatValidatorsPool) RedeemValidator(s *formatValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed format should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed format should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *typeValidatorsPool) BorrowValidator() *typeValidator {
	s := p.Get().(*typeValidator)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled type should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *typeValidatorsPool) RedeemValidator(s *typeValidator) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed type should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic(fmt.Errorf("redeemed type should have been allocated from a fresh or recycled pointer. Got status %s, already redeamed at: %s", x, p.redeemMap[s]))
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *schemasPool) BorrowSchema() *spec.Schema {
	s := p.Get().(*spec.Schema)

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled spec.Schema should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *schemasPool) RedeemSchema(s *spec.Schema) {
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed spec.Schema should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed spec.Schema should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *resultsPool) BorrowResult() *Result {
	s := p.Get().(*Result).cleared()

	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		p.debugMap[s] = statusFresh
	} else {
		if x != statusRedeemed {
			panic("recycled result should have been redeemed")
		}
		p.debugMap[s] = statusRecycled
	}
	p.allocMap[s] = caller()

	return s
}

func (p *resultsPool) RedeemResult(s *Result) {
	if s == emptyResult {
		if len(s.Errors) > 0 || len(s.Warnings) > 0 {
			panic("empty result should not mutate")
		}
		return
	}
	p.mx.Lock()
	defer p.mx.Unlock()
	x, ok := p.debugMap[s]
	if !ok {
		panic("redeemed Result should have been allocated")
	}
	if x != statusRecycled && x != statusFresh {
		panic("redeemed Result should have been allocated from a fresh or recycled pointer")
	}
	p.debugMap[s] = statusRedeemed
	p.redeemMap[s] = caller()
	p.Put(s)
}

func (p *allPools) allIsRedeemed(t testing.TB) bool {
	outcome := true
	for k, v := range p.poolOfSchemaValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("schemaValidator should be redeemed. Allocated by: %s", p.poolOfSchemaValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfObjectValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("objectValidator should be redeemed. Allocated by: %s", p.poolOfObjectValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfSliceValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("sliceValidator should be redeemed. Allocated by: %s", p.poolOfSliceValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfItemsValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("itemsValidator should be redeemed. Allocated by: %s", p.poolOfItemsValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfBasicCommonValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("basicCommonValidator should be redeemed. Allocated by: %s", p.poolOfBasicCommonValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfHeaderValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("headerValidator should be redeemed. Allocated by: %s", p.poolOfHeaderValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfParamValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("paramValidator should be redeemed. Allocated by: %s", p.poolOfParamValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfBasicSliceValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("basicSliceValidator should be redeemed. Allocated by: %s", p.poolOfBasicSliceValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfNumberValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("numberValidator should be redeemed. Allocated by: %s", p.poolOfNumberValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfStringValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("stringValidator should be redeemed. Allocated by: %s", p.poolOfStringValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfSchemaPropsValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("schemaPropsValidator should be redeemed. Allocated by: %s", p.poolOfSchemaPropsValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfFormatValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("formatValidator should be redeemed. Allocated by: %s", p.poolOfFormatValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfTypeValidators.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("typeValidator should be redeemed. Allocated by: %s", p.poolOfTypeValidators.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfSchemas.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("schemas should be redeemed. Allocated by: %s", p.poolOfSchemas.allocMap[k])
		outcome = false
	}
	for k, v := range p.poolOfResults.debugMap {
		if v == statusRedeemed {
			continue
		}
		t.Logf("result should be redeemed. Allocated by: %s", p.poolOfResults.allocMap[k])
		outcome = false
	}

	return outcome
}

func caller() string {
	pc, _, _, _ := runtime.Caller(3) //nolint:dogsled
	from, line := runtime.FuncForPC(pc).FileLine(pc)

	return fmt.Sprintf("%s:%d", from, line)
}
