diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index ec67258..1722ebc 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -14,4 +14,6 @@ on: jobs: test: uses: go-openapi/ci-workflows/.github/workflows/go-test.yml@befbc52f24215555cb601b0d824b2987c1d49d0b # v0.4.3 + with: + extra-flags: '-tags poolsdebug' secrets: inherit diff --git a/default_validator.go b/default_validator.go index d6da321..010a10f 100644 --- a/default_validator.go +++ b/default_validator.go @@ -17,7 +17,7 @@ type defaultValidator struct { // Validate validates the default values declared in the swagger spec. func (d *defaultValidator) Validate() *Result { - errs := pools.poolOfResults.BorrowResult() // will redeem when merged + errs := validatorPools.results.Borrow() // will redeem when merged if d == nil || d.SpecValidator == nil { return errs @@ -73,7 +73,7 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { // every default value that is specified must validate against the schema for that property // headers, items, parameters, schema - res := pools.poolOfResults.BorrowResult() // will redeem when merged + res := validatorPools.results.Borrow() // will redeem when merged s := d.SpecValidator for method, pathItem := range s.expandedAnalyzer().Operations() { @@ -96,7 +96,7 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -107,7 +107,7 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueItemsDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -118,7 +118,7 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } } @@ -173,7 +173,7 @@ func (d *defaultValidator) validateDefaultInResponse( res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -184,7 +184,7 @@ func (d *defaultValidator) validateDefaultInResponse( res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -206,7 +206,7 @@ func (d *defaultValidator) validateDefaultInResponse( res.addErrorsAt(responsePath(path, method, responseCodeAsStr), defaultValueInDoesNotValidateMsg(operationID, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } return res @@ -218,7 +218,7 @@ func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path pathSegm return nil } d.beingVisited(path) - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() s := d.SpecValidator if schema.Default != nil { @@ -264,7 +264,7 @@ func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path pathSegm // NOTE: Temporary duplicated code. Need to refactor with examples func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() s := d.SpecValidator if items != nil { if items.Default != nil { diff --git a/example_validator.go b/example_validator.go index 07272a7..1f830a6 100644 --- a/example_validator.go +++ b/example_validator.go @@ -22,7 +22,7 @@ type exampleValidator struct { // - individual property // - responses func (ex *exampleValidator) Validate() *Result { - errs := pools.poolOfResults.BorrowResult() + errs := validatorPools.results.Borrow() if ex == nil || ex.SpecValidator == nil { return errs @@ -63,7 +63,7 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { // in: schemas, properties, object, items // not in: headers, parameters without schema - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() s := ex.SpecValidator for method, pathItem := range s.expandedAnalyzer().Operations() { @@ -86,7 +86,7 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueDoesNotValidateMsg(param.Name, param.In)) res.MergeAsWarnings(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -97,7 +97,7 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueItemsDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -108,7 +108,7 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } } @@ -163,7 +163,7 @@ func (ex *exampleValidator) validateExampleInResponse( res.addWarningsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) res.MergeAsWarnings(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -174,7 +174,7 @@ func (ex *exampleValidator) validateExampleInResponse( res.addWarningsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) res.MergeAsWarnings(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -196,7 +196,7 @@ func (ex *exampleValidator) validateExampleInResponse( res.addWarningsAt(responsePath(path, method, responseCodeAsStr), exampleValueInDoesNotValidateMsg(operationID, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } @@ -228,7 +228,7 @@ func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path pathSeg } ex.beingVisited(path) s := ex.SpecValidator - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if schema.Example != nil { res.MergeAsWarnings( @@ -274,7 +274,7 @@ func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path pathSeg // func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() s := ex.SpecValidator if items != nil { if items.Example != nil { diff --git a/formats.go b/formats.go index a050b39..bb58f79 100644 --- a/formats.go +++ b/formats.go @@ -25,7 +25,7 @@ func newFormatValidator(path pathSegments, in, format string, formats strfmt.Reg var f *formatValidator if opts.recycleValidators { - f = pools.poolOfFormatValidators.BorrowValidator() + f = validatorPools.formatValidators.Borrow() } else { f = new(formatValidator) } @@ -67,7 +67,7 @@ func (f *formatValidator) Validate(val any) *Result { var result *Result if f.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -89,5 +89,5 @@ func (f *formatValidator) setPath(path pathSegments) { } func (f *formatValidator) redeem() { - pools.poolOfFormatValidators.RedeemValidator(f) + validatorPools.formatValidators.Redeem(f) } diff --git a/go.mod b/go.mod index 230ce38..6002895 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/go-openapi/swag/fileutils v0.28.0 github.com/go-openapi/swag/jsonutils v0.28.0 github.com/go-openapi/swag/loading v0.28.0 + github.com/go-openapi/swag/pools v0.28.0 github.com/go-openapi/swag/stringutils v0.28.0 github.com/go-openapi/testify/v2 v2.6.0 go.yaml.in/yaml/v3 v3.0.5 @@ -19,7 +20,6 @@ require ( require ( github.com/go-openapi/jsonreference v1.0.0 // indirect github.com/go-openapi/swag/mangling v0.28.0 // indirect - github.com/go-openapi/swag/pools v0.28.0 // indirect github.com/go-openapi/swag/typeutils v0.28.0 // indirect github.com/go-openapi/swag/yamlutils v0.28.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect diff --git a/helpers.go b/helpers.go index 027f134..b8f331e 100644 --- a/helpers.go +++ b/helpers.go @@ -160,7 +160,7 @@ func (h *errorHelper) sErr(err errors.Error, recycle bool) *Result { func (h *errorHelper) sErrAt(at pathSegments, err errors.Error, recycle bool) *Result { var result *Result if recycle { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } diff --git a/object_validator.go b/object_validator.go index 2cc8dce..2d14557 100644 --- a/object_validator.go +++ b/object_validator.go @@ -37,7 +37,7 @@ func newObjectValidator(path pathSegments, in string, var v *objectValidator if opts.recycleValidators { - v = pools.poolOfObjectValidators.BorrowValidator() + v = validatorPools.objectValidators.Borrow() } else { v = new(objectValidator) } @@ -83,7 +83,7 @@ func (o *objectValidator) Validate(data any) *Result { var res *Result if o.Options.recycleResult { - res = pools.poolOfResults.BorrowResult() + res = validatorPools.results.Borrow() } else { res = new(Result) } @@ -328,9 +328,9 @@ func (o *objectValidator) validatePropertiesSchema(val map[string]any, res *Resu // Property types: // - regular Property - pSchema := pools.poolOfSchemas.BorrowSchema() // recycle a spec.Schema object which lifespan extends only to the validation of properties + pSchema := validatorPools.schemas.Borrow() // recycle a spec.Schema object which lifespan extends only to the validation of properties defer func() { - pools.poolOfSchemas.RedeemSchema(pSchema) + validatorPools.schemas.Redeem(pSchema) }() for pName := range o.Properties { @@ -387,9 +387,9 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result succeededOnce := false patterns := make([]string, 0, len(o.PatternProperties)) - schema := pools.poolOfSchemas.BorrowSchema() + schema := validatorPools.schemas.Borrow() defer func() { - pools.poolOfSchemas.RedeemSchema(schema) + validatorPools.schemas.Redeem(schema) }() for k := range o.PatternProperties { @@ -420,5 +420,5 @@ func (o *objectValidator) setPath(path pathSegments) { } func (o *objectValidator) redeem() { - pools.poolOfObjectValidators.RedeemValidator(o) + validatorPools.objectValidators.Redeem(o) } diff --git a/pools.go b/pools.go index c8936bd..0bc95d1 100644 --- a/pools.go +++ b/pools.go @@ -1,369 +1,87 @@ // SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 -//go:build !validatedebug - package validate import ( - "sync" - "github.com/go-openapi/spec" + "github.com/go-openapi/swag/pools" ) -var pools allPools +// validatorPools recycles the objects allocated while validating. +// +// Validation allocates a validator per schema node and a result per check, so +// the same handful of types are built and thrown away constantly. Recycling +// them is what keeps validating a large specification affordable. +// +// Build with the "poolsdebug" tag to have every borrow and redeem tracked: +// misuse then panics where it happens rather than corrupting a pool, and +// [pools.AssertNoLeaks] reports what was borrowed and never given back. +var validatorPools allPools func init() { resetPools() } +// resetPools builds a fresh set of pools. +// +// Recycling an object twice leaves a pool holding it twice, and it would then +// be handed to two borrowers at once. A test that provokes such misuse has to +// start the next one from clean pools. 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 - }, - }, - }, - poolOfObjectValidators: objectValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &objectValidator{} - - return s - }, - }, - }, - poolOfSliceValidators: sliceValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &schemaSliceValidator{} - - return s - }, - }, - }, - poolOfItemsValidators: itemsValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &itemsValidator{} - - return s - }, - }, - }, - poolOfBasicCommonValidators: basicCommonValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &basicCommonValidator{} - - return s - }, - }, - }, - poolOfHeaderValidators: headerValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &HeaderValidator{} - - return s - }, - }, - }, - poolOfParamValidators: paramValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &ParamValidator{} - - return s - }, - }, - }, - poolOfBasicSliceValidators: basicSliceValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &basicSliceValidator{} - - return s - }, - }, - }, - poolOfNumberValidators: numberValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &numberValidator{} - - return s - }, - }, - }, - poolOfStringValidators: stringValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &stringValidator{} - - return s - }, - }, - }, - poolOfSchemaPropsValidators: schemaPropsValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &schemaPropsValidator{} - - return s - }, - }, - }, - poolOfFormatValidators: formatValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &formatValidator{} - - return s - }, - }, - }, - poolOfTypeValidators: typeValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &typeValidator{} - - return s - }, - }, - }, - poolOfSchemas: schemasPool{ - Pool: &sync.Pool{ - New: func() any { - s := &spec.Schema{} - - return s - }, - }, - }, - poolOfResults: resultsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &Result{} - - return s - }, - }, - }, - } -} - -type ( - 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 - } - - objectValidatorsPool struct { - *sync.Pool - } - - sliceValidatorsPool struct { - *sync.Pool - } - - itemsValidatorsPool struct { - *sync.Pool - } - - basicCommonValidatorsPool struct { - *sync.Pool - } - - headerValidatorsPool struct { - *sync.Pool - } - - paramValidatorsPool struct { - *sync.Pool - } - - basicSliceValidatorsPool struct { - *sync.Pool - } - - numberValidatorsPool struct { - *sync.Pool - } - - stringValidatorsPool struct { - *sync.Pool - } - - schemaPropsValidatorsPool struct { - *sync.Pool - } - - formatValidatorsPool struct { - *sync.Pool - } - - typeValidatorsPool struct { - *sync.Pool - } - - schemasPool struct { - *sync.Pool - } - - resultsPool struct { - *sync.Pool - } -) - -func (p schemaValidatorsPool) BorrowValidator() *SchemaValidator { - return p.Get().(*SchemaValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p schemaValidatorsPool) RedeemValidator(s *SchemaValidator) { - // NOTE: s might be nil. In that case, Put is a noop. - p.Put(s) -} - -func (p objectValidatorsPool) BorrowValidator() *objectValidator { - return p.Get().(*objectValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p objectValidatorsPool) RedeemValidator(s *objectValidator) { - p.Put(s) -} - -func (p sliceValidatorsPool) BorrowValidator() *schemaSliceValidator { - return p.Get().(*schemaSliceValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p sliceValidatorsPool) RedeemValidator(s *schemaSliceValidator) { - p.Put(s) -} - -func (p itemsValidatorsPool) BorrowValidator() *itemsValidator { - return p.Get().(*itemsValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p itemsValidatorsPool) RedeemValidator(s *itemsValidator) { - p.Put(s) -} - -func (p basicCommonValidatorsPool) BorrowValidator() *basicCommonValidator { - return p.Get().(*basicCommonValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p basicCommonValidatorsPool) RedeemValidator(s *basicCommonValidator) { - p.Put(s) -} - -func (p headerValidatorsPool) BorrowValidator() *HeaderValidator { - return p.Get().(*HeaderValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p headerValidatorsPool) RedeemValidator(s *HeaderValidator) { - p.Put(s) -} - -func (p paramValidatorsPool) BorrowValidator() *ParamValidator { - return p.Get().(*ParamValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p paramValidatorsPool) RedeemValidator(s *ParamValidator) { - p.Put(s) -} - -func (p basicSliceValidatorsPool) BorrowValidator() *basicSliceValidator { - return p.Get().(*basicSliceValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p basicSliceValidatorsPool) RedeemValidator(s *basicSliceValidator) { - p.Put(s) -} - -func (p numberValidatorsPool) BorrowValidator() *numberValidator { - return p.Get().(*numberValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p numberValidatorsPool) RedeemValidator(s *numberValidator) { - p.Put(s) -} - -func (p stringValidatorsPool) BorrowValidator() *stringValidator { - return p.Get().(*stringValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p stringValidatorsPool) RedeemValidator(s *stringValidator) { - p.Put(s) -} - -func (p schemaPropsValidatorsPool) BorrowValidator() *schemaPropsValidator { - return p.Get().(*schemaPropsValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p schemaPropsValidatorsPool) RedeemValidator(s *schemaPropsValidator) { - p.Put(s) -} - -func (p formatValidatorsPool) BorrowValidator() *formatValidator { - return p.Get().(*formatValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p formatValidatorsPool) RedeemValidator(s *formatValidator) { - p.Put(s) -} - -func (p typeValidatorsPool) BorrowValidator() *typeValidator { - return p.Get().(*typeValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p typeValidatorsPool) RedeemValidator(s *typeValidator) { - p.Put(s) -} - -func (p schemasPool) BorrowSchema() *spec.Schema { - return p.Get().(*spec.Schema) //nolint:forcetypeassert // pool New always returns this type -} - -func (p schemasPool) RedeemSchema(s *spec.Schema) { - p.Put(s) -} - -func (p resultsPool) BorrowResult() *Result { - return p.Get().(*Result).cleared() //nolint:forcetypeassert // pool New always returns *Result -} - -func (p resultsPool) RedeemResult(s *Result) { - if s == emptyResult { + validatorPools = allPools{ + schemaValidators: pools.New[SchemaValidator](), + objectValidators: pools.New[objectValidator](), + sliceValidators: pools.New[schemaSliceValidator](), + itemsValidators: pools.New[itemsValidator](), + basicCommonValidators: pools.New[basicCommonValidator](), + headerValidators: pools.New[HeaderValidator](), + paramValidators: pools.New[ParamValidator](), + basicSliceValidators: pools.New[basicSliceValidator](), + numberValidators: pools.New[numberValidator](), + stringValidators: pools.New[stringValidator](), + schemaPropsValidators: pools.New[schemaPropsValidator](), + formatValidators: pools.New[formatValidator](), + typeValidators: pools.New[typeValidator](), + schemas: pools.New[spec.Schema](), + results: pools.New[Result](), + } +} + +// allPools is the set of pools shared by the validators of this package. +type allPools struct { + schemaValidators *pools.Pool[SchemaValidator] + objectValidators *pools.Pool[objectValidator] + sliceValidators *pools.Pool[schemaSliceValidator] + itemsValidators *pools.Pool[itemsValidator] + basicCommonValidators *pools.Pool[basicCommonValidator] + headerValidators *pools.Pool[HeaderValidator] + paramValidators *pools.Pool[ParamValidator] + basicSliceValidators *pools.Pool[basicSliceValidator] + numberValidators *pools.Pool[numberValidator] + stringValidators *pools.Pool[stringValidator] + schemaPropsValidators *pools.Pool[schemaPropsValidator] + formatValidators *pools.Pool[formatValidator] + typeValidators *pools.Pool[typeValidator] + schemas *pools.Pool[spec.Schema] + results *pools.Pool[Result] +} + +// redeemResult returns a result to the pool. +// +// emptyResult is a shared value that was never borrowed, so it is not the +// pool's to take back: handing it over would be reported as a foreign redeem, +// rightly. +// +// Results are borrowed straight from the pool rather than through a helper, +// so that the instrumented build attributes a leak to the code that borrowed it. +// +// This wrapper costs that attribution on redeem, where a double redeem still +// names the offending call site in the panic it raises. +func redeemResult(r *Result) { + if r == emptyResult { return } - p.Put(s) + + validatorPools.results.Redeem(r) } diff --git a/pools_debug.go b/pools_debug.go deleted file mode 100644 index d123ed4..0000000 --- a/pools_debug.go +++ /dev/null @@ -1,1015 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers -// SPDX-License-Identifier: Apache-2.0 - -//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) -} diff --git a/pools_debug_test.go b/pools_debug_test.go deleted file mode 100644 index 9ca8a59..0000000 --- a/pools_debug_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers -// SPDX-License-Identifier: Apache-2.0 - -//go:build validatedebug - -package validate - -import ( - "path/filepath" - "testing" - - "github.com/go-openapi/loads" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/testify/v2/require" -) - -func Test_Debug_2866(t *testing.T) { - // This test to be run with build flag "validatedebug": it uses the debug pools and asserts that - // all allocated objects are indeed redeemed at the end of the spec validation. - - resetPools() - fp := filepath.Join("fixtures", "bugs", "2866", "2866.yaml") - - doc, err := loads.Spec(fp) - require.NoError(t, err) - require.NotNil(t, doc) - - require.NoError(t, Spec(doc, strfmt.Default)) - - require.True(t, pools.allIsRedeemed(t)) -} diff --git a/pools_leak_test.go b/pools_leak_test.go new file mode 100644 index 0000000..b6cbd5a --- /dev/null +++ b/pools_leak_test.go @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "encoding/json" + "path/filepath" + "testing" + + "github.com/go-openapi/loads" + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag/pools" + "github.com/go-openapi/testify/v2/require" +) + +// TestPools_NoLeakOnSpecValidation asserts that validating a specification +// gives back every object it borrowed. +// +// The assertion only bites when built with the "poolsdebug" tag, which turns +// on borrow tracking; without it the test still runs the validation, and the +// pools panic on nothing. Run the instrumented build with: +// +// go test -tags poolsdebug ./... +func TestPools_NoLeakOnSpecValidation(t *testing.T) { + resetPools() + pools.ResetTracking() + t.Cleanup(pools.ResetTracking) + + fp := filepath.Join("fixtures", "bugs", "2866", "2866.yaml") + doc, err := loads.Spec(fp) + require.NoError(t, err) + require.NotNil(t, doc) + + require.NoError(t, Spec(doc, strfmt.Default)) + + require.True(t, pools.AssertNoLeaks(t)) +} + +// TestPools_NoLeakOnSchemaValidation covers the other entry point that +// recycles: validating data against a schema, rather than a whole spec. +func TestPools_NoLeakOnSchemaValidation(t *testing.T) { + resetPools() + pools.ResetTracking() + t.Cleanup(pools.ResetTracking) + + schema := new(spec.Schema) + require.NoError(t, json.Unmarshal([]byte(`{ + "type": "object", + "required": ["name"], + "properties": { + "name": {"type": "string"}, + "friends": {"type": "array", "items": {"type": "object", "required": ["age"]}}, + "either": {"oneOf": [{"type": "string"}, {"type": "integer"}]} + } + }`), schema)) + + const friendsProp = "friends" + + for _, data := range []any{ + map[string]any{nameProp: "ok", friendsProp: []any{map[string]any{"age": 1}}, "either": "s"}, + map[string]any{friendsProp: []any{map[string]any{}}, "either": true}, + nil, + } { + _ = AgainstSchema(schema, data, strfmt.Default) + } + + require.True(t, pools.AssertNoLeaks(t)) +} + +// TestPools_NoLeakOnNilSchema exercises the path that hands back the shared +// empty result, which the pool must not be given. +func TestPools_NoLeakOnNilSchema(t *testing.T) { + resetPools() + pools.ResetTracking() + t.Cleanup(pools.ResetTracking) + + require.NoError(t, AgainstSchema(nil, map[string]any{}, strfmt.Default)) + require.True(t, pools.AssertNoLeaks(t)) +} diff --git a/pools_test.go b/pools_test.go index a6bc48f..1e27631 100644 --- a/pools_test.go +++ b/pools_test.go @@ -1,3 +1,5 @@ +//go:build !windows && !darwin + // SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 @@ -14,6 +16,10 @@ import ( ) func Test_ParallelPool(t *testing.T) { + // Hitting more stringent memory & threads constraints on windows, when running -race, so we disable this on + // that target OS. Typically, a CI runner breaks with "ThreadSanitizer failed to allocate ..." + // Also -race in this context times out on macos. We need our validation on our platform only. + fixture1 := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml") fixture2 := filepath.Join("fixtures", "bugs", "2866", "2866.yaml") fixture3 := filepath.Join("fixtures", "bugs", "43", "fixture-43.yaml") diff --git a/result.go b/result.go index fc6f8e7..a4834b4 100644 --- a/result.go +++ b/result.go @@ -134,7 +134,7 @@ func (r *Result) Merge(others ...*Result) *Result { r.mergeWithoutRootSchemata(other) r.rootObjectSchemata.Append(other.rootObjectSchemata) if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } } return r @@ -201,7 +201,7 @@ func (r *Result) MergeAsErrors(others ...*Result) *Result { r.carryErrors(other.Warnings, other.warningLocations) r.MatchCount += other.MatchCount if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } } } @@ -219,7 +219,7 @@ func (r *Result) MergeAsWarnings(others ...*Result) *Result { r.carryWarnings(other.Warnings, other.warningLocations) r.MatchCount += other.MatchCount if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } } } @@ -443,7 +443,7 @@ func (r *Result) mergeForField(obj map[string]any, field string, other *Result) }) } if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } return r @@ -471,7 +471,7 @@ func (r *Result) mergeForSlice(slice reflect.Value, i int, other *Result) *Resul } if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } return r @@ -574,7 +574,7 @@ func (r *Result) keepRelevantErrors() *Result { } var strippedResult *Result if r.wantsRedeemOnMerge { - strippedResult = pools.poolOfResults.BorrowResult() + strippedResult = validatorPools.results.Borrow() } else { strippedResult = new(Result) } @@ -585,6 +585,14 @@ func (r *Result) keepRelevantErrors() *Result { return strippedResult } +// Reset clears this result so it may be reused, keeping allocated capacity. +// +// It implements the hook the pool calls when a result is borrowed and when it +// is redeemed. Calling it on a result still in use loses its findings. +func (r *Result) Reset() { + _ = r.cleared() +} + func (r *Result) cleared() *Result { // clear the Result to be reusable. Keep allocated capacity. r.Errors = r.Errors[:0] diff --git a/result_location_test.go b/result_location_test.go index 11d209b..6ccb7c1 100644 --- a/result_location_test.go +++ b/result_location_test.go @@ -97,7 +97,7 @@ func TestResultLocations_DedupeKeepsTheFirstLocation(t *testing.T) { func TestResultLocations_ClearedOnRecycle(t *testing.T) { t.Parallel() - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() res.addErrorsAt(newPathSegments("a"), errOne) require.Len(t, res.LocatedErrors(), 1) diff --git a/schema.go b/schema.go index 2dfa39a..e7af892 100644 --- a/schema.go +++ b/schema.go @@ -42,7 +42,7 @@ func AgainstSchema(schema *spec.Schema, data any, formats strfmt.Registry, optio append(options, WithRecycleValidators(true), withRecycleResults(true))..., ).Validate(data) defer func() { - pools.poolOfResults.RedeemResult(res) + redeemResult(res) }() if res.HasErrors() { @@ -100,7 +100,7 @@ func newSchemaValidator(schema *spec.Schema, rootSchema any, root pathSegments, var s *SchemaValidator if opts.recycleValidators { - s = pools.poolOfSchemaValidators.BorrowValidator() + s = validatorPools.schemaValidators.Borrow() } else { s = new(SchemaValidator) } @@ -158,7 +158,7 @@ func (s *SchemaValidator) Validate(data any) *Result { var result *Result if s.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() result.data = data } else { result = &Result{data: data} @@ -366,7 +366,7 @@ func (s *SchemaValidator) setPath(path pathSegments) { } func (s *SchemaValidator) redeem() { - pools.poolOfSchemaValidators.RedeemValidator(s) + validatorPools.schemaValidators.Redeem(s) } func (s *SchemaValidator) redeemChildren() { diff --git a/schema_props.go b/schema_props.go index 08a2030..83a3735 100644 --- a/schema_props.go +++ b/schema_props.go @@ -56,7 +56,7 @@ func newSchemaPropsValidator( var s *schemaPropsValidator if opts.recycleValidators { - s = pools.poolOfSchemaPropsValidators.BorrowValidator() + s = validatorPools.schemaPropsValidators.Borrow() } else { s = new(schemaPropsValidator) } @@ -87,7 +87,7 @@ func (s *schemaPropsValidator) Applies(source any, _ reflect.Kind) bool { func (s *schemaPropsValidator) Validate(data any) *Result { var mainResult *Result if s.Options.recycleResult { - mainResult = pools.poolOfResults.BorrowResult() + mainResult = validatorPools.results.Borrow() } else { mainResult = new(Result) } @@ -107,17 +107,17 @@ func (s *schemaPropsValidator) Validate(data any) *Result { } if len(s.anyOfValidators) > 0 { - keepResultAnyOf = pools.poolOfResults.BorrowResult() + keepResultAnyOf = validatorPools.results.Borrow() s.validateAnyOf(data, mainResult, keepResultAnyOf) } if len(s.oneOfValidators) > 0 { - keepResultOneOf = pools.poolOfResults.BorrowResult() + keepResultOneOf = validatorPools.results.Borrow() s.validateOneOf(data, mainResult, keepResultOneOf) } if len(s.allOfValidators) > 0 { - keepResultAllOf = pools.poolOfResults.BorrowResult() + keepResultAllOf = validatorPools.results.Borrow() s.validateAllOf(data, mainResult, keepResultAllOf) } @@ -150,7 +150,7 @@ func (s *schemaPropsValidator) validateAnyOf(data any, mainResult, keepResultAny if result.IsValid() { if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } _ = keepResultAnyOf.cleared() @@ -162,7 +162,7 @@ func (s *schemaPropsValidator) validateAnyOf(data any, mainResult, keepResultAny // MatchCount is used to select errors from the schema with most positive checks if bestFailures == nil || result.MatchCount > bestFailures.MatchCount { if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } bestFailures = result @@ -170,7 +170,7 @@ func (s *schemaPropsValidator) validateAnyOf(data any, mainResult, keepResultAny } if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } } @@ -201,7 +201,7 @@ func (s *schemaPropsValidator) validateOneOf(data any, mainResult, keepResultOne if firstSuccess == nil { firstSuccess = result } else if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } continue @@ -210,11 +210,11 @@ func (s *schemaPropsValidator) validateOneOf(data any, mainResult, keepResultOne // MatchCount is used to select errors from the schema with most positive checks if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) { if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } bestFailures = result } else if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } } @@ -226,13 +226,13 @@ func (s *schemaPropsValidator) validateOneOf(data any, mainResult, keepResultOne case 1: mainResult.Merge(firstSuccess) if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } default: mainResult.addErrorsAt(s.Path, mustValidateOnlyOneSchemaMsg(s.Path.dotted(), fmt.Sprintf("Found %d valid alternatives", validated))) mainResult.Merge(bestFailures) if firstSuccess != nil && firstSuccess.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(firstSuccess) + redeemResult(firstSuccess) } } } @@ -273,7 +273,7 @@ func (s *schemaPropsValidator) validateNot(data any, mainResult *Result) { mainResult.addErrorsAt(s.Path, mustNotValidatechemaMsg(s.Path.dotted())) } if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } } @@ -307,7 +307,7 @@ func (s *schemaPropsValidator) setPath(path pathSegments) { } func (s *schemaPropsValidator) redeem() { - pools.poolOfSchemaPropsValidators.RedeemValidator(s) + validatorPools.schemaPropsValidators.Redeem(s) } func (s *schemaPropsValidator) redeemChildren() { diff --git a/slice_validator.go b/slice_validator.go index 3445dad..0a0ee74 100644 --- a/slice_validator.go +++ b/slice_validator.go @@ -34,7 +34,7 @@ func newSliceValidator(path pathSegments, in string, var v *schemaSliceValidator if opts.recycleValidators { - v = pools.poolOfSliceValidators.BorrowValidator() + v = validatorPools.sliceValidators.Borrow() } else { v = new(schemaSliceValidator) } @@ -68,7 +68,7 @@ func (s *schemaSliceValidator) Validate(data any) *Result { var result *Result if s.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -137,5 +137,5 @@ func (s *schemaSliceValidator) setPath(path pathSegments) { } func (s *schemaSliceValidator) redeem() { - pools.poolOfSliceValidators.RedeemValidator(s) + validatorPools.sliceValidators.Redeem(s) } diff --git a/spec.go b/spec.go index 5568d38..f2ced4c 100644 --- a/spec.go +++ b/spec.go @@ -178,7 +178,7 @@ func (s *SpecValidator) SetContinueOnErrors(c bool) { } func (s *SpecValidator) validateNonEmptyPathParamNames() *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if s.spec.Spec().Paths == nil { // There is no Paths object: error res.addErrorsAt(newPathSegments(swaggerPaths), noValidPathMsg()) @@ -212,7 +212,7 @@ func (s *SpecValidator) validateDuplicateOperationIDs() *Result { // fallback on possible incomplete picture because of previous errors analyzer = s.analyzer } - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() known := make(map[string]int) for _, v := range analyzer.OperationIDs() { if v != "" { @@ -234,7 +234,7 @@ type dupProp struct { func (s *SpecValidator) validateDuplicatePropertyNames() *Result { // definition can't declare a property that's already defined by one of its ancestors - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for k, sch := range s.spec.Spec().Definitions { if len(sch.AllOf) == 0 { continue @@ -283,7 +283,7 @@ func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, schn := nm schc := &sch - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for schc.Ref.String() != "" { // gather property names @@ -320,7 +320,7 @@ func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, } func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there return nil, res @@ -371,7 +371,7 @@ func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, kno //nolint:gocognit // refactor in a forthcoming PR func (s *SpecValidator) validateItems() *Result { // validate parameter, items, schema and response objects for presence of item if type is array - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for method, pi := range s.analyzer.Operations() { for path, op := range pi { @@ -436,7 +436,7 @@ func (s *SpecValidator) validateItems() *Result { // Verifies constraints on array type. func (s *SpecValidator) validateSchemaItems(schema spec.Schema, at pathSegments, prefix, opID string) *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if !schema.Type.Contains(arrayType) { return res } @@ -460,7 +460,7 @@ func (s *SpecValidator) validateSchemaItems(schema spec.Schema, at pathSegments, func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result { // Each defined operation path parameters must correspond to a named element in the API's path pattern. // (For example, you cannot have a path parameter named id for the following path /pets/{petId} but you must have a path parameter named petId.) - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for _, l := range fromPath { var matched bool for _, r := range fromOperation { @@ -513,7 +513,7 @@ func (s *SpecValidator) validateReferencedParameters() *Result { if len(expected) == 0 { return nil } - result := pools.poolOfResults.BorrowResult() + result := validatorPools.results.Borrow() for k := range expected { result.addWarningsAt(localRefPath(k), unusedParamMsg(k)) } @@ -538,7 +538,7 @@ func (s *SpecValidator) validateReferencedResponses() *Result { if len(expected) == 0 { return nil } - result := pools.poolOfResults.BorrowResult() + result := validatorPools.results.Borrow() for k := range expected { result.addWarningsAt(localRefPath(k), unusedResponseMsg(k)) } @@ -573,7 +573,7 @@ func (s *SpecValidator) validateReferencedDefinitions() *Result { func (s *SpecValidator) validateRequiredDefinitions() *Result { // Each property listed in the required array must be defined in the properties of the model - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() DEFINITIONS: for d, schema := range s.spec.Spec().Definitions { @@ -607,7 +607,7 @@ func (s *SpecValidator) validateRequiredProperties( path, in string, schemaAt, requiredAt pathSegments, v *spec.Schema, ) *Result { // Takes care of recursive property definitions, which may be nested in additionalProperties schemas - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() propertyMatch := false patternMatch := false additionalPropertiesMatch := false @@ -674,7 +674,7 @@ func (s *SpecValidator) validateParameters() *Result { // - parameters with pattern property must specify valid patterns // - $ref in parameters must resolve // - path param must be required - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`) for method, pi := range s.expandedAnalyzer().Operations() { methodPaths := make(map[string]map[string]string) @@ -816,7 +816,7 @@ func (s *SpecValidator) validateParameters() *Result { func (s *SpecValidator) validateReferencesValid() *Result { // each reference must point to a valid object - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for _, r := range s.analyzer.AllRefs() { if !r.IsValidURI(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI res.addErrorsAt(s.refLocations.at(r.String()), invalidRefMsg(r.String())) @@ -845,7 +845,7 @@ func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operatio // However, there are some issues with such a factorization: // - analysis does not seem to fully expand params // - param keys may be altered by x-go-name - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() pnames := make(map[string]struct{}) if op.Parameters != nil { // Safeguard diff --git a/spec_ref_warnings.go b/spec_ref_warnings.go index a499a64..3c8608f 100644 --- a/spec_ref_warnings.go +++ b/spec_ref_warnings.go @@ -34,7 +34,7 @@ const minDistinctHostsToWarn = 2 // // All findings are warnings: they do not affect validity (see Result.IsValid). func (s *SpecValidator) validateDubiousRefs() *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() baseDir, hasBase := s.localBaseDir() diff --git a/type.go b/type.go index 9baa3d6..3016783 100644 --- a/type.go +++ b/type.go @@ -30,7 +30,7 @@ func newTypeValidator(path pathSegments, in string, typ spec.StringOrArray, null var t *typeValidator if opts.recycleValidators { - t = pools.poolOfTypeValidators.BorrowValidator() + t = validatorPools.typeValidators.Borrow() } else { t = new(typeValidator) } @@ -202,5 +202,5 @@ func (t *typeValidator) setPath(path pathSegments) { } func (t *typeValidator) redeem() { - pools.poolOfTypeValidators.RedeemValidator(t) + validatorPools.typeValidators.Redeem(t) } diff --git a/validator.go b/validator.go index 46a32ca..3989b21 100644 --- a/validator.go +++ b/validator.go @@ -39,7 +39,7 @@ func newItemsValidator(path pathSegments, in string, items *spec.Items, root any var iv *itemsValidator if opts.recycleValidators { - iv = pools.poolOfItemsValidators.BorrowValidator() + iv = validatorPools.itemsValidators.Borrow() } else { iv = new(itemsValidator) } @@ -73,7 +73,7 @@ func (i *itemsValidator) Validate(index int, data any) *Result { kind := tpe.Kind() var result *Result if i.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -193,7 +193,7 @@ func (i *itemsValidator) formatValidator() valueValidator { } func (i *itemsValidator) redeem() { - pools.poolOfItemsValidators.RedeemValidator(i) + validatorPools.itemsValidators.Redeem(i) } func (i *itemsValidator) redeemChildren() { @@ -226,7 +226,7 @@ func newBasicCommonValidator(path pathSegments, in string, def any, enum []any, var b *basicCommonValidator if opts.recycleValidators { - b = pools.poolOfBasicCommonValidators.BorrowValidator() + b = validatorPools.basicCommonValidators.Borrow() } else { b = new(basicCommonValidator) } @@ -282,7 +282,7 @@ func (b *basicCommonValidator) setPath(path pathSegments) { } func (b *basicCommonValidator) redeem() { - pools.poolOfBasicCommonValidators.RedeemValidator(b) + validatorPools.basicCommonValidators.Redeem(b) } // A HeaderValidator has very limited subset of validations to apply. @@ -311,7 +311,7 @@ func newHeaderValidator(name string, header *spec.Header, formats strfmt.Registr var p *HeaderValidator if opts.recycleValidators { - p = pools.poolOfHeaderValidators.BorrowValidator() + p = validatorPools.headerValidators.Borrow() } else { p = new(HeaderValidator) } @@ -354,7 +354,7 @@ func (p *HeaderValidator) Validate(data any) *Result { var result *Result if p.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -460,7 +460,7 @@ func (p *HeaderValidator) formatValidator() valueValidator { } func (p *HeaderValidator) redeem() { - pools.poolOfHeaderValidators.RedeemValidator(p) + validatorPools.headerValidators.Redeem(p) } func (p *HeaderValidator) redeemChildren() { @@ -503,7 +503,7 @@ func newParamValidator(param *spec.Parameter, formats strfmt.Registry, opts *Sch var p *ParamValidator if opts.recycleValidators { - p = pools.poolOfParamValidators.BorrowValidator() + p = validatorPools.paramValidators.Borrow() } else { p = new(ParamValidator) } @@ -538,7 +538,7 @@ func (p *ParamValidator) Validate(data any) *Result { var result *Result if p.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -652,7 +652,7 @@ func (p *ParamValidator) formatValidator() valueValidator { } func (p *ParamValidator) redeem() { - pools.poolOfParamValidators.RedeemValidator(p) + validatorPools.paramValidators.Redeem(p) } func (p *ParamValidator) redeemChildren() { @@ -695,7 +695,7 @@ func newBasicSliceValidator( var s *basicSliceValidator if opts.recycleValidators { - s = pools.poolOfBasicSliceValidators.BorrowValidator() + s = validatorPools.basicSliceValidators.Borrow() } else { s = new(basicSliceValidator) } @@ -762,7 +762,7 @@ func (s *basicSliceValidator) Validate(data any) *Result { return err } if err.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(err) + redeemResult(err) } } } @@ -775,7 +775,7 @@ func (s *basicSliceValidator) setPath(path pathSegments) { } func (s *basicSliceValidator) redeem() { - pools.poolOfBasicSliceValidators.RedeemValidator(s) + validatorPools.basicSliceValidators.Redeem(s) } type numberValidator struct { @@ -805,7 +805,7 @@ func newNumberValidator( var n *numberValidator if opts.recycleValidators { - n = pools.poolOfNumberValidators.BorrowValidator() + n = validatorPools.numberValidators.Borrow() } else { n = new(numberValidator) } @@ -866,7 +866,7 @@ func (n *numberValidator) Validate(val any) *Result { var res, resMultiple, resMinimum, resMaximum *Result if n.Options.recycleResult { - res = pools.poolOfResults.BorrowResult() + res = validatorPools.results.Borrow() } else { res = new(Result) } @@ -879,7 +879,7 @@ func (n *numberValidator) Validate(val any) *Result { res.addErrorsAt(n.Path, IsValueValidAgainstRange(val, n.Type, n.Format, "Checked", n.Path.dotted())) if n.MultipleOf != nil { - resMultiple = pools.poolOfResults.BorrowResult() + resMultiple = validatorPools.results.Borrow() // Is the constraint specifier within the range of the specific numeric type and format? resMultiple.addErrorsAt(n.Path, IsValueValidAgainstRange(*n.MultipleOf, n.Type, n.Format, "MultipleOf", n.Path.dotted())) @@ -897,7 +897,7 @@ func (n *numberValidator) Validate(val any) *Result { } if n.Maximum != nil { - resMaximum = pools.poolOfResults.BorrowResult() + resMaximum = validatorPools.results.Borrow() // Is the constraint specifier within the range of the specific numeric type and format? resMaximum.addErrorsAt(n.Path, IsValueValidAgainstRange(*n.Maximum, n.Type, n.Format, "Maximum boundary", n.Path.dotted())) @@ -915,7 +915,7 @@ func (n *numberValidator) Validate(val any) *Result { } if n.Minimum != nil { - resMinimum = pools.poolOfResults.BorrowResult() + resMinimum = validatorPools.results.Borrow() // Is the constraint specifier within the range of the specific numeric type and format? resMinimum.addErrorsAt(n.Path, IsValueValidAgainstRange(*n.Minimum, n.Type, n.Format, "Minimum boundary", n.Path.dotted())) @@ -942,7 +942,7 @@ func (n *numberValidator) setPath(path pathSegments) { } func (n *numberValidator) redeem() { - pools.poolOfNumberValidators.RedeemValidator(n) + validatorPools.numberValidators.Redeem(n) } type stringValidator struct { @@ -968,7 +968,7 @@ func newStringValidator( var s *stringValidator if opts.recycleValidators { - s = pools.poolOfStringValidators.BorrowValidator() + s = validatorPools.stringValidators.Borrow() } else { s = new(stringValidator) } @@ -1038,5 +1038,5 @@ func (s *stringValidator) setPath(path pathSegments) { } func (s *stringValidator) redeem() { - pools.poolOfStringValidators.RedeemValidator(s) + validatorPools.stringValidators.Redeem(s) } diff --git a/validator_test.go b/validator_test.go index 1d85741..13914ec 100644 --- a/validator_test.go +++ b/validator_test.go @@ -48,7 +48,7 @@ func TestHeaderValidator(t *testing.T) { require.NotNil(t, res) require.Empty(t, res.Errors) require.TrueT(t, res.wantsRedeemOnMerge) - pools.poolOfResults.RedeemResult(res) + redeemResult(res) }) }) }