⚡ Batch Scoring
⚡ Feature · pkg/cvss
BatchScore and BatchAllScores run the CVSS scoring pipeline across a slice of *Cvss3x vectors using a fixed-size worker pool. Each result carries the original index so callers can rejoin input and output, plus an Error field so one bad vector never aborts the batch.
Synopsis
results := cvss.BatchScore([]*cvss.Cvss3x{a, b, c}, 4)
for _, r := range results {
if r.Error != nil {
log.Printf("index %d: %v", r.Index, r.Error)
continue
}
fmt.Printf("index %d: %.1f (%s)\n", r.Index, r.Score, r.Severity)
}workerCount controls concurrency. workerCount <= 0 uses len(vectors), and values larger than the slice are clamped down to len(vectors). A nil entry in the input slice yields a result with Error = ErrNilReceiver rather than a panic. Results are returned in input order — the goroutines write to results[idx], not to a shared append target.
API Reference
BatchScore
func BatchScore(vectors []*Cvss3x, workerCount int) []BatchScoreResultScores each vector with NewCalculator(v).Calculate() and tags it with GetSeverity(score). Returns nil for an empty input.
results := cvss.BatchScore(vectors, 0) // 0 => len(vectors) workersBatchScoreResult
type BatchScoreResult struct {
Index int // original input index
Vector *Cvss3x // original CVSS object (nil if input was nil)
Score float64 // score, 0 on failure
Severity Severity // severity rating
Error error // scoring error, nil on success
}BatchAllScores
func BatchAllScores(vectors []*Cvss3x, workerCount int) []BatchAllScoresResultComputes the full *AllScores for each vector (base/temporal/environmental + sub-scores) via Calculator.GetAllScores. Use this when you need more than the headline number.
all := cvss.BatchAllScores(vectors, 4)BatchAllScoresResult
type BatchAllScoresResult struct {
Index int // original input index
Scores *AllScores // full score set, nil on failure
Error error // error
}Index is your join key
The functions never reorder or drop results. results[i].Index always equals the position in the input slice, so you can correlate failures back to the source vector without sorting.
workerCount is a cap, not a guaranteed pool size
The pool size is min(workerCount, len(vectors)). Passing 1000 for a 5-element slice spawns 5 goroutines, not 1000.
Concurrency Model
A buffered jobs channel (capacity len(vectors)) feeds indices to workerCount goroutines. Each worker pulls an index, scores vectors[idx], and writes the result straight into results[idx] — no result channel, no reordering. sync.WaitGroup gates the return.
Example
package main
import (
"fmt"
"log"
"github.com/scagogogo/cvss-skills/pkg/cvss"
"github.com/scagogogo/cvss-skills/pkg/parser"
)
func main() {
vectors := []*cvss.Cvss3x{
mustParse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"),
mustParse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"),
nil, // a nil entry is reported, not panicked
mustParse("CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N"),
}
results := cvss.BatchScore(vectors, 4)
for _, r := range results {
if r.Error != nil {
log.Printf("index %d failed: %v", r.Index, r.Error)
continue
}
fmt.Printf("index %d: %.1f (%s)\n", r.Index, r.Score, r.Severity)
}
// Full score breakdown for every vector at once.
for _, r := range cvss.BatchAllScores(vectors, 2) {
if r.Error != nil {
continue
}
fmt.Printf("index %d base=%.1f\n", r.Index, r.Scores.BaseScore)
}
}
func mustParse(s string) *cvss.Cvss3x {
cv, err := parser.ParseString(s)
if err != nil {
panic(err)
}
return cv
}Related
- Scoring (calculator) — what
BatchScorecalls under the hood - Scores — the per-vector
AllScoresproduced byBatchAllScores - Severity — the
Severityfield on each result - CLI:
batch score