Skip to content

Commit 95e5972

Browse files
committed
reduce complexity and clean up constraint checking
1 parent 9c68278 commit 95e5972

File tree

3 files changed

+125
-43
lines changed

3 files changed

+125
-43
lines changed

functions/functions_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ import (
88

99
func TestMapBuiltinFunctions(t *testing.T) {
1010
funcs := MapBuiltinFunctions()
11-
assert.Len(t, funcs.GetAllFunctions(), 80)
11+
assert.Len(t, funcs.GetAllFunctions(), 81)
1212
}

functions/openapi/examples_schema.go

Lines changed: 120 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
4848
return results
4949
}
5050

51-
// Get configuration values from context, use defaults if not set
51+
// get configuration values from context, use defaults if not set
5252
maxConcurrentValidations := ruleContext.MaxConcurrentValidations
5353
if maxConcurrentValidations <= 0 {
5454
maxConcurrentValidations = 10 // Default: 10 parallel validations
@@ -59,19 +59,19 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
5959
validationTimeout = 10 * time.Second // Default: 10 seconds
6060
}
6161

62-
// Create a timeout context for the entire validation process
62+
// create a timeout context for the entire validation process
6363
ctx, cancel := context.WithTimeout(context.Background(), validationTimeout)
6464
defer cancel()
6565

66-
// Create semaphore for concurrency limiting
66+
// create semaphore for concurrency limiting
6767
sem := make(chan struct{}, maxConcurrentValidations)
6868

69-
// Track active workers
69+
// track active workers
7070
var activeWorkers int32
7171
var completedWorkers int32
7272

7373
buildResult := func(message, path string, key, node *yaml.Node, component v3.AcceptsRuleResults) model.RuleFunctionResult {
74-
// Try to find all paths for this node if it's a schema
74+
// try to find all paths for this node if it's a schema
7575
var allPaths []string
7676
if schema, ok := component.(*v3.Schema); ok {
7777
_, allPaths = vacuumUtils.LocateSchemaPropertyPaths(ruleContext, schema, key, node)
@@ -85,7 +85,7 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
8585
Rule: ruleContext.Rule,
8686
}
8787

88-
// Set the Paths array if we found multiple locations
88+
// set the Paths array if we found multiple locations
8989
if len(allPaths) > 1 {
9090
result.Paths = allPaths
9191
}
@@ -97,9 +97,9 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
9797
var expLock sync.Mutex
9898
var wg sync.WaitGroup
9999

100-
// Helper function to spawn workers with context and concurrency control
100+
// helper function to spawn workers with context and concurrency control
101101
spawnWorker := func(work func()) {
102-
// Check if context is already cancelled before spawning
102+
// check if context is already cancelled before spawning
103103
select {
104104
case <-ctx.Done():
105105
return
@@ -114,26 +114,26 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
114114
defer atomic.AddInt32(&completedWorkers, 1)
115115
defer atomic.AddInt32(&activeWorkers, -1)
116116

117-
// Recover from panics to prevent crashes
117+
// recover from panics to prevent crashes
118118
defer func() {
119119
if r := recover(); r != nil {
120-
// Log panic if logger available
120+
// log panic if logger available
121121
if ruleContext.Logger != nil {
122122
ruleContext.Logger.Error("ExamplesSchema validation panic", "error", r)
123123
}
124124
}
125125
}()
126126

127-
// Try to acquire semaphore with context
127+
// try to acquire semaphore with context
128128
select {
129129
case sem <- struct{}{}:
130130
defer func() { <-sem }()
131131
case <-ctx.Done():
132-
// Context cancelled while waiting for semaphore
132+
// context cancelled while waiting for semaphore
133133
return
134134
}
135135

136-
// Check context again before starting work
136+
// check context again before starting work
137137
select {
138138
case <-ctx.Done():
139139
return
@@ -199,7 +199,7 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
199199
for i := range ruleContext.DrDocument.Schemas {
200200
s := ruleContext.DrDocument.Schemas[i]
201201
spawnWorker(func() {
202-
// Check context at start of work
202+
// check context at start of work
203203
select {
204204
case <-ctx.Done():
205205
return
@@ -208,7 +208,7 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
208208

209209
if s.Value.Examples != nil {
210210
for x, ex := range s.Value.Examples {
211-
// Check context in loop
211+
// check context in loop
212212
select {
213213
case <-ctx.Done():
214214
return
@@ -269,38 +269,100 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
269269
}
270270
}
271271

272+
// exampleValidatorFunc defines the function signature for validating examples
273+
type exampleValidatorFunc func(example any) (bool, []*errors.ValidationError)
274+
275+
// processValidationErrors converts validation errors to rule function results
276+
processValidationErrors := func(
277+
validationErrors []*errors.ValidationError,
278+
path string,
279+
keyNode, valueNode *yaml.Node,
280+
schema *v3.Schema,
281+
) []model.RuleFunctionResult {
282+
var rx []model.RuleFunctionResult
283+
for _, r := range validationErrors {
284+
for _, err := range r.SchemaValidationErrors {
285+
result := buildResult(
286+
vacuumUtils.SuppliedOrDefault(ruleContext.Rule.Message, err.Reason),
287+
path, keyNode, valueNode, schema)
288+
289+
// check if this is a banned error
290+
banned := false
291+
for g := range bannedErrors {
292+
if strings.Contains(err.Reason, bannedErrors[g]) {
293+
banned = true
294+
break
295+
}
296+
}
297+
if !banned {
298+
rx = append(rx, result)
299+
}
300+
}
301+
}
302+
return rx
303+
}
304+
305+
// createJSONValidator creates a validator for JSON examples
306+
createJSONValidator := func(
307+
iKey *int,
308+
sKey, label string,
309+
s *v3.Schema,
310+
obj v3.AcceptsRuleResults,
311+
node *yaml.Node,
312+
keyNode *yaml.Node,
313+
) exampleValidatorFunc {
314+
return func(example any) (bool, []*errors.ValidationError) {
315+
return validateSchema(iKey, sKey, label, s, obj, node, keyNode, example)
316+
}
317+
}
318+
319+
// createXMLValidator creates a validator for XML examples
320+
createXMLValidator := func(s *v3.Schema, ver float32) exampleValidatorFunc {
321+
return func(example any) (bool, []*errors.ValidationError) {
322+
if xmlStr, ok := example.(string); ok {
323+
if ver > 0 {
324+
return validator.ValidateXMLStringWithVersion(s.Value, xmlStr, ver)
325+
}
326+
return validator.ValidateXMLString(s.Value, xmlStr)
327+
}
328+
return true, nil
329+
}
330+
}
331+
272332
parseExamples := func(s *v3.Schema,
273333
obj v3.AcceptsRuleResults,
274-
examples *orderedmap.Map[string,
275-
*v3Base.Example]) []model.RuleFunctionResult {
334+
examples *orderedmap.Map[string, *v3Base.Example],
335+
validatorFunc exampleValidatorFunc) []model.RuleFunctionResult {
276336

277337
var rx []model.RuleFunctionResult
278338
for examplesPairs := examples.First(); examplesPairs != nil; examplesPairs = examplesPairs.Next() {
279-
280339
example := examplesPairs.Value()
281340
exampleKey := examplesPairs.Key()
282341

283342
var ex any
284343
if example.Value != nil {
285344
_ = example.Value.Decode(&ex)
286-
result := validateSchema(nil, exampleKey, "examples", s, obj, example.Value, example.GoLow().KeyNode, ex)
287-
if result != nil {
288-
rx = append(rx, result...)
345+
valid, validationErrors := validatorFunc(ex)
346+
347+
if !valid {
348+
path := fmt.Sprintf("%s.examples['%s']", obj.(v3.Foundational).GenerateJSONPath(), exampleKey)
349+
rx = append(rx, processValidationErrors(validationErrors, path,
350+
example.GoLow().KeyNode, example.Value, s)...)
289351
}
290352
}
291353
}
292354
return rx
293355
}
294356

295-
parseExample := func(s *v3.Schema, node, key *yaml.Node) []model.RuleFunctionResult {
296-
357+
parseExample := func(s *v3.Schema, node, key *yaml.Node, validatorFunc exampleValidatorFunc) []model.RuleFunctionResult {
297358
var rx []model.RuleFunctionResult
298359
var ex any
299360
_ = node.Decode(&ex)
300361

301-
result := validateSchema(nil, "", "example", s, s, node, key, ex)
302-
if result != nil {
303-
rx = append(rx, result...)
362+
valid, validationErrors := validatorFunc(ex)
363+
if !valid {
364+
path := ""
365+
rx = append(rx, processValidationErrors(validationErrors, path, key, node, s)...)
304366
}
305367
return rx
306368
}
@@ -309,25 +371,27 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
309371
for i := range ruleContext.DrDocument.Parameters {
310372
p := ruleContext.DrDocument.Parameters[i]
311373
spawnWorker(func() {
312-
// Check context at start of work
374+
// check context at start of work
313375
select {
314376
case <-ctx.Done():
315377
return
316378
default:
317379
}
318380

319381
if p.Value.Examples.Len() >= 1 && p.SchemaProxy != nil {
382+
jsonValidator := createJSONValidator(nil, "", "examples", p.SchemaProxy.Schema, p, nil, nil)
320383
expLock.Lock()
321384
if p.Value.Examples != nil && p.Value.Examples.Len() > 0 {
322-
results = append(results, parseExamples(p.SchemaProxy.Schema, p, p.Value.Examples)...)
385+
results = append(results, parseExamples(p.SchemaProxy.Schema, p, p.Value.Examples, jsonValidator)...)
323386
}
324387
expLock.Unlock()
325388
} else {
326389
if p.Value.Example != nil && p.SchemaProxy != nil {
390+
jsonValidator := createJSONValidator(nil, "", "example", p.SchemaProxy.Schema, p, p.Value.Example, p.Value.GoLow().Example.GetKeyNode())
327391
expLock.Lock()
328392
if p.Value.Examples != nil && p.Value.Examples.Len() > 0 {
329393
results = append(results, parseExample(p.SchemaProxy.Schema, p.Value.Example,
330-
p.Value.GoLow().Example.GetKeyNode())...)
394+
p.Value.GoLow().Example.GetKeyNode(), jsonValidator)...)
331395
}
332396
expLock.Unlock()
333397
}
@@ -340,22 +404,24 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
340404
for i := range ruleContext.DrDocument.Headers {
341405
h := ruleContext.DrDocument.Headers[i]
342406
spawnWorker(func() {
343-
// Check context at start of work
407+
// check context at start of work
344408
select {
345409
case <-ctx.Done():
346410
return
347411
default:
348412
}
349413

350414
if h.Value.Examples.Len() >= 1 && h.Schema != nil {
415+
jsonValidator := createJSONValidator(nil, "", "examples", h.Schema.Schema, h, nil, nil)
351416
expLock.Lock()
352-
results = append(results, parseExamples(h.Schema.Schema, h, h.Value.Examples)...)
417+
results = append(results, parseExamples(h.Schema.Schema, h, h.Value.Examples, jsonValidator)...)
353418
expLock.Unlock()
354419
} else {
355420
if h.Value.Example != nil && h.Schema != nil {
421+
jsonValidator := createJSONValidator(nil, "", "example", h.Schema.Schema, h, h.Value.Example, h.Value.GoLow().Example.GetKeyNode())
356422
expLock.Lock()
357423
results = append(results, parseExample(h.Schema.Schema, h.Value.Example,
358-
h.Value.GoLow().Example.GetKeyNode())...)
424+
h.Value.GoLow().Example.GetKeyNode(), jsonValidator)...)
359425
expLock.Unlock()
360426
}
361427
}
@@ -368,22 +434,38 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
368434
for i := range ruleContext.DrDocument.MediaTypes {
369435
mt := ruleContext.DrDocument.MediaTypes[i]
370436
spawnWorker(func() {
371-
// Check context at start of work
437+
// check context at start of work
372438
select {
373439
case <-ctx.Done():
374440
return
375441
default:
376442
}
377443

444+
// check if this is xml content type
445+
mediaTypeStr := mt.GetKeyValue()
446+
isXML := schema_validation.IsXMLContentType(mediaTypeStr)
447+
378448
if mt.Value.Examples.Len() >= 1 && mt.SchemaProxy != nil {
449+
var exampleValidator exampleValidatorFunc
450+
if isXML {
451+
exampleValidator = createXMLValidator(mt.SchemaProxy.Schema, version)
452+
} else {
453+
exampleValidator = createJSONValidator(nil, "", "examples", mt.SchemaProxy.Schema, mt, nil, nil)
454+
}
379455
expLock.Lock()
380-
results = append(results, parseExamples(mt.SchemaProxy.Schema, mt, mt.Value.Examples)...)
456+
results = append(results, parseExamples(mt.SchemaProxy.Schema, mt, mt.Value.Examples, exampleValidator)...)
381457
expLock.Unlock()
382458
} else {
383459
if mt.Value.Example != nil && mt.SchemaProxy != nil {
460+
var exampleValidator exampleValidatorFunc
461+
if isXML {
462+
exampleValidator = createXMLValidator(mt.SchemaProxy.Schema, version)
463+
} else {
464+
exampleValidator = createJSONValidator(nil, "", "example", mt.SchemaProxy.Schema, mt, mt.Value.Example, mt.Value.GoLow().Example.GetKeyNode())
465+
}
384466
expLock.Lock()
385467
results = append(results, parseExample(mt.SchemaProxy.Schema, mt.Value.Example,
386-
mt.Value.GoLow().Example.GetKeyNode())...)
468+
mt.Value.GoLow().Example.GetKeyNode(), exampleValidator)...)
387469
expLock.Unlock()
388470
}
389471
}
@@ -392,7 +474,7 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
392474

393475
}
394476

395-
// Wait for all workers to complete or context to timeout
477+
// wait for all workers to complete or context to timeout
396478
done := make(chan struct{})
397479
go func() {
398480
wg.Wait()
@@ -401,13 +483,13 @@ func (es ExamplesSchema) RunRule(_ []*yaml.Node, ruleContext model.RuleFunctionC
401483

402484
select {
403485
case <-done:
404-
// All workers completed normally
486+
// all workers completed normally
405487
if ruleContext.Logger != nil && atomic.LoadInt32(&completedWorkers) > 0 {
406488
ruleContext.Logger.Debug("ExamplesSchema completed validations",
407489
"completed", atomic.LoadInt32(&completedWorkers))
408490
}
409491
case <-ctx.Done():
410-
// Timeout occurred - return whatever results we have
492+
// timeout occurred - return whatever results we have
411493
if ruleContext.Logger != nil {
412494
ruleContext.Logger.Warn("ExamplesSchema validation timeout",
413495
"timeout", validationTimeout,

rulesets/rulesets_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414
"time"
1515
)
1616

17-
var totalRules = 65
17+
var totalRules = 66
18+
var totalRecommendedRules = 54
1819
var totalOwaspRules = 23
19-
var totalRecommendedRules = 53
2020

2121
func TestBuildDefaultRuleSets(t *testing.T) {
2222

@@ -551,7 +551,7 @@ rules:
551551
rs, err := CreateRuleSetFromData([]byte(yamlA))
552552
assert.NoError(t, err)
553553
override := def.GenerateRuleSetFromSuppliedRuleSet(rs)
554-
assert.Len(t, override.Rules, 55)
554+
assert.Len(t, override.Rules, 56)
555555
assert.Len(t, override.RuleDefinitions, 2)
556556
assert.NotNil(t, rs.Rules["ding"])
557557
assert.NotNil(t, rs.Rules["dong"])
@@ -740,7 +740,7 @@ func TestRuleSet_GetExtendsLocalSpec_Multi_Chain(t *testing.T) {
740740
rs, err := CreateRuleSetFromData([]byte(yaml))
741741
assert.NoError(t, err)
742742
override := def.GenerateRuleSetFromSuppliedRuleSet(rs)
743-
assert.Len(t, override.Rules, 66)
743+
assert.Len(t, override.Rules, 67)
744744
assert.Len(t, override.RuleDefinitions, 1)
745745

746746
}

0 commit comments

Comments
 (0)