From ab2e73ae083bfe3742f2c16262675f16b5c66628 Mon Sep 17 00:00:00 2001 From: Federico Loi Date: Tue, 24 Dec 2024 15:34:59 +0100 Subject: [PATCH 1/3] Implementation of Package.resolved Swift Extractor --- docs/supported_inventory_types.md | 2 + .../swift/packageresolved/packageresolved.go | 196 +++++++++++++ .../packageresolved/packageresolved_test.go | 264 ++++++++++++++++++ .../swift/packageresolved/testdata/empty | 0 .../swift/packageresolved/testdata/invalid | 2 + .../swift/packageresolved/testdata/valid | 32 +++ extractor/filesystem/list/list.go | 5 + 7 files changed, 501 insertions(+) create mode 100644 extractor/filesystem/language/swift/packageresolved/packageresolved.go create mode 100644 extractor/filesystem/language/swift/packageresolved/packageresolved_test.go create mode 100644 extractor/filesystem/language/swift/packageresolved/testdata/empty create mode 100644 extractor/filesystem/language/swift/packageresolved/testdata/invalid create mode 100644 extractor/filesystem/language/swift/packageresolved/testdata/valid diff --git a/docs/supported_inventory_types.md b/docs/supported_inventory_types.md index 63bc8d2f..bdc6eccb 100644 --- a/docs/supported_inventory_types.md +++ b/docs/supported_inventory_types.md @@ -52,6 +52,8 @@ SCALIBR supports extracting software package information from a variety of OS an * Lockfiles: Gemfile.lock (OSV) * Rust * Cargo.lock +* Swift + * Package.resolved ## Container inventory diff --git a/extractor/filesystem/language/swift/packageresolved/packageresolved.go b/extractor/filesystem/language/swift/packageresolved/packageresolved.go new file mode 100644 index 00000000..d7699861 --- /dev/null +++ b/extractor/filesystem/language/swift/packageresolved/packageresolved.go @@ -0,0 +1,196 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package resolved extracts Package.resolved files +package packageresolved + +import ( + "context" + "encoding/json" + "fmt" + "io" + "path/filepath" + + "github.com/google/osv-scalibr/extractor" + "github.com/google/osv-scalibr/extractor/filesystem" + "github.com/google/osv-scalibr/extractor/filesystem/internal/units" + "github.com/google/osv-scalibr/plugin" + "github.com/google/osv-scalibr/purl" + "github.com/google/osv-scalibr/stats" +) + +const ( + // Name is the unique name of this extractor. + Name = "swift/packageresolved" +) + +// Config is the configuration for the Extractor. +type Config struct { + // Stats is a stats collector for reporting metrics. + Stats stats.Collector + // MaxFileSizeBytes is the maximum file size this extractor will process. + MaxFileSizeBytes int64 +} + +// DefaultConfig returns the default configuration for the extractor. +func DefaultConfig() Config { + return Config{ + Stats: nil, + MaxFileSizeBytes: 10 * units.MiB, + } +} + +// Extractor extracts packages from inside a Package.resolved. +type Extractor struct { + stats stats.Collector + maxFileSizeBytes int64 +} + +// New returns a Package.resolved extractor. +func New(cfg Config) *Extractor { + return &Extractor{ + stats: cfg.Stats, + maxFileSizeBytes: cfg.MaxFileSizeBytes, + } +} + +// Config returns the configuration of the extractor. +func (e Extractor) Config() Config { + return Config{ + Stats: e.stats, + MaxFileSizeBytes: e.maxFileSizeBytes, + } +} + +// Name of the extractor. +func (e Extractor) Name() string { return Name } + +// Version of the extractor. +func (e Extractor) Version() int { return 0 } + +// Requirements of the extractor. +func (e Extractor) Requirements() *plugin.Capabilities { return &plugin.Capabilities{} } + +// FileRequired checks if the file is named "Package.resolved". +func (e Extractor) FileRequired(api filesystem.FileAPI) bool { + path := api.Path() + if filepath.Base(path) != "Package.resolved" { + return false + } + + fileinfo, err := api.Stat() + if err != nil { + return false + } + if e.maxFileSizeBytes > 0 && fileinfo.Size() > e.maxFileSizeBytes { + e.reportFileRequired(path, fileinfo.Size(), stats.FileRequiredResultSizeLimitExceeded) + return false + } + + e.reportFileRequired(path, fileinfo.Size(), stats.FileRequiredResultOK) + return true +} + +func (e Extractor) reportFileRequired(path string, fileSizeBytes int64, result stats.FileRequiredResult) { + if e.stats == nil { + return + } + e.stats.AfterFileRequired(e.Name(), &stats.FileRequiredStats{ + Path: path, + Result: result, + FileSizeBytes: fileSizeBytes, + }) +} + +// Extract parses and extracts dependency data from a Package.resolved file. +func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) { + inventory, err := e.extractFromInput(ctx, input) + if e.stats != nil { + var fileSizeBytes int64 + if input.Info != nil { + fileSizeBytes = input.Info.Size() + } + e.stats.AfterFileExtracted(e.Name(), &stats.FileExtractedStats{ + Path: input.Path, + Result: filesystem.ExtractorErrorToFileExtractedResult(err), + FileSizeBytes: fileSizeBytes, + }) + } + return inventory, err +} + +func (e Extractor) extractFromInput(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) { + pkgs, err := Parse(input.Reader) + if err != nil { + return nil, err + } + + var inventories []*extractor.Inventory + for _, pkg := range pkgs { + i := &extractor.Inventory{ + Name: pkg.Name, + Version: pkg.Version, + Locations: []string{ + input.Path, + }, + } + inventories = append(inventories, i) + } + + return inventories, nil +} + +// Package represents a parsed package entry from the Package.resolved file. +type Package struct { + Name string + Version string +} + +// Parse reads and parses a Package.resolved file for package details. +func Parse(r io.Reader) ([]Package, error) { + var resolvedFile struct { + Pins []struct { + Package string `json:"identity"` + State struct { + Version string `json:"version"` + } `json:"state"` + } `json:"pins"` + } + + if err := json.NewDecoder(r).Decode(&resolvedFile); err != nil { + return nil, fmt.Errorf("failed to parse Package.resolved: %w", err) + } + + var packages []Package + for _, pin := range resolvedFile.Pins { + packages = append(packages, Package{ + Name: pin.Package, + Version: pin.State.Version, + }) + } + + return packages, nil +} + +// ToPURL converts an inventory item into a PURL. +func (e Extractor) ToPURL(i *extractor.Inventory) *purl.PackageURL { + return &purl.PackageURL{ + Type: purl.TypeCocoapods, + Name: i.Name, + Version: i.Version, + } +} + +// Ecosystem returns the OSV Ecosystem for Swift. +func (Extractor) Ecosystem(i *extractor.Inventory) string { return "Cocoapods" } diff --git a/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go b/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go new file mode 100644 index 00000000..172bd239 --- /dev/null +++ b/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go @@ -0,0 +1,264 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package packageresolved_test + +import ( + "context" + "io/fs" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/osv-scalibr/extractor" + "github.com/google/osv-scalibr/extractor/filesystem" + "github.com/google/osv-scalibr/extractor/filesystem/internal/units" + "github.com/google/osv-scalibr/extractor/filesystem/language/swift/packageresolved" + "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" + scalibrfs "github.com/google/osv-scalibr/fs" + "github.com/google/osv-scalibr/purl" + "github.com/google/osv-scalibr/stats" + "github.com/google/osv-scalibr/testing/fakefs" + "github.com/google/osv-scalibr/testing/testcollector" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + cfg packageresolved.Config + wantCfg packageresolved.Config + }{ + { + name: "default", + cfg: packageresolved.DefaultConfig(), + wantCfg: packageresolved.Config{ + MaxFileSizeBytes: 10 * units.MiB, + }, + }, + { + name: "custom", + cfg: packageresolved.Config{ + MaxFileSizeBytes: 10, + }, + wantCfg: packageresolved.Config{ + MaxFileSizeBytes: 10, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := packageresolved.New(tt.cfg) + if !reflect.DeepEqual(got.Config(), tt.wantCfg) { + t.Errorf("New(%+v).Config(): got %+v, want %+v", tt.cfg, got.Config(), tt.wantCfg) + } + }) + } +} + +func TestFileRequired(t *testing.T) { + tests := []struct { + name string + path string + fileSizeBytes int64 + maxFileSizeBytes int64 + wantRequired bool + wantResultMetric stats.FileRequiredResult + }{ + { + name: "Package.resolved file", + path: "Package.resolved", + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + { + name: "path Package.resolved file", + path: "path/to/my/Package.resolved", + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + { + name: "file not required", + path: "test.resolved", + wantRequired: false, + }, + { + name: "Package.resolved file required if file size < max file size", + path: "Package.resolved", + fileSizeBytes: 100 * units.KiB, + maxFileSizeBytes: 1000 * units.KiB, + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + { + name: "Package.resolved file required if file size == max file size", + path: "Package.resolved", + fileSizeBytes: 1000 * units.KiB, + maxFileSizeBytes: 1000 * units.KiB, + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + { + name: "Package.resolved file not required if file size > max file size", + path: "Package.resolved", + fileSizeBytes: 1000 * units.KiB, + maxFileSizeBytes: 100 * units.KiB, + wantRequired: false, + wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, + }, + { + name: "Package.resolved file required if max file size set to 0", + path: "Package.resolved", + fileSizeBytes: 100 * units.KiB, + maxFileSizeBytes: 0, + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + collector := testcollector.New() + var e filesystem.Extractor = packageresolved.New(packageresolved.Config{ + Stats: collector, + MaxFileSizeBytes: tt.maxFileSizeBytes, + }) + + fileSizeBytes := tt.fileSizeBytes + if fileSizeBytes == 0 { + fileSizeBytes = 1000 + } + + isRequired := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{ + FileName: filepath.Base(tt.path), + FileMode: fs.ModePerm, + FileSize: fileSizeBytes, + })) + if isRequired != tt.wantRequired { + t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, isRequired, tt.wantRequired) + } + + gotResultMetric := collector.FileRequiredResult(tt.path) + if tt.wantResultMetric != "" && gotResultMetric != tt.wantResultMetric { + t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) + } + }) + } +} + +func TestExtract(t *testing.T) { + tests := []struct { + name string + path string + osrelease string + cfg packageresolved.Config + wantInventory []*extractor.Inventory + wantErr error + wantResultMetric stats.FileExtractedResult + }{ + { + name: "valid Package.resolved file", + path: "testdata/valid", + wantInventory: []*extractor.Inventory{ + { + Name: "swift-algorithms", + Version: "1.0.0", + Locations: []string{"testdata/valid"}, + }, + { + Name: "swift-async-algorithms", + Version: "0.1.0", + Locations: []string{"testdata/valid"}, + }, + { + Name: "swift-atomics", + Version: "1.1.0", + Locations: []string{"testdata/valid"}, + }, + }, + wantResultMetric: stats.FileExtractedResultSuccess, + }, + { + name: "Package.resolved file not valid", + path: "testdata/invalid", + wantErr: cmpopts.AnyError, + wantResultMetric: stats.FileExtractedResultErrorUnknown, + }, + { + name: "Package.resolved file empty", + path: "testdata/empty", + wantErr: cmpopts.AnyError, + wantResultMetric: stats.FileExtractedResultErrorUnknown, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + collector := testcollector.New() + var e filesystem.Extractor = packageresolved.New(packageresolved.Config{ + Stats: collector, + MaxFileSizeBytes: 100, + }) + + d := t.TempDir() + + // Opening and Reading the Test File + r, err := os.Open(tt.path) + defer func() { + if err = r.Close(); err != nil { + t.Errorf("Close(): %v", err) + } + }() + if err != nil { + t.Fatal(err) + } + + info, err := os.Stat(tt.path) + if err != nil { + t.Fatalf("Failed to stat test file: %v", err) + } + + input := &filesystem.ScanInput{ + FS: scalibrfs.DirFS(d), Path: tt.path, Reader: r, Root: d, Info: info, + } + + got, err := e.Extract(context.Background(), input) + + if diff := cmp.Diff(tt.wantInventory, got); diff != "" { + t.Errorf("Inventory mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestToPURL(t *testing.T) { + e := packageresolved.Extractor{} + i := &extractor.Inventory{ + Name: "Name", + Version: "1.2.3", + Locations: []string{"location"}, + } + want := &purl.PackageURL{ + Type: purl.TypeCocoapods, + Name: "Name", + Version: "1.2.3", + } + got := e.ToPURL(i) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ToPURL(%v) (-want +got):\n%s", i, diff) + } +} diff --git a/extractor/filesystem/language/swift/packageresolved/testdata/empty b/extractor/filesystem/language/swift/packageresolved/testdata/empty new file mode 100644 index 00000000..e69de29b diff --git a/extractor/filesystem/language/swift/packageresolved/testdata/invalid b/extractor/filesystem/language/swift/packageresolved/testdata/invalid new file mode 100644 index 00000000..6127c7c0 --- /dev/null +++ b/extractor/filesystem/language/swift/packageresolved/testdata/invalid @@ -0,0 +1,2 @@ +not +json \ No newline at end of file diff --git a/extractor/filesystem/language/swift/packageresolved/testdata/valid b/extractor/filesystem/language/swift/packageresolved/testdata/valid new file mode 100644 index 00000000..efbf6701 --- /dev/null +++ b/extractor/filesystem/language/swift/packageresolved/testdata/valid @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "b14b7f4c528c942f121c8b860b9410b2bf57825e", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", + "version" : "0.1.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version" : "1.1.0" + } + } + ], + "version" : 2 +} \ No newline at end of file diff --git a/extractor/filesystem/list/list.go b/extractor/filesystem/list/list.go index f5933557..af89b04a 100644 --- a/extractor/filesystem/list/list.go +++ b/extractor/filesystem/list/list.go @@ -51,6 +51,7 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/language/ruby/gemfilelock" "github.com/google/osv-scalibr/extractor/filesystem/language/ruby/gemspec" "github.com/google/osv-scalibr/extractor/filesystem/language/rust/cargolock" + "github.com/google/osv-scalibr/extractor/filesystem/language/swift/packageresolved" "github.com/google/osv-scalibr/extractor/filesystem/os/apk" "github.com/google/osv-scalibr/extractor/filesystem/os/cos" "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg" @@ -115,6 +116,8 @@ var ( Dotnet []filesystem.Extractor = []filesystem.Extractor{packageslockjson.New(packageslockjson.DefaultConfig())} // PHP extractors. PHP []filesystem.Extractor = []filesystem.Extractor{&composerlock.Extractor{}} + // Swift extractors. + Swift []filesystem.Extractor = []filesystem.Extractor{packageresolved.Extractor{}} // Containers extractors. Containers []filesystem.Extractor = []filesystem.Extractor{containerd.New(containerd.DefaultConfig())} @@ -149,6 +152,7 @@ var ( Rust, Dotnet, SBOM, + Swift, OS, Containers, ) @@ -169,6 +173,7 @@ var ( "rust": Rust, "sbom": SBOM, + "swift": Swift, "os": OS, "containers": Containers, From a14cb3b12ad231a4e25f5602e76459591f6a4f18 Mon Sep 17 00:00:00 2001 From: Federico Loi Date: Mon, 13 Jan 2025 10:21:30 +0100 Subject: [PATCH 2/3] add fix and newlines --- .../packageresolved/packageresolved_test.go | 77 +++++++------------ .../swift/packageresolved/testdata/empty | 2 + .../swift/packageresolved/testdata/invalid | 3 +- .../swift/packageresolved/testdata/valid | 3 +- 4 files changed, 34 insertions(+), 51 deletions(-) diff --git a/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go b/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go index 172bd239..9a990532 100644 --- a/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go +++ b/extractor/filesystem/language/swift/packageresolved/packageresolved_test.go @@ -17,9 +17,7 @@ package packageresolved_test import ( "context" "io/fs" - "os" "path/filepath" - "reflect" "testing" "github.com/google/go-cmp/cmp" @@ -29,9 +27,9 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/internal/units" "github.com/google/osv-scalibr/extractor/filesystem/language/swift/packageresolved" "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" - scalibrfs "github.com/google/osv-scalibr/fs" "github.com/google/osv-scalibr/purl" "github.com/google/osv-scalibr/stats" + "github.com/google/osv-scalibr/testing/extracttest" "github.com/google/osv-scalibr/testing/fakefs" "github.com/google/osv-scalibr/testing/testcollector" ) @@ -63,8 +61,8 @@ func TestNew(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := packageresolved.New(tt.cfg) - if !reflect.DeepEqual(got.Config(), tt.wantCfg) { - t.Errorf("New(%+v).Config(): got %+v, want %+v", tt.cfg, got.Config(), tt.wantCfg) + if diff := cmp.Diff(tt.wantCfg, got.Config()); diff != "" { + t.Errorf("New(%+v).Config(): (-want +got):\n%s", tt.cfg, diff) } }) } @@ -161,19 +159,13 @@ func TestFileRequired(t *testing.T) { } func TestExtract(t *testing.T) { - tests := []struct { - name string - path string - osrelease string - cfg packageresolved.Config - wantInventory []*extractor.Inventory - wantErr error - wantResultMetric stats.FileExtractedResult - }{ + tests := []extracttest.TestTableEntry{ { - name: "valid Package.resolved file", - path: "testdata/valid", - wantInventory: []*extractor.Inventory{ + Name: "valid Package.resolved file", + InputConfig: extracttest.ScanInputMockConfig{ + Path: "testdata/valid", + }, + WantInventory: []*extractor.Inventory{ { Name: "swift-algorithms", Version: "1.0.0", @@ -190,56 +182,43 @@ func TestExtract(t *testing.T) { Locations: []string{"testdata/valid"}, }, }, - wantResultMetric: stats.FileExtractedResultSuccess, }, { - name: "Package.resolved file not valid", - path: "testdata/invalid", - wantErr: cmpopts.AnyError, - wantResultMetric: stats.FileExtractedResultErrorUnknown, + Name: "Package.resolved file not valid", + InputConfig: extracttest.ScanInputMockConfig{ + Path: "testdata/invalid", + }, + WantErr: cmpopts.AnyError, }, { - name: "Package.resolved file empty", - path: "testdata/empty", - wantErr: cmpopts.AnyError, - wantResultMetric: stats.FileExtractedResultErrorUnknown, + Name: "Package.resolved file empty", + InputConfig: extracttest.ScanInputMockConfig{ + Path: "testdata/empty", + }, + WantErr: cmpopts.AnyError, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.Name, func(t *testing.T) { collector := testcollector.New() var e filesystem.Extractor = packageresolved.New(packageresolved.Config{ Stats: collector, MaxFileSizeBytes: 100, }) - d := t.TempDir() - - // Opening and Reading the Test File - r, err := os.Open(tt.path) - defer func() { - if err = r.Close(); err != nil { - t.Errorf("Close(): %v", err) - } - }() - if err != nil { - t.Fatal(err) - } + scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig) + defer extracttest.CloseTestScanInput(t, scanInput) - info, err := os.Stat(tt.path) - if err != nil { - t.Fatalf("Failed to stat test file: %v", err) - } + got, err := e.Extract(context.Background(), &scanInput) - input := &filesystem.ScanInput{ - FS: scalibrfs.DirFS(d), Path: tt.path, Reader: r, Root: d, Info: info, + if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", e.Name(), tt.InputConfig.Path, diff) + return } - got, err := e.Extract(context.Background(), input) - - if diff := cmp.Diff(tt.wantInventory, got); diff != "" { - t.Errorf("Inventory mismatch (-want +got):\n%s", diff) + if diff := cmp.Diff(tt.WantInventory, got, cmpopts.SortSlices(extracttest.InventoryCmpLess)); diff != "" { + t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", e.Name(), tt.InputConfig.Path, diff) } }) } diff --git a/extractor/filesystem/language/swift/packageresolved/testdata/empty b/extractor/filesystem/language/swift/packageresolved/testdata/empty index e69de29b..139597f9 100644 --- a/extractor/filesystem/language/swift/packageresolved/testdata/empty +++ b/extractor/filesystem/language/swift/packageresolved/testdata/empty @@ -0,0 +1,2 @@ + + diff --git a/extractor/filesystem/language/swift/packageresolved/testdata/invalid b/extractor/filesystem/language/swift/packageresolved/testdata/invalid index 6127c7c0..da174273 100644 --- a/extractor/filesystem/language/swift/packageresolved/testdata/invalid +++ b/extractor/filesystem/language/swift/packageresolved/testdata/invalid @@ -1,2 +1,3 @@ not -json \ No newline at end of file +json + diff --git a/extractor/filesystem/language/swift/packageresolved/testdata/valid b/extractor/filesystem/language/swift/packageresolved/testdata/valid index efbf6701..c64750d6 100644 --- a/extractor/filesystem/language/swift/packageresolved/testdata/valid +++ b/extractor/filesystem/language/swift/packageresolved/testdata/valid @@ -29,4 +29,5 @@ } ], "version" : 2 -} \ No newline at end of file +} + From bfb8e664c8f411bea8cdb86ffae08993f9fadb7d Mon Sep 17 00:00:00 2001 From: Federico Loi Date: Fri, 17 Jan 2025 14:35:06 +0100 Subject: [PATCH 3/3] add fix --- .../language/swift/packageresolved/packageresolved.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extractor/filesystem/language/swift/packageresolved/packageresolved.go b/extractor/filesystem/language/swift/packageresolved/packageresolved.go index d7699861..e84d6232 100644 --- a/extractor/filesystem/language/swift/packageresolved/packageresolved.go +++ b/extractor/filesystem/language/swift/packageresolved/packageresolved.go @@ -131,7 +131,7 @@ func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) ([] } func (e Extractor) extractFromInput(ctx context.Context, input *filesystem.ScanInput) ([]*extractor.Inventory, error) { - pkgs, err := Parse(input.Reader) + pkgs, err := parse(input.Reader) if err != nil { return nil, err } @@ -152,13 +152,13 @@ func (e Extractor) extractFromInput(ctx context.Context, input *filesystem.ScanI } // Package represents a parsed package entry from the Package.resolved file. -type Package struct { +type pkg struct { Name string Version string } // Parse reads and parses a Package.resolved file for package details. -func Parse(r io.Reader) ([]Package, error) { +func parse(r io.Reader) ([]pkg, error) { var resolvedFile struct { Pins []struct { Package string `json:"identity"` @@ -172,9 +172,9 @@ func Parse(r io.Reader) ([]Package, error) { return nil, fmt.Errorf("failed to parse Package.resolved: %w", err) } - var packages []Package + var packages []pkg for _, pin := range resolvedFile.Pins { - packages = append(packages, Package{ + packages = append(packages, pkg{ Name: pin.Package, Version: pin.State.Version, })