github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/formats/syftjson/model/package.go (about) 1 package model 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "reflect" 8 9 "github.com/anchore/syft/internal/log" 10 "github.com/anchore/syft/syft/file" 11 "github.com/anchore/syft/syft/license" 12 "github.com/anchore/syft/syft/pkg" 13 ) 14 15 var errUnknownMetadataType = errors.New("unknown metadata type") 16 17 // Package represents a pkg.Package object specialized for JSON marshaling and unmarshalling. 18 type Package struct { 19 PackageBasicData 20 PackageCustomData 21 } 22 23 // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package. 24 type PackageBasicData struct { 25 ID string `json:"id"` 26 Name string `json:"name"` 27 Version string `json:"version"` 28 Type pkg.Type `json:"type"` 29 FoundBy string `json:"foundBy"` 30 Locations []file.Location `json:"locations"` 31 Licenses licenses `json:"licenses"` 32 Language pkg.Language `json:"language"` 33 CPEs []string `json:"cpes"` 34 PURL string `json:"purl"` 35 } 36 37 type licenses []License 38 39 type License struct { 40 Value string `json:"value"` 41 SPDXExpression string `json:"spdxExpression"` 42 Type license.Type `json:"type"` 43 URLs []string `json:"urls"` 44 Locations []file.Location `json:"locations"` 45 } 46 47 func newModelLicensesFromValues(licenses []string) (ml []License) { 48 for _, v := range licenses { 49 expression, err := license.ParseExpression(v) 50 if err != nil { 51 log.Trace("could not find valid spdx expression for %s: %w", v, err) 52 } 53 ml = append(ml, License{ 54 Value: v, 55 SPDXExpression: expression, 56 Type: license.Declared, 57 }) 58 } 59 return ml 60 } 61 62 func (f *licenses) UnmarshalJSON(b []byte) error { 63 var licenses []License 64 if err := json.Unmarshal(b, &licenses); err != nil { 65 var simpleLicense []string 66 if err := json.Unmarshal(b, &simpleLicense); err != nil { 67 return fmt.Errorf("unable to unmarshal license: %w", err) 68 } 69 licenses = newModelLicensesFromValues(simpleLicense) 70 } 71 *f = licenses 72 return nil 73 } 74 75 // PackageCustomData contains ambiguous values (type-wise) from pkg.Package. 76 type PackageCustomData struct { 77 MetadataType pkg.MetadataType `json:"metadataType,omitempty"` 78 Metadata interface{} `json:"metadata,omitempty"` 79 } 80 81 // packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling. 82 type packageMetadataUnpacker struct { 83 MetadataType pkg.MetadataType `json:"metadataType"` 84 Metadata json.RawMessage `json:"metadata"` 85 } 86 87 func (p *packageMetadataUnpacker) String() string { 88 return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata)) 89 } 90 91 // UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types. 92 func (p *Package) UnmarshalJSON(b []byte) error { 93 var basic PackageBasicData 94 if err := json.Unmarshal(b, &basic); err != nil { 95 return err 96 } 97 p.PackageBasicData = basic 98 99 var unpacker packageMetadataUnpacker 100 if err := json.Unmarshal(b, &unpacker); err != nil { 101 log.Warnf("failed to unmarshall into packageMetadataUnpacker: %v", err) 102 return err 103 } 104 105 err := unpackPkgMetadata(p, unpacker) 106 if errors.Is(err, errUnknownMetadataType) { 107 log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID) 108 return nil 109 } 110 111 return err 112 } 113 114 func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error { 115 p.MetadataType = pkg.CleanMetadataType(unpacker.MetadataType) 116 117 typ, ok := pkg.MetadataTypeByName[p.MetadataType] 118 if ok { 119 val := reflect.New(typ).Interface() 120 if len(unpacker.Metadata) > 0 { 121 if err := json.Unmarshal(unpacker.Metadata, val); err != nil { 122 return err 123 } 124 } 125 p.Metadata = reflect.ValueOf(val).Elem().Interface() 126 return nil 127 } 128 129 // capture unknown metadata as a generic struct 130 if len(unpacker.Metadata) > 0 { 131 var val interface{} 132 if err := json.Unmarshal(unpacker.Metadata, &val); err != nil { 133 return err 134 } 135 p.Metadata = val 136 } 137 138 if p.MetadataType != "" { 139 return errUnknownMetadataType 140 } 141 142 return nil 143 }