🏗️ Building Vectors
⏱️ 15 min · intermediate · Go SDK
Parsing a vector string with parser.ParseString is the right move when the string already exists. But when your code computes a vector — assembling metrics from a scanner, a form, or a database row — you want to build it programmatically. The Go SDK gives you three idioms for that, all producing the same vector. This tutorial builds CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H three ways and compares them.
Prerequisites
- Go 1.18+ (the module's
go.modpinsgo 1.18) - Finish getting-started so the SDK import paths are familiar
- The module on disk:
go get github.com/scagogogo/cvss-skills
Flow
The target
Every example below builds this vector and feeds it to a calculator:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 CriticalStep 1 — cvss.FromMap: data-shaped input
FromMap takes a map[string]string whose keys are metric short names (AV, AC, …) and whose values are short values (N, L, …). A version key (or a WithVersion option) sets the spec version.
package main
import (
"fmt"
"github.com/scagogogo/cvss-skills/pkg/cvss"
)
func main() {
m := map[string]string{
"version": "3.1",
"AV": "N", "AC": "L", "PR": "N", "UI": "N",
"S": "U", "C": "H", "I": "H", "A": "H",
}
cv, err := cvss.FromMap(m)
if err != nil {
panic(err)
}
fmt.Println(cv.String())
// CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
calc := cvss.NewCalculator(cv)
score, _ := calc.Calculate()
fmt.Printf("%.1f\n", score) // 9.8
}CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
9.8FromMap is best when the data is already a map
If your input arrives as JSON, a database row, or a map[string]string from a form, FromMap is the lowest-friction path — no chaining, no builder plumbing. MustFromMap(m) is the panic-on-error variant for tests.
Step 2 — cvss.NewBuilder(): fluent chaining
The Builder is a fluent API: each method returns the builder, so you chain calls in one expression. Methods take rune values ('N', 'L', …) and there is one method per metric.
package main
import (
"fmt"
"github.com/scagogogo/cvss-skills/pkg/cvss"
)
func main() {
cv, err := cvss.NewBuilder().
Version(3, 1).
AV('N').AC('L').PR('N').UI('N').
S('U').C('H').I('H').A('H').
Build()
if err != nil {
panic(err)
}
fmt.Println(cv.String())
// CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
calc := cvss.NewCalculator(cv)
score, _ := calc.Calculate()
fmt.Printf("%.1f\n", score) // 9.8
}CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
9.8The builder also has temporal and environmental methods (E, RL, RC, CR, MAV, …) with the same chaining shape. Build() returns the struct and an error; MustBuild() panics on error.
Builder is best for fixed, readable vectors
When the vector is fixed at code-write time (a preset, a test fixture, a documented default), the builder reads top-to-bottom and is easy to audit visually.
Step 3 — cvss.NewCvss3xWithOptions: functional options
The functional-options idiom passes WithXxx(rune) options to a variadic constructor. Each metric has a WithXxx option (WithAV, WithAC, …), and WithVersion31() / WithVersion30() set the version.
package main
import (
"fmt"
"github.com/scagogogo/cvss-skills/pkg/cvss"
)
func main() {
cv, err := cvss.NewCvss3xWithOptions(
cvss.WithVersion31(),
cvss.WithAV('N'), cvss.WithAC('L'), cvss.WithPR('N'), cvss.WithUI('N'),
cvss.WithS('U'), cvss.WithC('H'), cvss.WithI('H'), cvss.WithA('H'),
)
if err != nil {
panic(err)
}
fmt.Println(cv.String())
// CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
calc := cvss.NewCalculator(cv)
score, _ := calc.Calculate()
fmt.Printf("%.1f\n", score) // 9.8
}CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
9.8Options are best when the call site varies
Functional options shine when the set of metrics is dynamic — a scanner that conditionally emits temporal metrics, a config struct that maps to options. You can append WithXxx to a slice and splat it in: cvss.NewCvss3xWithOptions(opts...). New options can be added without breaking existing callers.
There are also convenience options that set a group at once:
WithTemporal(e, rl, rc rune)— three temporal metrics in one call.WithRequirements(cr, ir, ar rune)— three requirement metrics in one call.
Step 4 — Compare the three styles
| Idiom | Input shape | Best for | Variants |
|---|---|---|---|
FromMap(map[string]string) | a map | JSON / DB rows / forms | MustFromMap (panic) |
NewBuilder().AV(...).Build() | chained calls | fixed, readable vectors, test fixtures | BuildChecked, MustBuild |
NewCvss3xWithOptions(opts...) | option slice | dynamic call sites, library APIs | MustNewCvss3xWithOptions (panic) |
All three produce the same *Cvss3x, so the calculator, serializer, and comparator work identically downstream. Pairwise string equality confirms it:
// a := FromMap(...), b := NewBuilder()...Build(), c := NewCvss3xWithOptions(...)
a.String() == b.String() // true
b.String() == c.String() // truePick one idiom per codebase
The three idioms are interchangeable at the type level, but mixing them in one module makes the code harder to scan. Pick the one that matches your input shape and stick with it.
Step 5 — Round-trip: ToMap and FromVectorValues
Two more construction helpers round out the toolkit:
ToMap() is the inverse of FromMap — it serializes a Cvss3x back to a map[string]string:
cv, _ := cvss.NewBuilder().Version(3, 1).
AV('N').AC('L').PR('N').UI('N').S('U').C('H').I('H').A('H').Build()
m := cv.ToMap()
// map[A:H AC:L AV:N C:H I:H PR:N S:U UI:N version:3.1]FromVectorValues(version string, pairs ...string) builds from "KEY:VALUE" pairs:
cv, err := cvss.FromVectorValues("3.1",
"AV:N", "AC:L", "PR:N", "UI:N", "S:U", "C:H", "I:H", "A:H")Use FromVectorValues when you already have the segments split out (e.g. from a tokenizer) but not a full vector string.
Recap
FromMap— for map-shaped data (JSON, DB, forms).NewBuilder()— for fixed, readable, chainable construction.NewCvss3xWithOptions— for dynamic call sites and library APIs.- All three return the same
*Cvss3x; downstream code (calculator,String(),ToMap()) is identical. MustFromMap/MustBuild/MustNewCvss3xWithOptionspanic on error — for tests only.
Next
- Migrate between v3.0 and v3.1 in version-migration
- Generate test vectors with
mock.RandomCvss3xin presets-and-random