mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 15:06:36 +01:00
migrate json schema generation (#4270)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type CompletionTester struct {
|
||||
saw []any
|
||||
valid []any
|
||||
ignore []any
|
||||
}
|
||||
|
||||
func NewCompletionTester(t testing.TB, ignore ...any) *CompletionTester {
|
||||
t.Helper()
|
||||
tester := &CompletionTester{
|
||||
valid: AllTypes(),
|
||||
ignore: ignore,
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
t.Helper()
|
||||
tester.validate(t)
|
||||
})
|
||||
return tester
|
||||
}
|
||||
|
||||
func (tr *CompletionTester) Ignore(is ...any) {
|
||||
tr.ignore = append(tr.ignore, keepNonNil(is...)...)
|
||||
}
|
||||
|
||||
func (tr *CompletionTester) Expect(is ...any) {
|
||||
tr.valid = append(tr.valid, keepNonNil(is...)...)
|
||||
}
|
||||
|
||||
func (tr *CompletionTester) Tested(t testing.TB, m any) {
|
||||
t.Helper()
|
||||
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
if len(tr.valid) == 0 {
|
||||
t.Fatal("no valid metadata types to test against")
|
||||
}
|
||||
ty := reflect.TypeOf(m)
|
||||
|
||||
for _, v := range tr.ignore {
|
||||
if reflect.TypeOf(v) == ty {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range tr.valid {
|
||||
if reflect.TypeOf(v) == ty {
|
||||
tr.saw = append(tr.saw, m)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("tested metadata type is not valid: %s", ty.Name())
|
||||
}
|
||||
|
||||
func (tr *CompletionTester) validate(t testing.TB) {
|
||||
t.Helper()
|
||||
|
||||
count := make(map[reflect.Type]int)
|
||||
for _, m := range tr.saw {
|
||||
count[reflect.TypeOf(m)]++
|
||||
}
|
||||
|
||||
validations:
|
||||
for _, v := range tr.valid {
|
||||
ty := reflect.TypeOf(v)
|
||||
|
||||
for _, ignore := range tr.ignore {
|
||||
if ty == reflect.TypeOf(ignore) {
|
||||
// skip ignored types
|
||||
continue validations
|
||||
}
|
||||
}
|
||||
|
||||
if c, exists := count[ty]; c == 0 || !exists {
|
||||
t.Errorf("metadata type %s is not covered by a test", ty.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func keepNonNil(is ...any) []any {
|
||||
var result []any
|
||||
for _, i := range is {
|
||||
if i != nil {
|
||||
result = append(result, i)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
// these are names of struct types in the pkg package that are not metadata types (thus should not be in the JSON schema)
|
||||
var knownNonMetadataTypeNames = strset.New(
|
||||
"Package",
|
||||
"Collection",
|
||||
"License",
|
||||
"LicenseSet",
|
||||
)
|
||||
|
||||
// these are names that would be removed due to common convention (e.g. used within another metadata type) but are
|
||||
// known to be metadata types themselves. Adding to this list will prevent the removal of the type from the schema.
|
||||
var knownMetadataTypeNames = strset.New(
|
||||
"DotnetPortableExecutableEntry",
|
||||
)
|
||||
|
||||
func DiscoverTypeNames() ([]string, error) {
|
||||
root, err := RepoRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files, err := filepath.Glob(filepath.Join(root, "syft/pkg/*.go"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return findMetadataDefinitionNames(files...)
|
||||
}
|
||||
|
||||
func RepoRoot() (string, error) {
|
||||
root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to find repo root dir: %+v", err)
|
||||
}
|
||||
absRepoRoot, err := filepath.Abs(strings.TrimSpace(string(root)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get abs path to repo root: %w", err)
|
||||
}
|
||||
return absRepoRoot, nil
|
||||
}
|
||||
|
||||
func findMetadataDefinitionNames(paths ...string) ([]string, error) {
|
||||
names := strset.New()
|
||||
usedNames := strset.New()
|
||||
for _, path := range paths {
|
||||
metadataDefinitions, usedTypeNames, err := findMetadataDefinitionNamesInFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// useful for debugging...
|
||||
// fmt.Println(path)
|
||||
// fmt.Println("Defs:", metadataDefinitions)
|
||||
// fmt.Println("Used Types:", usedTypeNames)
|
||||
// fmt.Println()
|
||||
|
||||
names.Add(metadataDefinitions...)
|
||||
usedNames.Add(usedTypeNames...)
|
||||
}
|
||||
|
||||
// any definition that is used within another struct should not be considered a top-level metadata definition
|
||||
removeNames := strset.Difference(usedNames, knownMetadataTypeNames)
|
||||
names.Remove(removeNames.List()...)
|
||||
|
||||
// remove known exceptions, that is, types exported in the pkg Package that are not used
|
||||
// in a metadata type but are not metadata types themselves.
|
||||
names.Remove("Licenses", "KeyValue")
|
||||
|
||||
strNames := names.List()
|
||||
sort.Strings(strNames)
|
||||
|
||||
// note: 35 is a point-in-time gut check. This number could be updated if new metadata definitions are added, but is not required.
|
||||
// it is really intended to catch any major issues with the generation process that would generate, say, 0 definitions.
|
||||
if len(strNames) < 35 {
|
||||
msg := fmt.Sprintf("not enough metadata definitions found (discovered %d)", len(strNames))
|
||||
return nil, fmt.Errorf("%v", msg)
|
||||
}
|
||||
|
||||
return strNames, nil
|
||||
}
|
||||
|
||||
func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error) {
|
||||
// set up the parser
|
||||
fs := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fs, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var metadataDefinitions []string
|
||||
var usedTypeNames []string
|
||||
for _, decl := range f.Decls {
|
||||
// check if the declaration is a type declaration
|
||||
spec, ok := decl.(*ast.GenDecl)
|
||||
if !ok || spec.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
|
||||
// loop over all types declared in the type declaration
|
||||
for _, typ := range spec.Specs {
|
||||
// check if the type is a struct type
|
||||
spec, ok := typ.(*ast.TypeSpec)
|
||||
if !ok || spec.Type == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := spec.Name.String()
|
||||
|
||||
// only look for exported types
|
||||
if !isMetadataTypeCandidate(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
structType := extractStructType(spec.Type)
|
||||
if structType == nil {
|
||||
// maybe this is a slice of structs? This is useful (say type KeyValues is []KeyValue)
|
||||
structType = extractSliceOfStructType(spec.Type)
|
||||
if structType == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
metadataDefinitions = append(metadataDefinitions, name)
|
||||
usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...)
|
||||
}
|
||||
}
|
||||
return metadataDefinitions, usedTypeNames, nil
|
||||
}
|
||||
|
||||
func extractSliceOfStructType(exp ast.Expr) *ast.StructType {
|
||||
var structType *ast.StructType
|
||||
switch ty := exp.(type) {
|
||||
case *ast.ArrayType:
|
||||
// this is a standard definition:
|
||||
// type FooMetadata []BarMetadata
|
||||
structType = extractStructType(ty.Elt)
|
||||
case *ast.Ident:
|
||||
if ty.Obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// this might be a type created from another type:
|
||||
// type FooMetadata BarMetadata
|
||||
// ... but we need to check that the other type definition is a struct type
|
||||
typeSpec, ok := ty.Obj.Decl.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
nestedStructType, ok := typeSpec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
structType = nestedStructType
|
||||
}
|
||||
return structType
|
||||
}
|
||||
|
||||
func extractStructType(exp ast.Expr) *ast.StructType {
|
||||
var structType *ast.StructType
|
||||
switch ty := exp.(type) {
|
||||
case *ast.StructType:
|
||||
// this is a standard definition:
|
||||
// type FooMetadata struct { ... }
|
||||
structType = ty
|
||||
case *ast.Ident:
|
||||
if ty.Obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// this might be a type created from another type:
|
||||
// type FooMetadata BarMetadata
|
||||
// ... but we need to check that the other type definition is a struct type
|
||||
typeSpec, ok := ty.Obj.Decl.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
nestedStructType, ok := typeSpec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
structType = nestedStructType
|
||||
}
|
||||
return structType
|
||||
}
|
||||
|
||||
func typeNamesUsedInStruct(structType *ast.StructType) []string {
|
||||
// recursively find all type names used in the struct type
|
||||
var names []string
|
||||
for i := range structType.Fields.List {
|
||||
// capture names of all of the types (not field names)
|
||||
ast.Inspect(structType.Fields.List[i].Type, func(n ast.Node) bool {
|
||||
ident, ok := n.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// add the type name to the list
|
||||
names = append(names, ident.Name)
|
||||
|
||||
// continue inspecting
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func isMetadataTypeCandidate(name string) bool {
|
||||
return len(name) > 0 &&
|
||||
unicode.IsUpper(rune(name[0])) && // must be exported
|
||||
!knownNonMetadataTypeNames.Has(name)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDiscoverTypeNames_byExample(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
want: "ApkDBEntry",
|
||||
},
|
||||
{
|
||||
name: "type shadowed with another type",
|
||||
want: "RpmDBEntry",
|
||||
},
|
||||
{
|
||||
name: "type shadows another type",
|
||||
want: "RpmArchive",
|
||||
},
|
||||
}
|
||||
|
||||
got, err := DiscoverTypeNames()
|
||||
require.NotEmpty(t, got)
|
||||
gotSet := strset.New(got...)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.True(t, gotSet.Has(tt.want))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/packagemetadata"
|
||||
"github.com/dave/jennifer/jen"
|
||||
)
|
||||
|
||||
// This program is invoked from syft/internal and generates packagemetadata/generated.go
|
||||
|
||||
const (
|
||||
pkgImport = "github.com/anchore/syft/syft/pkg"
|
||||
path = "packagemetadata/generated.go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
typeNames, err := packagemetadata.DiscoverTypeNames()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to get all metadata type names: %w", err))
|
||||
}
|
||||
|
||||
// useful for debugging...
|
||||
// for _, typeName := range typeNames {
|
||||
// fmt.Printf(" - %s\n", typeName)
|
||||
//}
|
||||
|
||||
fmt.Printf("updating package metadata type list with %+v types\n", len(typeNames))
|
||||
|
||||
f := jen.NewFile("packagemetadata")
|
||||
f.HeaderComment("DO NOT EDIT: generated by internal/packagemetadata/generate/main.go")
|
||||
f.ImportName(pkgImport, "pkg")
|
||||
f.Comment("AllTypes returns a list of all pkg metadata types that syft supports (that are represented in the pkg.Package.Metadata field).")
|
||||
|
||||
f.Func().Id("AllTypes").Params().Index().Any().BlockFunc(func(g *jen.Group) {
|
||||
g.ReturnFunc(func(g *jen.Group) {
|
||||
g.Index().Any().ValuesFunc(func(g *jen.Group) {
|
||||
for _, typeName := range typeNames {
|
||||
g.Qual(pkgImport, typeName).Values()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
rendered := fmt.Sprintf("%#v", f)
|
||||
|
||||
fh, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to open file: %w", err))
|
||||
}
|
||||
|
||||
// fix a little whitespacing
|
||||
rendered = strings.ReplaceAll(rendered, ",", ",\n")
|
||||
rendered = strings.ReplaceAll(rendered, "[]any{", "[]any{\n")
|
||||
rendered = strings.ReplaceAll(rendered, "}}\n}", "},\n}\n}")
|
||||
|
||||
_, err = fh.WriteString(rendered)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to write file: %w", err))
|
||||
}
|
||||
if err := fh.Close(); err != nil {
|
||||
panic(fmt.Errorf("unable to close file: %w", err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// DO NOT EDIT: generated by internal/packagemetadata/generate/main.go
|
||||
|
||||
package packagemetadata
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
// AllTypes returns a list of all pkg metadata types that syft supports (that are represented in the pkg.Package.Metadata field).
|
||||
func AllTypes() []any {
|
||||
return []any{
|
||||
pkg.AlpmDBEntry{},
|
||||
pkg.ApkDBEntry{},
|
||||
pkg.BinarySignature{},
|
||||
pkg.BitnamiSBOMEntry{},
|
||||
pkg.CocoaPodfileLockEntry{},
|
||||
pkg.ConanV1LockEntry{},
|
||||
pkg.ConanV2LockEntry{},
|
||||
pkg.ConanfileEntry{},
|
||||
pkg.ConaninfoEntry{},
|
||||
pkg.CondaMetaPackage{},
|
||||
pkg.DartPubspec{},
|
||||
pkg.DartPubspecLockEntry{},
|
||||
pkg.DotnetDepsEntry{},
|
||||
pkg.DotnetPackagesLockEntry{},
|
||||
pkg.DotnetPortableExecutableEntry{},
|
||||
pkg.DpkgArchiveEntry{},
|
||||
pkg.DpkgDBEntry{},
|
||||
pkg.ELFBinaryPackageNoteJSONPayload{},
|
||||
pkg.ElixirMixLockEntry{},
|
||||
pkg.ErlangRebarLockEntry{},
|
||||
pkg.GitHubActionsUseStatement{},
|
||||
pkg.GolangBinaryBuildinfoEntry{},
|
||||
pkg.GolangModuleEntry{},
|
||||
pkg.GolangSourceEntry{},
|
||||
pkg.HackageStackYamlEntry{},
|
||||
pkg.HackageStackYamlLockEntry{},
|
||||
pkg.HomebrewFormula{},
|
||||
pkg.JavaArchive{},
|
||||
pkg.JavaVMInstallation{},
|
||||
pkg.LinuxKernel{},
|
||||
pkg.LinuxKernelModule{},
|
||||
pkg.LuaRocksPackage{},
|
||||
pkg.MicrosoftKbPatch{},
|
||||
pkg.NixStoreEntry{},
|
||||
pkg.NpmPackage{},
|
||||
pkg.NpmPackageLockEntry{},
|
||||
pkg.OpamPackage{},
|
||||
pkg.PEBinary{},
|
||||
pkg.PhpComposerInstalledEntry{},
|
||||
pkg.PhpComposerLockEntry{},
|
||||
pkg.PhpPearEntry{},
|
||||
pkg.PhpPeclEntry{},
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPackage{},
|
||||
pkg.PythonPipfileLockEntry{},
|
||||
pkg.PythonPoetryLockEntry{},
|
||||
pkg.PythonRequirementsEntry{},
|
||||
pkg.PythonUvLockEntry{},
|
||||
pkg.RDescription{},
|
||||
pkg.RpmArchive{},
|
||||
pkg.RpmDBEntry{},
|
||||
pkg.RubyGemspec{},
|
||||
pkg.RustBinaryAuditEntry{},
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SnapEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.SwiplPackEntry{},
|
||||
pkg.TerraformLockProviderEntry{},
|
||||
pkg.WordpressPluginEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type jsonType struct {
|
||||
ty any
|
||||
name string
|
||||
legacyNames []string
|
||||
noLookupLegacyName string // legacy name that conflict with other types, thus should not affect the lookup
|
||||
}
|
||||
|
||||
func jsonNames(ty any, name string, legacyNames ...string) jsonType {
|
||||
return jsonType{
|
||||
ty: ty,
|
||||
name: name,
|
||||
legacyNames: expandLegacyNameVariants(legacyNames...),
|
||||
}
|
||||
}
|
||||
|
||||
func jsonNamesWithoutLookup(ty any, name string, noLookupLegacyName string) jsonType {
|
||||
return jsonType{
|
||||
ty: ty,
|
||||
name: name,
|
||||
noLookupLegacyName: noLookupLegacyName,
|
||||
}
|
||||
}
|
||||
|
||||
type jsonTypeMapping struct {
|
||||
typeToName map[reflect.Type]string
|
||||
typeToLegacyName map[reflect.Type]string
|
||||
nameToType map[string]reflect.Type
|
||||
}
|
||||
|
||||
func makeJSONTypes(types ...jsonType) jsonTypeMapping {
|
||||
out := jsonTypeMapping{
|
||||
typeToName: make(map[reflect.Type]string),
|
||||
typeToLegacyName: make(map[reflect.Type]string),
|
||||
nameToType: make(map[string]reflect.Type),
|
||||
}
|
||||
for _, t := range types {
|
||||
typ := reflect.TypeOf(t.ty)
|
||||
out.typeToName[typ] = t.name
|
||||
if len(t.noLookupLegacyName) > 0 {
|
||||
out.typeToLegacyName[typ] = t.noLookupLegacyName
|
||||
} else if len(t.legacyNames) > 0 {
|
||||
out.typeToLegacyName[typ] = t.legacyNames[0]
|
||||
}
|
||||
out.nameToType[strings.ToLower(t.name)] = typ
|
||||
for _, name := range t.legacyNames {
|
||||
out.nameToType[strings.ToLower(name)] = typ
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// jsonNameFromType is lookup of all known package metadata types to their current JSON name and all previously known aliases.
|
||||
// It is important that if a name needs to change that the old name is kept in this map (as an alias) for backwards
|
||||
// compatibility to support decoding older JSON documents.
|
||||
var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.AlpmDBEntry{}, "alpm-db-entry", "AlpmMetadata"),
|
||||
jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"),
|
||||
jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"),
|
||||
jsonNames(pkg.BitnamiSBOMEntry{}, "bitnami-sbom-entry"),
|
||||
jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"),
|
||||
jsonNames(pkg.ConanV1LockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
|
||||
jsonNames(pkg.ConanV2LockEntry{}, "c-conan-lock-v2-entry"),
|
||||
jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"),
|
||||
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
|
||||
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
|
||||
jsonNames(pkg.DartPubspec{}, "dart-pubspec"),
|
||||
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
|
||||
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
|
||||
jsonNames(pkg.DpkgArchiveEntry{}, "dpkg-archive-entry"),
|
||||
jsonNames(pkg.DpkgDBEntry{}, "dpkg-db-entry", "DpkgMetadata"),
|
||||
jsonNames(pkg.ELFBinaryPackageNoteJSONPayload{}, "elf-binary-package-note-json-payload"),
|
||||
jsonNames(pkg.RubyGemspec{}, "ruby-gemspec", "GemMetadata"),
|
||||
jsonNames(pkg.GitHubActionsUseStatement{}, "github-actions-use-statement"),
|
||||
jsonNames(pkg.GolangBinaryBuildinfoEntry{}, "go-module-buildinfo-entry", "GolangBinMetadata", "GolangMetadata"),
|
||||
jsonNames(pkg.GolangModuleEntry{}, "go-module-entry", "GolangModMetadata"),
|
||||
jsonNames(pkg.GolangSourceEntry{}, "go-source-entry"),
|
||||
jsonNames(pkg.HackageStackYamlLockEntry{}, "haskell-hackage-stack-lock-entry", "HackageMetadataType"),
|
||||
jsonNamesWithoutLookup(pkg.HackageStackYamlEntry{}, "haskell-hackage-stack-entry", "HackageMetadataType"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.JavaArchive{}, "java-archive", "JavaMetadata"),
|
||||
jsonNames(pkg.JavaVMInstallation{}, "java-jvm-installation"),
|
||||
jsonNames(pkg.MicrosoftKbPatch{}, "microsoft-kb-patch", "KbPatchMetadata"),
|
||||
jsonNames(pkg.LinuxKernel{}, "linux-kernel-archive", "LinuxKernel"),
|
||||
jsonNames(pkg.LinuxKernelModule{}, "linux-kernel-module", "LinuxKernelModule"),
|
||||
jsonNames(pkg.ElixirMixLockEntry{}, "elixir-mix-lock-entry", "MixLockMetadataType"),
|
||||
jsonNames(pkg.NixStoreEntry{}, "nix-store-entry", "NixStoreMetadata"),
|
||||
jsonNames(pkg.NpmPackage{}, "javascript-npm-package", "NpmPackageJsonMetadata"),
|
||||
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
|
||||
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
|
||||
jsonNames(pkg.PEBinary{}, "pe-binary"),
|
||||
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.PhpPeclEntry{}, "php-pecl-entry", "PhpPeclMetadata"),
|
||||
jsonNames(pkg.PhpPearEntry{}, "php-pear-entry"),
|
||||
jsonNames(pkg.PortageEntry{}, "portage-db-entry", "PortageMetadata"),
|
||||
jsonNames(pkg.PythonPackage{}, "python-package", "PythonPackageMetadata"),
|
||||
jsonNames(pkg.PythonPipfileLockEntry{}, "python-pipfile-lock-entry", "PythonPipfileLockMetadata"),
|
||||
jsonNames(pkg.PythonPoetryLockEntry{}, "python-poetry-lock-entry", "PythonPoetryLockMetadata"),
|
||||
jsonNames(pkg.PythonRequirementsEntry{}, "python-pip-requirements-entry", "PythonRequirementsMetadata"),
|
||||
jsonNames(pkg.PythonUvLockEntry{}, "python-uv-lock-entry"),
|
||||
jsonNames(pkg.ErlangRebarLockEntry{}, "erlang-rebar-lock-entry", "RebarLockMetadataType"),
|
||||
jsonNames(pkg.RDescription{}, "r-description", "RDescriptionFileMetadataType"),
|
||||
jsonNames(pkg.RpmDBEntry{}, "rpm-db-entry", "RpmMetadata", "RpmdbMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.RpmArchive{}, "rpm-archive", "RpmMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.SwiftPackageManagerResolvedEntry{}, "swift-package-manager-lock-entry", "SwiftPackageManagerMetadata"),
|
||||
jsonNames(pkg.SwiplPackEntry{}, "swiplpack-package"),
|
||||
jsonNames(pkg.OpamPackage{}, "opam-package"),
|
||||
jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.SnapEntry{}, "snap-entry"),
|
||||
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
|
||||
jsonNames(pkg.HomebrewFormula{}, "homebrew-formula"),
|
||||
jsonNames(pkg.LuaRocksPackage{}, "luarocks-package"),
|
||||
jsonNames(pkg.TerraformLockProviderEntry{}, "terraform-lock-provider-entry"),
|
||||
jsonNames(pkg.DotnetPackagesLockEntry{}, "dotnet-packages-lock-entry"),
|
||||
jsonNames(pkg.CondaMetaPackage{}, "conda-metadata-entry", "CondaPackageMetadata"),
|
||||
)
|
||||
|
||||
func expandLegacyNameVariants(names ...string) []string {
|
||||
var candidates []string
|
||||
for _, name := range names {
|
||||
candidates = append(candidates, name)
|
||||
if strings.HasSuffix(name, "MetadataType") {
|
||||
candidates = append(candidates, strings.TrimSuffix(name, "Type"))
|
||||
} else if strings.HasSuffix(name, "Metadata") {
|
||||
candidates = append(candidates, name+"Type")
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func AllTypeNames() []string {
|
||||
names := make([]string, 0)
|
||||
for _, t := range AllTypes() {
|
||||
names = append(names, reflect.TypeOf(t).Name())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func JSONName(metadata any) string {
|
||||
if name, exists := jsonTypes.typeToName[reflect.TypeOf(metadata)]; exists {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func JSONLegacyName(metadata any) string {
|
||||
if name, exists := jsonTypes.typeToLegacyName[reflect.TypeOf(metadata)]; exists {
|
||||
return name
|
||||
}
|
||||
return JSONName(metadata)
|
||||
}
|
||||
|
||||
func ReflectTypeFromJSONName(name string) reflect.Type {
|
||||
name = strings.ToLower(name)
|
||||
return jsonTypes.nameToType[name]
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestAllNames(t *testing.T) {
|
||||
// note: this is a form of completion testing relative to the current code base.
|
||||
|
||||
expected, err := DiscoverTypeNames()
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := AllTypeNames()
|
||||
|
||||
// ensure that the codebase (from ast analysis) reflects the latest code generated state
|
||||
if !assert.ElementsMatch(t, expected, actual) {
|
||||
t.Errorf("metadata types not fully represented: \n%s", cmp.Diff(expected, actual))
|
||||
t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?")
|
||||
t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)")
|
||||
}
|
||||
|
||||
for _, ty := range AllTypes() {
|
||||
assert.NotEmpty(t, JSONName(ty), "metadata type %q does not have a JSON name", reflect.TypeOf(ty).Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectTypeFromJSONName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
lookup string
|
||||
wantRecord reflect.Type
|
||||
}{
|
||||
{
|
||||
name: "exact match on ID",
|
||||
lookup: "rust-cargo-lock-entry",
|
||||
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "exact match on former name",
|
||||
lookup: "RustCargoPackageMetadata",
|
||||
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "case insensitive on ID",
|
||||
lookup: "RUST-CARGO-lock-entrY",
|
||||
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "case insensitive on alias",
|
||||
lookup: "rusTcArgopacKagEmEtadATa",
|
||||
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "consistent override",
|
||||
// there are two correct answers for this -- we should always get the same answer.
|
||||
lookup: "HackageMetadataType",
|
||||
wantRecord: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ReflectTypeFromJSONName(tt.lookup)
|
||||
require.NotNil(t, got)
|
||||
assert.Equal(t, tt.wantRecord.Name(), got.Name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected reflect.Type
|
||||
}{
|
||||
// these cases are always 1:1
|
||||
{
|
||||
name: "map pkg.AlpmDBEntry struct type",
|
||||
input: "AlpmMetadata",
|
||||
expected: reflect.TypeOf(pkg.AlpmDBEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.ApkDBEntry struct type",
|
||||
input: "ApkMetadata",
|
||||
expected: reflect.TypeOf(pkg.ApkDBEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.BinarySignature struct type",
|
||||
input: "BinaryMetadata",
|
||||
expected: reflect.TypeOf(pkg.BinarySignature{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.CocoaPodfileLockEntry struct type",
|
||||
input: "CocoapodsMetadataType",
|
||||
expected: reflect.TypeOf(pkg.CocoaPodfileLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.ConanLockEntry struct type",
|
||||
input: "ConanLockMetadataType",
|
||||
expected: reflect.TypeOf(pkg.ConanV1LockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.ConanfileEntry struct type",
|
||||
input: "ConanMetadataType",
|
||||
expected: reflect.TypeOf(pkg.ConanfileEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.DartPubspecLockEntry struct type",
|
||||
input: "DartPubMetadata",
|
||||
expected: reflect.TypeOf(pkg.DartPubspecLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.DotnetDepsEntry struct type",
|
||||
input: "DotnetDepsMetadata",
|
||||
expected: reflect.TypeOf(pkg.DotnetDepsEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.DpkgDBEntry struct type",
|
||||
input: "DpkgMetadata",
|
||||
expected: reflect.TypeOf(pkg.DpkgDBEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.RubyGemspec struct type",
|
||||
input: "GemMetadata",
|
||||
expected: reflect.TypeOf(pkg.RubyGemspec{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.GolangBinaryBuildinfoEntry struct type",
|
||||
input: "GolangBinMetadata",
|
||||
expected: reflect.TypeOf(pkg.GolangBinaryBuildinfoEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.GolangModuleEntry struct type",
|
||||
input: "GolangModMetadata",
|
||||
expected: reflect.TypeOf(pkg.GolangModuleEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.JavaArchive struct type",
|
||||
input: "JavaMetadata",
|
||||
expected: reflect.TypeOf(pkg.JavaArchive{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.MicrosoftKbPatch struct type",
|
||||
input: "KbPatchMetadata",
|
||||
expected: reflect.TypeOf(pkg.MicrosoftKbPatch{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.LinuxKernel struct type",
|
||||
input: "LinuxKernel",
|
||||
expected: reflect.TypeOf(pkg.LinuxKernel{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.LinuxKernelModule struct type",
|
||||
input: "LinuxKernelModule",
|
||||
expected: reflect.TypeOf(pkg.LinuxKernelModule{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.ElixirMixLockEntry struct type",
|
||||
input: "MixLockMetadataType",
|
||||
expected: reflect.TypeOf(pkg.ElixirMixLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.NixStoreEntry struct type",
|
||||
input: "NixStoreMetadata",
|
||||
expected: reflect.TypeOf(pkg.NixStoreEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.NpmPackage struct type",
|
||||
input: "NpmPackageJsonMetadata",
|
||||
expected: reflect.TypeOf(pkg.NpmPackage{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.NpmPackageLockEntry struct type",
|
||||
input: "NpmPackageLockJsonMetadata",
|
||||
expected: reflect.TypeOf(pkg.NpmPackageLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.PortageEntry struct type",
|
||||
input: "PortageMetadata",
|
||||
expected: reflect.TypeOf(pkg.PortageEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.PythonPackage struct type",
|
||||
input: "PythonPackageMetadata",
|
||||
expected: reflect.TypeOf(pkg.PythonPackage{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.PythonPipfileLockEntry struct type",
|
||||
input: "PythonPipfileLockMetadata",
|
||||
expected: reflect.TypeOf(pkg.PythonPipfileLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.PythonRequirementsEntry struct type",
|
||||
input: "PythonRequirementsMetadata",
|
||||
expected: reflect.TypeOf(pkg.PythonRequirementsEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.PhpPeclEntry struct type",
|
||||
input: "PhpPeclMetadata",
|
||||
expected: reflect.TypeOf(pkg.PhpPeclEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.ErlangRebarLockEntry struct type",
|
||||
input: "RebarLockMetadataType",
|
||||
expected: reflect.TypeOf(pkg.ErlangRebarLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.RDescription struct type",
|
||||
input: "RDescriptionFileMetadataType",
|
||||
expected: reflect.TypeOf(pkg.RDescription{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.RpmDBEntry struct type",
|
||||
input: "RpmdbMetadata",
|
||||
expected: reflect.TypeOf(pkg.RpmDBEntry{}),
|
||||
},
|
||||
// these cases are 1:many
|
||||
{
|
||||
name: "map pkg.RpmDBEntry struct type - overlap with RpmArchiveMetadata",
|
||||
input: "RpmMetadata",
|
||||
// this used to be shared as a use case for both RpmArchive and RpmDBEntry
|
||||
// from a data-shape perspective either would be equally correct
|
||||
// however, the RPMDBMetadata has been around longer and may have been more widely used
|
||||
// so we'll map to that type for backwards compatibility.
|
||||
expected: reflect.TypeOf(pkg.RpmDBEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.HackageStackYamlLockEntry struct type - overlap with HackageStack*Metadata",
|
||||
input: "HackageMetadataType",
|
||||
// this used to be shared as a use case for both HackageStackYamlLockEntry and HackageStackYamlEntry
|
||||
// but the HackageStackYamlLockEntry maps most closely to the original data shape.
|
||||
expected: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.PhpComposerLockEntry struct type",
|
||||
input: "PhpComposerJsonMetadata",
|
||||
// this used to be shared as a use case for both PhpComposerLockEntry and PhpComposerInstalledEntry
|
||||
// neither of these is more correct over the other. These parsers were also introduced at the same time.
|
||||
expected: reflect.TypeOf(pkg.PhpComposerLockEntry{}),
|
||||
},
|
||||
{
|
||||
name: "map pkg.RustCargoLockEntry struct type",
|
||||
input: "RustCargoPackageMetadata",
|
||||
// this used to be shared as a use case for both RustCargoLockEntry and RustBinaryAuditEntry
|
||||
// neither of these is more correct over the other.
|
||||
expected: reflect.TypeOf(pkg.RustCargoLockEntry{}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
result := ReflectTypeFromJSONName(testCase.input)
|
||||
assert.Equal(t, testCase.expected.Name(), result.Name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_JSONName_JSONLegacyName(t *testing.T) {
|
||||
// note: these are all the types and names covered by the v11.x and v12.x JSON schemas
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata any
|
||||
expectedJSONName string
|
||||
expectedLegacyName string
|
||||
}{
|
||||
{
|
||||
name: "AlpmMetadata",
|
||||
metadata: pkg.AlpmDBEntry{},
|
||||
expectedJSONName: "alpm-db-entry",
|
||||
expectedLegacyName: "AlpmMetadata",
|
||||
},
|
||||
{
|
||||
name: "ApkMetadata",
|
||||
metadata: pkg.ApkDBEntry{},
|
||||
expectedJSONName: "apk-db-entry",
|
||||
expectedLegacyName: "ApkMetadata",
|
||||
},
|
||||
{
|
||||
name: "BinaryMetadata",
|
||||
metadata: pkg.BinarySignature{},
|
||||
expectedJSONName: "binary-signature",
|
||||
expectedLegacyName: "BinaryMetadata",
|
||||
},
|
||||
{
|
||||
name: "CocoapodsMetadata",
|
||||
metadata: pkg.CocoaPodfileLockEntry{},
|
||||
expectedJSONName: "cocoa-podfile-lock-entry",
|
||||
expectedLegacyName: "CocoapodsMetadataType",
|
||||
},
|
||||
{
|
||||
name: "ConanLockMetadata",
|
||||
metadata: pkg.ConanV1LockEntry{},
|
||||
expectedJSONName: "c-conan-lock-entry",
|
||||
expectedLegacyName: "ConanLockMetadataType",
|
||||
},
|
||||
{
|
||||
name: "ConanMetadata",
|
||||
metadata: pkg.ConanfileEntry{},
|
||||
expectedJSONName: "c-conan-file-entry",
|
||||
expectedLegacyName: "ConanMetadataType",
|
||||
},
|
||||
{
|
||||
name: "DartPubMetadata",
|
||||
metadata: pkg.DartPubspecLockEntry{},
|
||||
expectedJSONName: "dart-pubspec-lock-entry",
|
||||
expectedLegacyName: "DartPubMetadata",
|
||||
},
|
||||
{
|
||||
name: "DotnetDepsMetadata",
|
||||
metadata: pkg.DotnetDepsEntry{},
|
||||
expectedJSONName: "dotnet-deps-entry",
|
||||
expectedLegacyName: "DotnetDepsMetadata",
|
||||
},
|
||||
{
|
||||
name: "DotnetPortableExecutableMetadata",
|
||||
metadata: pkg.DotnetPortableExecutableEntry{},
|
||||
expectedJSONName: "dotnet-portable-executable-entry",
|
||||
expectedLegacyName: "dotnet-portable-executable-entry", // note: the legacy name should never be blank if it didn't exist pre v11.x
|
||||
},
|
||||
{
|
||||
name: "DpkgMetadata",
|
||||
metadata: pkg.DpkgDBEntry{},
|
||||
expectedJSONName: "dpkg-db-entry",
|
||||
expectedLegacyName: "DpkgMetadata",
|
||||
},
|
||||
{
|
||||
name: "GemMetadata",
|
||||
metadata: pkg.RubyGemspec{},
|
||||
expectedJSONName: "ruby-gemspec",
|
||||
expectedLegacyName: "GemMetadata",
|
||||
},
|
||||
{
|
||||
name: "GolangBinMetadata",
|
||||
metadata: pkg.GolangBinaryBuildinfoEntry{},
|
||||
expectedJSONName: "go-module-buildinfo-entry",
|
||||
expectedLegacyName: "GolangBinMetadata",
|
||||
},
|
||||
{
|
||||
name: "GolangModMetadata",
|
||||
metadata: pkg.GolangModuleEntry{},
|
||||
expectedJSONName: "go-module-entry",
|
||||
expectedLegacyName: "GolangModMetadata",
|
||||
},
|
||||
{
|
||||
name: "GolangSourceMetadata",
|
||||
metadata: pkg.GolangSourceEntry{},
|
||||
expectedJSONName: "go-source-entry",
|
||||
expectedLegacyName: "go-source-entry",
|
||||
},
|
||||
{
|
||||
name: "HackageStackYamlLockMetadata",
|
||||
metadata: pkg.HackageStackYamlLockEntry{},
|
||||
expectedJSONName: "haskell-hackage-stack-lock-entry",
|
||||
expectedLegacyName: "HackageMetadataType", // this is closest to the original data shape in <=v11.x schema
|
||||
},
|
||||
{
|
||||
name: "HackageStackYamlMetadata",
|
||||
metadata: pkg.HackageStackYamlEntry{},
|
||||
expectedJSONName: "haskell-hackage-stack-entry",
|
||||
expectedLegacyName: "HackageMetadataType", // note: this conflicts with <=v11.x schema for "haskell-hackage-stack-lock" metadata type
|
||||
},
|
||||
{
|
||||
name: "JavaMetadata",
|
||||
metadata: pkg.JavaArchive{},
|
||||
expectedJSONName: "java-archive",
|
||||
expectedLegacyName: "JavaMetadata",
|
||||
},
|
||||
{
|
||||
name: "KbPatchMetadata",
|
||||
metadata: pkg.MicrosoftKbPatch{},
|
||||
expectedJSONName: "microsoft-kb-patch",
|
||||
expectedLegacyName: "KbPatchMetadata",
|
||||
},
|
||||
{
|
||||
name: "LinuxKernel",
|
||||
metadata: pkg.LinuxKernel{},
|
||||
expectedJSONName: "linux-kernel-archive",
|
||||
expectedLegacyName: "LinuxKernel",
|
||||
},
|
||||
{
|
||||
name: "LinuxKernelModule",
|
||||
metadata: pkg.LinuxKernelModule{},
|
||||
expectedJSONName: "linux-kernel-module",
|
||||
expectedLegacyName: "LinuxKernelModule",
|
||||
},
|
||||
{
|
||||
name: "MixLockMetadata",
|
||||
metadata: pkg.ElixirMixLockEntry{},
|
||||
expectedJSONName: "elixir-mix-lock-entry",
|
||||
expectedLegacyName: "MixLockMetadataType",
|
||||
},
|
||||
{
|
||||
name: "NixStoreMetadata",
|
||||
metadata: pkg.NixStoreEntry{},
|
||||
expectedJSONName: "nix-store-entry",
|
||||
expectedLegacyName: "NixStoreMetadata",
|
||||
},
|
||||
{
|
||||
name: "NpmPackageJSONMetadata",
|
||||
metadata: pkg.NpmPackage{},
|
||||
expectedJSONName: "javascript-npm-package",
|
||||
expectedLegacyName: "NpmPackageJsonMetadata",
|
||||
},
|
||||
{
|
||||
name: "NpmPackageLockJSONMetadata",
|
||||
metadata: pkg.NpmPackageLockEntry{},
|
||||
expectedJSONName: "javascript-npm-package-lock-entry",
|
||||
expectedLegacyName: "NpmPackageLockJsonMetadata",
|
||||
},
|
||||
{
|
||||
name: "PhpComposerLockMetadata",
|
||||
metadata: pkg.PhpComposerLockEntry{},
|
||||
expectedJSONName: "php-composer-lock-entry",
|
||||
expectedLegacyName: "PhpComposerJsonMetadata", // note: maps to multiple entries (v11-12 breaking change)
|
||||
},
|
||||
{
|
||||
name: "PhpComposerInstalledMetadata",
|
||||
metadata: pkg.PhpComposerInstalledEntry{},
|
||||
expectedJSONName: "php-composer-installed-entry",
|
||||
expectedLegacyName: "PhpComposerJsonMetadata", // note: maps to multiple entries (v11-12 breaking change)
|
||||
},
|
||||
{
|
||||
name: "PhpPeclMetadata",
|
||||
metadata: pkg.PhpPeclEntry{},
|
||||
expectedJSONName: "php-pecl-entry",
|
||||
expectedLegacyName: "PhpPeclMetadata",
|
||||
},
|
||||
{
|
||||
name: "PortageMetadata",
|
||||
metadata: pkg.PortageEntry{},
|
||||
expectedJSONName: "portage-db-entry",
|
||||
expectedLegacyName: "PortageMetadata",
|
||||
},
|
||||
{
|
||||
name: "PythonPackageMetadata",
|
||||
metadata: pkg.PythonPackage{},
|
||||
expectedJSONName: "python-package",
|
||||
expectedLegacyName: "PythonPackageMetadata",
|
||||
},
|
||||
{
|
||||
name: "PythonPipfileLockMetadata",
|
||||
metadata: pkg.PythonPipfileLockEntry{},
|
||||
expectedJSONName: "python-pipfile-lock-entry",
|
||||
expectedLegacyName: "PythonPipfileLockMetadata",
|
||||
},
|
||||
{
|
||||
name: "PythonRequirementsMetadata",
|
||||
metadata: pkg.PythonRequirementsEntry{},
|
||||
expectedJSONName: "python-pip-requirements-entry",
|
||||
expectedLegacyName: "PythonRequirementsMetadata",
|
||||
},
|
||||
{
|
||||
name: "RebarLockMetadata",
|
||||
metadata: pkg.ErlangRebarLockEntry{},
|
||||
expectedJSONName: "erlang-rebar-lock-entry",
|
||||
expectedLegacyName: "RebarLockMetadataType",
|
||||
},
|
||||
{
|
||||
name: "RDescriptionFileMetadata",
|
||||
metadata: pkg.RDescription{},
|
||||
expectedJSONName: "r-description",
|
||||
expectedLegacyName: "RDescriptionFileMetadataType",
|
||||
},
|
||||
{
|
||||
name: "RpmDBMetadata",
|
||||
metadata: pkg.RpmDBEntry{},
|
||||
expectedJSONName: "rpm-db-entry",
|
||||
expectedLegacyName: "RpmMetadata", // not accurate, but how it was pre v12 of the schema
|
||||
},
|
||||
{
|
||||
name: "RpmArchiveMetadata",
|
||||
metadata: pkg.RpmArchive{},
|
||||
expectedJSONName: "rpm-archive",
|
||||
expectedLegacyName: "RpmMetadata", // note: conflicts with <=v11.x schema for "rpm-db-entry" metadata type
|
||||
},
|
||||
{
|
||||
name: "CargoPackageMetadata",
|
||||
metadata: pkg.RustCargoLockEntry{},
|
||||
expectedJSONName: "rust-cargo-lock-entry",
|
||||
expectedLegacyName: "RustCargoPackageMetadata", // note: maps to multiple entries (v11-12 breaking change)
|
||||
},
|
||||
{
|
||||
name: "CargoPackageMetadata (audit binary)",
|
||||
metadata: pkg.RustBinaryAuditEntry{},
|
||||
expectedJSONName: "rust-cargo-audit-entry",
|
||||
expectedLegacyName: "RustCargoPackageMetadata", // note: maps to multiple entries (v11-12 breaking change)
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actualJSONName := JSONName(test.metadata)
|
||||
actualLegacyName := JSONLegacyName(test.metadata)
|
||||
assert.Equal(t, test.expectedJSONName, actualJSONName, "unexpected name")
|
||||
assert.Equal(t, test.expectedLegacyName, actualLegacyName, "unexpected legacy name")
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user