Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.7.0] - 2026-03-11
### Added
- Added support for Golang packages lookup via `GolangProjects` model and `pkg.go.dev` integration
Comment on lines +10 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep 0.7.0 under [Unreleased] until release day.

This entry is dated March 11, 2026, but the PR is still open on March 10, 2026. Pre-dating the section makes the changelog read as if 0.7.0 has already shipped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 10 - 12, The changelog entry for version header
"## [0.7.0] - 2026-03-11" is pre-dated; keep the 0.7.0 changes under an
"[Unreleased]" section until release day by moving the "Added" bullet
referencing the GolangProjects model and pkg.go.dev integration into the "##
[Unreleased]" section (or rename the existing header to "## [Unreleased]") and
remove the release date, so the file no longer presents 0.7.0 as already
shipped.


## [0.6.0] - 2026-03-09
### Changed
- Improved version query in `GetURLsByPurlNameTypeVersion` to cover semver "v" prefix variants using new `SemverTogglePrefix` helper
Expand Down Expand Up @@ -71,4 +75,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[0.4.0]: https://github.com/scanoss/go-models/compare/v0.3.0...v0.4.0
[0.5.0]: https://github.com/scanoss/go-models/compare/v0.4.0...v0.5.0
[0.5.1]: https://github.com/scanoss/go-models/compare/v0.5.0...v0.5.1
[0.6.0]: https://github.com/scanoss/go-models/compare/v0.5.1...v0.6.0
[0.6.0]: https://github.com/scanoss/go-models/compare/v0.5.1...v0.6.0
[0.7.0]: https://github.com/scanoss/go-models/compare/v0.6.0...v0.7.0
33 changes: 24 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,38 @@ require (
)

require (
github.com/PuerkitoBio/goquery v1.7.1 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/antchfx/htmlquery v1.2.4 // indirect
github.com/antchfx/xmlquery v1.3.7 // indirect
github.com/antchfx/xpath v1.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gocolly/colly/v2 v2.1.0 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/guseggert/pkggodev-client v0.0.0-20240318140526-cdb0034504cf // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/package-url/packageurl-go v0.1.3 // indirect
github.com/package-url/packageurl-go v0.1.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/scanoss/go-grpc-helper v0.13.0 // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
Expand Down
104 changes: 104 additions & 0 deletions go.sum

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions internal/testutils/mock/golang_projects.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
DROP TABLE IF EXISTS golang_projects;
CREATE TABLE golang_projects
(
component text not null,
version text not null,
version_id integer not null,
version_date text not null,
is_module boolean,
is_package boolean,
license text not null,
license_id integer not null,
has_valid_go_mod_file boolean,
has_redistributable_license boolean,
has_tagged_version boolean,
has_stable_version boolean,
repository text not null,
is_indexed boolean,
purl_name text not null,
mine_id integer not null,
index_timestamp text not null,
primary key (purl_name, version)
);

INSERT INTO golang_projects (component, version, version_id, version_date, license, license_id, repository, purl_name, mine_id, index_timestamp, is_indexed) VALUES ('github.com/scanoss/papi', 'v0.0.1', 5958021, '', 'MIT', 5614, 'github.com/scanoss/papi', 'github.com/scanoss/papi', 45, '2022-02-21T19:51:21.112979Z', True);
INSERT INTO golang_projects (component, version, version_id, version_date, license, license_id, repository, purl_name, mine_id, index_timestamp, is_indexed) VALUES ('google.golang.org/grpc', 'v1.19.0', 5193086, '', 'Apache-2.0', 552, 'github.com/grpc/grpc-go', 'google.golang.org/grpc', 45, '2022-05-09T20:17:02.339878Z', True);
INSERT INTO golang_projects (component, version, version_id, version_date, license, license_id, repository, purl_name, mine_id, index_timestamp, is_indexed) VALUES ('google.golang.org/grpc', 'v1.7.0', 11640350, '', '', 9999, 'github.com/grpc/grpc-go', 'google.golang.org/grpc', 45, '2023-11-24T20:17:02.339878Z', True);
83 changes: 81 additions & 2 deletions pkg/models/all_urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import (
"context"
"errors"
"fmt"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/jmoiron/sqlx"
"github.com/scanoss/go-models/pkg/helpers"
purlutils "github.com/scanoss/go-purl-helper/pkg"
)

// AllUrlsModel provides database access for URL information.
Expand Down Expand Up @@ -51,6 +53,20 @@ func NewAllURLModel(db *sqlx.DB) *AllUrlsModel {
}
}

// semverTogglePrefix returns the alternate version string by toggling the "v" prefix.
func semverTogglePrefix(version string) string {
if len(version) == 0 {
return version
}
if _, err := semver.NewVersion(version); err == nil {
if version[0] != 'v' {
return "v" + version
}
return strings.TrimLeft(version, "v")
}
return version
}

// GetURLsByPurlNameType retrieves all component URLs matching the specified PURL name and type.
func (m *AllUrlsModel) GetURLsByPurlNameType(ctx context.Context, purlName, purlType string) ([]AllURL, error) {
s := ctxzap.Extract(ctx).Sugar()
Expand Down Expand Up @@ -99,7 +115,7 @@ func (m *AllUrlsModel) GetURLsByPurlNameTypeVersion(ctx context.Context, purlNam
s.Error("Please specify a valid Purl Version to query")
return nil, errors.New("please specify a valid Purl Version to query")
}
semverV := helpers.SemverTogglePrefix(purlVersion)
semverV := semverTogglePrefix(purlVersion)

// This query is same as GetURLsByPurlNameType but adds a WHERE clause for versions
query := "SELECT component, v.version_name AS version, v.semver AS semver," +
Expand All @@ -120,3 +136,66 @@ func (m *AllUrlsModel) GetURLsByPurlNameTypeVersion(ctx context.Context, purlNam
s.Debugf("Found %v results for %v, %v, %v.", len(allUrls), purlType, purlName, purlVersion)
return allUrls, nil
}

// PickOneUrl takes the potential matching component/versions and selects the most appropriate one.
//

func PickOneUrl(ctx context.Context, allUrls []AllURL, purlName, purlType, purlReq string) (AllURL, error) {
s := ctxzap.Extract(ctx).Sugar()

if len(allUrls) == 0 {
s.Infof("No component match (in urls) found for %v, %v", purlName, purlType)
return AllURL{}, nil
}

var c *semver.Constraints
if len(purlReq) > 0 {
s.Debugf("Building version constraint for %v: %v", purlName, purlReq)
var err error
c, err = semver.NewConstraint(purlReq)
if err != nil {
s.Warnf("Encountered an issue parsing version constraint string '%v' (%v,%v): %v", purlReq, purlName, purlType, err)
}
Comment on lines +151 to +158
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 '\bPickOneUrl\s*\('

Repository: scanoss/go-models

Length of output: 2047


🏁 Script executed:

cat -n pkg/models/all_urls.go | sed -n '143,200p'

Repository: scanoss/go-models

Length of output: 2301


🏁 Script executed:

cat -n pkg/models/golang_projects.go | sed -n '115,125p'

Repository: scanoss/go-models

Length of output: 557


🏁 Script executed:

cat -n pkg/models/golang_projects.go | sed -n '170,180p'

Repository: scanoss/go-models

Length of output: 466


🏁 Script executed:

cat -n pkg/models/golang_projects.go | sed -n '95,120p'

Repository: scanoss/go-models

Length of output: 1377


🏁 Script executed:

cat -n pkg/models/golang_projects.go | sed -n '139,176p'

Repository: scanoss/go-models

Length of output: 2047


🏁 Script executed:

cat -n pkg/models/golang_projects.go | sed -n '80,95p'

Repository: scanoss/go-models

Length of output: 1028


🏁 Script executed:

cat -n pkg/models/golang_projects.go | sed -n '122,130p'

Repository: scanoss/go-models

Length of output: 635


Return error when version constraint parsing fails.

If semver.NewConstraint fails at line 155, c remains nil and line 182's constraint check is skipped, allowing the function to return the highest available version without validating it against the requirement. This silently masks malformed requirement strings. Since the calling functions GetGolangUrlsByPurlNameType() (line 119) and GetGolangUrlsByPurlNameTypeVersion() (line 175) both return (AllURL, error) and other callers like component.go:130 already handle PickOneUrl errors, the error should propagate up instead.

Proposed fix
 	c, err = semver.NewConstraint(purlReq)
 	if err != nil {
 		s.Warnf("Encountered an issue parsing version constraint string '%v' (%v,%v): %v", purlReq, purlName, purlType, err)
+		return AllURL{}, fmt.Errorf("invalid version constraint %q: %w", purlReq, err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/models/all_urls.go` around lines 151 - 158, The version-constraint
parsing error from semver.NewConstraint is currently only logged and ignored
(leaving c nil), which allows invalid requirement strings to be silently
bypassed; change the error handling in the block where
semver.NewConstraint(purlReq) is called so that on err you immediately return an
error (e.g., return empty AllURL and fmt.Errorf with context including purlReq,
purlName, purlType and err) instead of just s.Warnf; this makes
GetGolangUrlsByPurlNameType/GetGolangUrlsByPurlNameTypeVersion propagate the
parsing failure to callers and prevents skipping the constraint check that
relies on c.

}

zeroVersion, _ := semver.NewVersion("v0.0.0")
var bestVersion *semver.Version
var bestURL AllURL

s.Debugf("Checking versions...")
for _, url := range allUrls {
if len(url.SemVer) == 0 && len(url.Version) == 0 {
s.Infof("Skipping match as it doesn't have a version: %#v", url)
continue
}

v, err := semver.NewVersion(url.Version)
if err != nil && len(url.SemVer) > 0 {
s.Debugf("Failed to parse SemVer: '%v'. Trying Version instead: %v (%v)", url.Version, url.SemVer, err)
v, err = semver.NewVersion(url.SemVer)
}
if err != nil {
s.Warnf("Encountered an issue parsing version string '%v' (%v) for %v: %v. Using v0.0.0", url.Version, url.SemVer, url, err)
v = zeroVersion
}

if c != nil && !c.Check(v) {
continue
}

if bestVersion == nil || v.GreaterThan(bestVersion) {
bestVersion = v
bestURL = url
}
}

if bestVersion == nil { // TODO should we return the latest version anyway?
s.Warnf("No component match found for %v, %v after filter %v", purlName, purlType, purlReq)
return AllURL{}, nil
}

s.Debugf("Selected highest version: %v", bestVersion)
bestURL.URL, _ = purlutils.ProjectUrl(purlName, purlType)
s.Debugf("Selected version: %#v", bestURL)
return bestURL, nil
}
Loading
Loading