Skip to content

Commit bfdba29

Browse files
authored
refactor: deduplicate "versions exist" logic (#388)
The majority of implementations are the same just with different keys for getting the versions from the JSON response, so we can easily handle this in a single function --------- Signed-off-by: Gareth Jones <[email protected]>
1 parent 824f86a commit bfdba29

File tree

1 file changed

+58
-212
lines changed

1 file changed

+58
-212
lines changed

tools/osv-linter/internal/pkgchecker/version_check.go

Lines changed: 58 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,42 @@ import (
1515
"golang.org/x/mod/semver"
1616
)
1717

18-
// Confirm that all specified versions of a package exist in crates.io.
19-
func versionsExistInCrates(pkg string, versions []string) error {
20-
packageInstanceURL := fmt.Sprintf("%s/%s", EcosystemBaseURLs["crates.io"], pkg)
21-
18+
func fetchPackageData(packageInstanceURL string) ([]byte, error) {
2219
resp, err := faulttolerant.Get(packageInstanceURL)
2320
if err != nil {
24-
return fmt.Errorf("unable to validate package: %v", err)
21+
return nil, fmt.Errorf("unable to validate package: %v", err)
2522
}
2623
defer resp.Body.Close()
2724
if resp.StatusCode != http.StatusOK {
28-
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
25+
return nil, fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
2926
}
3027

31-
// Parse the known versions from the JSON.
32-
respJSON, err := io.ReadAll(resp.Body)
28+
return io.ReadAll(resp.Body)
29+
}
30+
31+
// Confirm that all specified versions of a package exist in a registry
32+
func versionsExistInGeneric(
33+
pkg string,
34+
versions []string,
35+
eco string,
36+
packageInstanceURL string,
37+
versionsPath string,
38+
) error {
39+
respJSON, err := fetchPackageData(packageInstanceURL)
3340
if err != nil {
3441
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
3542
}
43+
3644
// Fetch all known versions of package.
3745
versionsInRepository := []string{}
38-
releases := gjson.GetBytes(respJSON, "versions")
39-
releases.ForEach(func(key, value gjson.Result) bool {
40-
versionsInRepository = append(versionsInRepository, value.Get("num").String())
41-
return true // keep iterating.
42-
})
46+
for _, result := range gjson.GetBytes(respJSON, versionsPath).Array() {
47+
versionsInRepository = append(versionsInRepository, result.String())
48+
}
4349
// Determine which referenced versions are missing.
4450
versionsMissing := []string{}
4551
for _, versionToCheckFor := range versions {
4652
versionFound := false
47-
vc, err := semantic.Parse(versionToCheckFor, "crates.io")
53+
vc, err := semantic.Parse(versionToCheckFor, eco)
4854
if err != nil {
4955
versionsMissing = append(versionsMissing, versionToCheckFor)
5056
continue
@@ -61,12 +67,24 @@ func versionsExistInCrates(pkg string, versions []string) error {
6167
versionsMissing = append(versionsMissing, versionToCheckFor)
6268
}
6369
if len(versionsMissing) > 0 {
64-
return &MissingVersionsError{Package: pkg, Ecosystem: "crates.io", Missing: versionsMissing, Known: versionsInRepository}
70+
return &MissingVersionsError{Package: pkg, Ecosystem: eco, Missing: versionsMissing, Known: versionsInRepository}
6571
}
6672

6773
return nil
6874
}
6975

76+
// Confirm that all specified versions of a package exist in crates.io.
77+
func versionsExistInCrates(pkg string, versions []string) error {
78+
packageInstanceURL := fmt.Sprintf("%s/%s", EcosystemBaseURLs["crates.io"], pkg)
79+
80+
return versionsExistInGeneric(
81+
pkg, versions,
82+
"crates.io",
83+
packageInstanceURL,
84+
"versions.#.num",
85+
)
86+
}
87+
7088
// Confirm that all specified versions of a package exist in Go.
7189
func versionsExistInGo(pkg string, versions []string) error {
7290
if pkg == "stdlib" || pkg == "toolchain" {
@@ -81,18 +99,7 @@ func versionsExistInGo(pkg string, versions []string) error {
8199

82100
packageInstanceURL := fmt.Sprintf("%s/%s/@v/list", EcosystemBaseURLs["Go"], pkg)
83101

84-
// This 404's for non-existent packages.
85-
resp, err := faulttolerant.Get(packageInstanceURL)
86-
if err != nil {
87-
return fmt.Errorf("unable to validate package: %v", err)
88-
}
89-
defer resp.Body.Close()
90-
if resp.StatusCode != http.StatusOK {
91-
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
92-
}
93-
94-
// Load the known versions from the list provided.
95-
respBytes, err := io.ReadAll(resp.Body)
102+
respBytes, err := fetchPackageData(packageInstanceURL)
96103
if err != nil {
97104
return fmt.Errorf("unable to retrieve versions for for %q: %v", pkg, err)
98105
}
@@ -185,104 +192,24 @@ func goVersionsExist(versions []string) error {
185192
func versionsExistInNpm(pkg string, versions []string) error {
186193
packageInstanceURL := fmt.Sprintf("%s/%s", EcosystemBaseURLs["npm"], pkg)
187194

188-
resp, err := faulttolerant.Get(packageInstanceURL)
189-
if err != nil {
190-
return fmt.Errorf("unable to validate package: %v", err)
191-
}
192-
defer resp.Body.Close()
193-
if resp.StatusCode != http.StatusOK {
194-
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
195-
}
196-
197-
// Parse the known versions from the JSON.
198-
respJSON, err := io.ReadAll(resp.Body)
199-
if err != nil {
200-
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
201-
}
202-
// Fetch all known versions of package.
203-
versionsInRepository := []string{}
204-
releases := gjson.GetBytes(respJSON, "versions.@keys")
205-
releases.ForEach(func(key, value gjson.Result) bool {
206-
versionsInRepository = append(versionsInRepository, value.String())
207-
return true // keep iterating.
208-
})
209-
// Determine which referenced versions are missing.
210-
versionsMissing := []string{}
211-
for _, versionToCheckFor := range versions {
212-
versionFound := false
213-
vc, err := semantic.Parse(versionToCheckFor, "npm")
214-
if err != nil {
215-
versionsMissing = append(versionsMissing, versionToCheckFor)
216-
continue
217-
}
218-
for _, pkgversion := range versionsInRepository {
219-
if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil {
220-
versionFound = true
221-
break
222-
}
223-
}
224-
if versionFound {
225-
continue
226-
}
227-
versionsMissing = append(versionsMissing, versionToCheckFor)
228-
}
229-
if len(versionsMissing) > 0 {
230-
return &MissingVersionsError{Package: pkg, Ecosystem: "npm", Missing: versionsMissing, Known: versionsInRepository}
231-
}
232-
233-
return nil
195+
return versionsExistInGeneric(
196+
pkg, versions,
197+
"npm",
198+
packageInstanceURL,
199+
"versions.@keys",
200+
)
234201
}
235202

236203
// Confirm that all specified versions of a package exist in Packagist.
237204
func versionsExistInPackagist(pkg string, versions []string) error {
238205
packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["Packagist"], pkg)
239206

240-
resp, err := faulttolerant.Get(packageInstanceURL)
241-
if err != nil {
242-
return fmt.Errorf("unable to validate package: %v", err)
243-
}
244-
defer resp.Body.Close()
245-
if resp.StatusCode != http.StatusOK {
246-
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
247-
}
248-
249-
// Parse the known versions from the JSON.
250-
respJSON, err := io.ReadAll(resp.Body)
251-
if err != nil {
252-
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
253-
}
254-
// Fetch all known versions of package.
255-
versionsInRepository := []string{}
256-
releases := gjson.GetBytes(respJSON, fmt.Sprintf("packages.%s", pkg))
257-
releases.ForEach(func(key, value gjson.Result) bool {
258-
versionsInRepository = append(versionsInRepository, value.Get("version").String())
259-
return true // keep iterating.
260-
})
261-
// Determine which referenced versions are missing.
262-
versionsMissing := []string{}
263-
for _, versionToCheckFor := range versions {
264-
versionFound := false
265-
vc, err := semantic.Parse(versionToCheckFor, "Packagist")
266-
if err != nil {
267-
versionsMissing = append(versionsMissing, versionToCheckFor)
268-
continue
269-
}
270-
for _, pkgversion := range versionsInRepository {
271-
if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil {
272-
versionFound = true
273-
break
274-
}
275-
}
276-
if versionFound {
277-
continue
278-
}
279-
versionsMissing = append(versionsMissing, versionToCheckFor)
280-
}
281-
if len(versionsMissing) > 0 {
282-
return &MissingVersionsError{Package: pkg, Ecosystem: "Packagist", Missing: versionsMissing, Known: versionsInRepository}
283-
}
284-
285-
return nil
207+
return versionsExistInGeneric(
208+
pkg, versions,
209+
"Packagist",
210+
packageInstanceURL,
211+
fmt.Sprintf("packages.%s.#.version", pkg),
212+
)
286213
}
287214

288215
// Confirm that all specified versions of a package exist in PyPI.
@@ -292,103 +219,22 @@ func versionsExistInPyPI(pkg string, versions []string) error {
292219
pkgNormalized := strings.ToLower(pythonNormalizationRegex.ReplaceAllString(pkg, "-"))
293220
packageInstanceURL := fmt.Sprintf("%s/%s/json", EcosystemBaseURLs["PyPI"], pkgNormalized)
294221

295-
// This 404's for non-existent packages.
296-
resp, err := faulttolerant.Get(packageInstanceURL)
297-
if err != nil {
298-
return fmt.Errorf("unable to validate package: %v", err)
299-
}
300-
defer resp.Body.Close()
301-
if resp.StatusCode != http.StatusOK {
302-
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
303-
}
304-
305-
// Parse the known versions from the JSON.
306-
respJSON, err := io.ReadAll(resp.Body)
307-
if err != nil {
308-
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
309-
}
310-
// Fetch all known versions of package.
311-
versionsInPyPy := []string{}
312-
releases := gjson.GetBytes(respJSON, "releases.@keys")
313-
releases.ForEach(func(key, value gjson.Result) bool {
314-
versionsInPyPy = append(versionsInPyPy, value.String())
315-
return true // keep iterating.
316-
})
317-
// Determine which referenced versions are missing.
318-
versionsMissing := []string{}
319-
for _, versionToCheckFor := range versions {
320-
versionFound := false
321-
vc, err := semantic.Parse(versionToCheckFor, "PyPI")
322-
if err != nil {
323-
versionsMissing = append(versionsMissing, versionToCheckFor)
324-
continue
325-
}
326-
for _, pkgversion := range versionsInPyPy {
327-
if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil {
328-
versionFound = true
329-
break
330-
}
331-
}
332-
if versionFound {
333-
continue
334-
}
335-
versionsMissing = append(versionsMissing, versionToCheckFor)
336-
}
337-
if len(versionsMissing) > 0 {
338-
return &MissingVersionsError{Package: pkg, Ecosystem: "PyPI", Missing: versionsMissing, Known: versionsInPyPy}
339-
}
340-
341-
return nil
222+
return versionsExistInGeneric(
223+
pkg, versions,
224+
"PyPI",
225+
packageInstanceURL,
226+
"releases.@keys",
227+
)
342228
}
343229

344230
// Confirm that all specified versions of a package exist in RubyGems.
345231
func versionsExistInRubyGems(pkg string, versions []string) error {
346232
packageInstanceURL := fmt.Sprintf("%s/versions/%s.json", EcosystemBaseURLs["RubyGems"], pkg)
347233

348-
resp, err := faulttolerant.Get(packageInstanceURL)
349-
if err != nil {
350-
return fmt.Errorf("unable to validate package: %v", err)
351-
}
352-
defer resp.Body.Close()
353-
if resp.StatusCode != http.StatusOK {
354-
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
355-
}
356-
357-
// Parse the known versions from the JSON.
358-
respJSON, err := io.ReadAll(resp.Body)
359-
if err != nil {
360-
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
361-
}
362-
// Fetch all known versions of package.
363-
versionsInRepository := []string{}
364-
releases := gjson.GetBytes(respJSON, "@this")
365-
releases.ForEach(func(key, value gjson.Result) bool {
366-
versionsInRepository = append(versionsInRepository, value.Get("number").String())
367-
return true // keep iterating.
368-
})
369-
// Determine which referenced versions are missing.
370-
versionsMissing := []string{}
371-
for _, versionToCheckFor := range versions {
372-
versionFound := false
373-
vc, err := semantic.Parse(versionToCheckFor, "RubyGems")
374-
if err != nil {
375-
versionsMissing = append(versionsMissing, versionToCheckFor)
376-
continue
377-
}
378-
for _, pkgversion := range versionsInRepository {
379-
if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil {
380-
versionFound = true
381-
break
382-
}
383-
}
384-
if versionFound {
385-
continue
386-
}
387-
versionsMissing = append(versionsMissing, versionToCheckFor)
388-
}
389-
if len(versionsMissing) > 0 {
390-
return &MissingVersionsError{Package: pkg, Ecosystem: "RubyGems", Missing: versionsMissing, Known: versionsInRepository}
391-
}
392-
393-
return nil
234+
return versionsExistInGeneric(
235+
pkg, versions,
236+
"RubyGems",
237+
packageInstanceURL,
238+
"@this.#.number",
239+
)
394240
}

0 commit comments

Comments
 (0)