Skip to content

Commit a13142e

Browse files
committed
chore: add custom unmarshal method and use concrete depgraph type
1 parent 88f2c7c commit a13142e

File tree

3 files changed

+218
-5
lines changed

3 files changed

+218
-5
lines changed

internal/snykclient/types.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
//nolint:tagliatelle // Allowing snake case for API response schemas
22
package snykclient
33

4+
import (
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/snyk/cli-extension-dep-graph/internal/depgraph"
9+
)
10+
411
type ScanResultTarget struct {
512
RemoteURL string `json:"remoteUrl"`
613
}
@@ -12,8 +19,40 @@ type ScanResultIdentity struct {
1219
}
1320

1421
type ScanResultFact struct {
15-
Type string `json:"type"`
16-
Data interface{} `json:"data"`
22+
Type string `json:"type"`
23+
Data any `json:"data"`
24+
}
25+
26+
// UnmarshalJSON implements custom JSON unmarshaling for ScanResultFact.
27+
// When type is "depGraph", it unmarshals data directly into *depgraph.DepGraph.
28+
func (f *ScanResultFact) UnmarshalJSON(data []byte) error {
29+
var scanResultRaw struct {
30+
Type string `json:"type"`
31+
Data json.RawMessage `json:"data"`
32+
}
33+
34+
if err := json.Unmarshal(data, &scanResultRaw); err != nil {
35+
return fmt.Errorf("failed to unmarshal ScanResultFact: %w", err)
36+
}
37+
38+
f.Type = scanResultRaw.Type
39+
40+
switch scanResultRaw.Type {
41+
case "depGraph":
42+
var depGraph depgraph.DepGraph
43+
if err := json.Unmarshal(scanResultRaw.Data, &depGraph); err != nil {
44+
return fmt.Errorf("failed to unmarshal depGraph data: %w", err)
45+
}
46+
f.Data = &depGraph
47+
default:
48+
var v any
49+
if err := json.Unmarshal(scanResultRaw.Data, &v); err != nil {
50+
return fmt.Errorf("failed to unmarshal fact data: %w", err)
51+
}
52+
f.Data = v
53+
}
54+
55+
return nil
1756
}
1857

1958
type ScanResult struct {

internal/snykclient/types_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package snykclient
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/snyk/cli-extension-dep-graph/internal/depgraph"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestScanResultFact_UnmarshalJSON_DepGraph(t *testing.T) {
13+
jsonData := `{
14+
"type": "depGraph",
15+
"data": {
16+
"schemaVersion": "1.2.0",
17+
"pkgManager": {
18+
"name": "npm",
19+
"version": "8.0.0",
20+
"repositories": [
21+
{
22+
"alias": "npmjs"
23+
}
24+
]
25+
},
26+
"pkgs": [
27+
{
28+
29+
"info": {
30+
"name": "root",
31+
"version": "1.0.0",
32+
"purl": "pkg:npm/[email protected]"
33+
}
34+
},
35+
{
36+
37+
"info": {
38+
"name": "dep",
39+
"version": "2.0.0"
40+
}
41+
}
42+
],
43+
"graph": {
44+
"rootNodeId": "root-node",
45+
"nodes": [
46+
{
47+
"nodeId": "root-node",
48+
"pkgId": "[email protected]",
49+
"deps": [
50+
{
51+
"nodeId": "dep-node"
52+
}
53+
]
54+
},
55+
{
56+
"nodeId": "dep-node",
57+
"pkgId": "[email protected]",
58+
"deps": []
59+
}
60+
]
61+
}
62+
}
63+
}`
64+
65+
var fact ScanResultFact
66+
err := json.Unmarshal([]byte(jsonData), &fact)
67+
68+
require.NoError(t, err)
69+
assert.Equal(t, "depGraph", fact.Type)
70+
71+
depGraph, ok := fact.Data.(*depgraph.DepGraph)
72+
require.True(t, ok, "expected fact.Data to be *depgraph.DepGraph")
73+
require.NotNil(t, depGraph)
74+
75+
assert.Equal(t, "1.2.0", depGraph.SchemaVersion)
76+
assert.Equal(t, "npm", depGraph.PkgManager.Name)
77+
assert.Equal(t, "8.0.0", depGraph.PkgManager.Version)
78+
assert.Len(t, depGraph.PkgManager.Repositories, 1)
79+
assert.Equal(t, "npmjs", depGraph.PkgManager.Repositories[0].Alias)
80+
81+
require.Len(t, depGraph.Pkgs, 2)
82+
assert.Equal(t, "[email protected]", depGraph.Pkgs[0].ID)
83+
assert.Equal(t, "root", depGraph.Pkgs[0].Info.Name)
84+
assert.Equal(t, "1.0.0", depGraph.Pkgs[0].Info.Version)
85+
assert.Equal(t, "pkg:npm/[email protected]", depGraph.Pkgs[0].Info.PackageURL)
86+
assert.Equal(t, "[email protected]", depGraph.Pkgs[1].ID)
87+
88+
assert.Equal(t, "root-node", depGraph.Graph.RootNodeID)
89+
require.Len(t, depGraph.Graph.Nodes, 2)
90+
assert.Equal(t, "root-node", depGraph.Graph.Nodes[0].NodeID)
91+
assert.Equal(t, "[email protected]", depGraph.Graph.Nodes[0].PkgID)
92+
require.Len(t, depGraph.Graph.Nodes[0].Deps, 1)
93+
assert.Equal(t, "dep-node", depGraph.Graph.Nodes[0].Deps[0].NodeID)
94+
assert.Equal(t, "dep-node", depGraph.Graph.Nodes[1].NodeID)
95+
assert.Empty(t, depGraph.Graph.Nodes[1].Deps)
96+
}
97+
98+
func TestScanResultFact_UnmarshalJSON_OtherType(t *testing.T) {
99+
jsonData := `{
100+
"type": "vulnerability",
101+
"data": {
102+
"severity": "high",
103+
"title": "Test vulnerability"
104+
}
105+
}`
106+
107+
var fact ScanResultFact
108+
err := json.Unmarshal([]byte(jsonData), &fact)
109+
110+
require.NoError(t, err)
111+
assert.Equal(t, "vulnerability", fact.Type)
112+
113+
dataMap, ok := fact.Data.(map[string]any)
114+
require.True(t, ok, "expected fact.Data to be map[string]any")
115+
assert.Equal(t, "high", dataMap["severity"])
116+
assert.Equal(t, "Test vulnerability", dataMap["title"])
117+
}
118+
119+
func TestScanResultFact_UnmarshalJSON_MalformedDepGraph(t *testing.T) {
120+
jsonData := `{
121+
"type": "depGraph",
122+
"data": {
123+
"schemaVersion": "1.2.0",
124+
"pkgs": "this should be an array not a string"
125+
}
126+
}`
127+
128+
var fact ScanResultFact
129+
err := json.Unmarshal([]byte(jsonData), &fact)
130+
131+
require.Error(t, err)
132+
assert.Contains(t, err.Error(), "failed to unmarshal depGraph data")
133+
}
134+
135+
func TestScanResultFact_UnmarshalJSON_MissingDataField(t *testing.T) {
136+
tests := []struct {
137+
name string
138+
jsonData string
139+
expectedErrMsg string
140+
}{
141+
{
142+
name: "missing data field for depGraph",
143+
jsonData: `{
144+
"type": "depGraph"
145+
}`,
146+
expectedErrMsg: "failed to unmarshal depGraph data",
147+
},
148+
{
149+
name: "missing data field for other type",
150+
jsonData: `{
151+
"type": "vulnerability"
152+
}`,
153+
expectedErrMsg: "failed to unmarshal fact data",
154+
},
155+
}
156+
157+
for _, tt := range tests {
158+
t.Run(tt.name, func(t *testing.T) {
159+
var fact ScanResultFact
160+
err := json.Unmarshal([]byte(tt.jsonData), &fact)
161+
162+
require.Error(t, err)
163+
assert.Contains(t, err.Error(), tt.expectedErrMsg)
164+
})
165+
}
166+
}

pkg/depgraph/sbom_resolution.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,16 @@ func extractDepGraphsFromScans(scans []*snykclient.ScanResult, normalisedTargetF
203203
continue
204204
}
205205

206-
// Create workflow data with the depgraph
207-
data, err := workflowDataFromDepGraph(fact.Data, normalisedTargetFile, scan.Identity.TargetFile)
206+
// ScanResultFact.UnmarshalJSON deserializes fact.Data into *depgraph.DepGraph when type is "depGraph".
207+
depGraph, ok := fact.Data.(*depgraph.DepGraph)
208+
if !ok {
209+
return nil, fmt.Errorf("expected fact.Data to be *depgraph.DepGraph, got %T", fact.Data)
210+
}
211+
if depGraph == nil {
212+
return nil, fmt.Errorf("depGraph is nil for fact with type 'depGraph'")
213+
}
214+
215+
data, err := workflowDataFromDepGraph(depGraph, normalisedTargetFile, scan.Identity.TargetFile)
208216
if err != nil {
209217
return nil, fmt.Errorf("failed to create workflow data: %w", err)
210218
}
@@ -216,7 +224,7 @@ func extractDepGraphsFromScans(scans []*snykclient.ScanResult, normalisedTargetF
216224
return depGraphList, nil
217225
}
218226

219-
func workflowDataFromDepGraph(depGraph any, normalisedTargetFile, targetFileFromPlugin string) (workflow.Data, error) {
227+
func workflowDataFromDepGraph(depGraph *depgraph.DepGraph, normalisedTargetFile, targetFileFromPlugin string) (workflow.Data, error) {
220228
depGraphBytes, err := json.Marshal(depGraph)
221229
if err != nil {
222230
return nil, fmt.Errorf("failed to marshal depgraph data: %w", err)

0 commit comments

Comments
 (0)