From 002840c6e72321025e023aeb78b1869aafd77787 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 8 Aug 2023 21:37:32 -0400 Subject: [PATCH] refactor crate versions --- services/crates/crates-base.js | 41 ++++++++++++++++++++- services/crates/crates-downloads.service.js | 14 +++---- services/crates/crates-license.service.js | 16 ++------ services/crates/crates-license.spec.js | 13 +++++-- services/crates/crates-version.service.js | 4 +- 5 files changed, 61 insertions(+), 27 deletions(-) diff --git a/services/crates/crates-base.js b/services/crates/crates-base.js index 112befc83f62b..2f3c315b375f2 100644 --- a/services/crates/crates-base.js +++ b/services/crates/crates-base.js @@ -1,6 +1,6 @@ import Joi from 'joi' import { nonNegativeInteger } from '../validators.js' -import { BaseJsonService } from '../index.js' +import { BaseJsonService, InvalidParameter } from '../index.js' const keywords = ['Rust'] @@ -47,6 +47,45 @@ class BaseCratesService extends BaseJsonService { : `https://crates.io/api/v1/crates/${crate}?include=versions,downloads` return this._requestJson({ schema, url }) } + + /** get the default version for a crate using the same logic as crates.io + * this should be kept in sync with the crates.io implementation in + * https://github.com/rust-lang/crates.io/blob/20bbac9f5521cb4888ef63f8464fddb28fd973f5/app/models/crate.js#L34-L41 + * + * @param {object} crate the `json.crate` response from crates.io + * @returns {string} the default version string, or throws an error + */ + static defaultVersion(crate) { + if (crate) { + if (crate.max_stable_version) { + return crate.max_stable_version + } + if (crate.max_version && crate.max_version !== '0.0.0') { + return crate.max_version + } + } + throw new InvalidParameter({ + prettyMessage: + 'default version not found, possibly all versions were yanked', + }) + } + + /** get the default version object from the `json.versions` array. + * + * @param {object} crate the `json.crate` response from crates.io + * @param {object[]} versions the `json.versions` response from crates.io + * @returns {object} the default version object, or throws an error if not found + */ + static defaultVersionObject(crate, versions) { + const lastVerNum = BaseCratesService.defaultVersion(crate) + const version = versions.find(ver => ver.num === lastVerNum) + if (!version) { + throw new InvalidParameter({ + prettyMessage: 'version object not found in the versions array', + }) + } + return version + } } export { BaseCratesService, keywords } diff --git a/services/crates/crates-downloads.service.js b/services/crates/crates-downloads.service.js index db578880e1939..5a2b84a2851aa 100644 --- a/services/crates/crates-downloads.service.js +++ b/services/crates/crates-downloads.service.js @@ -68,16 +68,14 @@ export default class CratesDownloads extends BaseCratesService { transform({ variant, json }) { switch (variant) { case 'dv': - let lastVer = json.version if (json.crate) { - const lastVerNum = json.crate.max_stable_version - ? json.crate.max_stable_version - : json.crate.max_version - lastVer = - json.versions.find(ver => ver.num === lastVerNum) || - json.versions[0] + return BaseCratesService.defaultVersionObject( + json.crate, + json.versions, + ).downloads + } else { + return json.version.downloads } - return lastVer.downloads case 'dr': return json.crate.recent_downloads || 0 default: diff --git a/services/crates/crates-license.service.js b/services/crates/crates-license.service.js index 74dc0502af369..fede390d3a427 100644 --- a/services/crates/crates-license.service.js +++ b/services/crates/crates-license.service.js @@ -28,24 +28,16 @@ export default class CratesLicense extends BaseCratesService { return { message } } - static transform({ errors, version, versions, crate }) { + static transform({ errors, version, crate, versions }) { // crates.io returns a 200 response with an errors object in // error scenarios, e.g. https://crates.io/api/v1/crates/libc/0.1 if (errors) { throw new InvalidResponse({ prettyMessage: errors[0].detail }) } - let license - if (version) { - license = version.license - } else { - const lastVerNum = crate.max_stable_version - ? crate.max_stable_version - : crate.max_version - const lastVer = - versions.find(ver => ver.num === lastVerNum) || versions[0] - license = lastVer.license - } + const license = version + ? version.license + : BaseCratesService.defaultVersionObject(crate, versions).license if (!license) { throw new InvalidResponse({ prettyMessage: 'invalid null license' }) diff --git a/services/crates/crates-license.spec.js b/services/crates/crates-license.spec.js index 54259e3c9d2f9..5707c2784a0e7 100644 --- a/services/crates/crates-license.spec.js +++ b/services/crates/crates-license.spec.js @@ -6,11 +6,13 @@ import CratesLicense from './crates-license.service.js' describe('CratesLicense', function () { test(CratesLicense.transform, () => { given({ + crate: { max_stable_version: '1.0.0', max_version: '1.0.0' }, version: { num: '1.0.0', license: 'MIT' }, - versions: [{ license: 'MIT/Apache 2.0' }], + versions: [{ num: '1.0.0', license: 'MIT/Apache 2.0' }], }).expect({ license: 'MIT' }) given({ - versions: [{ license: 'MIT/Apache 2.0' }], + crate: { max_stable_version: '1.0.0', max_version: '1.0.0' }, + versions: [{ num: '1.0.0', license: 'MIT/Apache 2.0' }], }).expect({ license: 'MIT/Apache 2.0' }) }) @@ -31,7 +33,12 @@ describe('CratesLicense', function () { }) it('throws InvalidResponse on null license with latest version', function () { - expect(() => CratesLicense.transform({ versions: [{ license: null }] })) + expect(() => + CratesLicense.transform({ + crate: { max_stable_version: '1.0.0', max_version: '1.0.0' }, + versions: [{ num: '1.0.0', license: null }], + }), + ) .to.throw(InvalidResponse) .with.property('prettyMessage', 'invalid null license') }) diff --git a/services/crates/crates-version.service.js b/services/crates/crates-version.service.js index e5517899b1878..6bfa8cd692f59 100644 --- a/services/crates/crates-version.service.js +++ b/services/crates/crates-version.service.js @@ -19,9 +19,7 @@ export default class CratesVersion extends BaseCratesService { if (json.errors) { throw new InvalidResponse({ prettyMessage: json.errors[0].detail }) } - return json.crate.max_stable_version - ? json.crate.max_stable_version - : json.crate.max_version + return BaseCratesService.defaultVersion(json.crate) } async handle({ crate }) {