add tests for rust [crates] and refactor (#2462)
* add tests for rust [crates] and refactor
This commit is contained in:
55
services/crates/crates-base.js
Normal file
55
services/crates/crates-base.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const BaseJsonService = require('../base-json')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const keywords = ['Rust']
|
||||
|
||||
const crateSchema = Joi.object({
|
||||
crate: Joi.object({
|
||||
downloads: nonNegativeInteger,
|
||||
max_version: Joi.string().required(),
|
||||
}),
|
||||
versions: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
downloads: nonNegativeInteger,
|
||||
license: Joi.string().required(),
|
||||
})
|
||||
)
|
||||
.min(1)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
const versionSchema = Joi.object({
|
||||
version: Joi.object({
|
||||
downloads: nonNegativeInteger,
|
||||
num: Joi.string().required(),
|
||||
license: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required()
|
||||
|
||||
const errorSchema = Joi.object({
|
||||
errors: Joi.array()
|
||||
.items(Joi.object({ detail: Joi.string().required() }))
|
||||
.min(1)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
const schema = Joi.alternatives(crateSchema, versionSchema, errorSchema)
|
||||
|
||||
class BaseCratesService extends BaseJsonService {
|
||||
async fetch({ crate, version }) {
|
||||
const url = version
|
||||
? `https://crates.io/api/v1/crates/${crate}/${version}`
|
||||
: `https://crates.io/api/v1/crates/${crate}`
|
||||
return this._requestJson({ schema, url })
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'crates.io' }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BaseCratesService, keywords }
|
||||
101
services/crates/crates-downloads.service.js
Normal file
101
services/crates/crates-downloads.service.js
Normal file
@@ -0,0 +1,101 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
downloadCount: downloadCountColor,
|
||||
} = require('../../lib/color-formatters')
|
||||
const { metric } = require('../../lib/text-formatters')
|
||||
const { BaseCratesService, keywords } = require('./crates-base')
|
||||
|
||||
module.exports = class CratesDownloads extends BaseCratesService {
|
||||
static get category() {
|
||||
return 'downloads'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'crates',
|
||||
format: '(d|dv)/([A-Za-z0-9_-]+)(?:/([0-9.]+))?',
|
||||
capture: ['which', 'crate', 'version'],
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: 'd/:crate',
|
||||
namedParams: { crate: 'rustc-serialize' },
|
||||
staticExample: this.render({ downloads: 5000000 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: 'd/:crate/:version',
|
||||
namedParams: { crate: 'rustc-serialize', version: '0.3.24' },
|
||||
staticExample: this.render({ downloads: 2000000, version: '0.3.24' }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: 'dv/:crate',
|
||||
namedParams: { crate: 'rustc-serialize' },
|
||||
staticExample: this.render({ which: 'dv', downloads: 2000000 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: 'dv/:crate/:version',
|
||||
namedParams: { crate: 'rustc-serialize', version: '0.3.24' },
|
||||
staticExample: this.render({
|
||||
which: 'dv',
|
||||
downloads: 2000000,
|
||||
version: '0.3.24',
|
||||
}),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static _getLabel(version, which) {
|
||||
if (version) {
|
||||
return `downloads@${version}`
|
||||
} else {
|
||||
if (which === 'dv') {
|
||||
return 'downloads@latest'
|
||||
} else {
|
||||
return 'downloads'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static render({ which, downloads, version }) {
|
||||
return {
|
||||
label: this._getLabel(version, which),
|
||||
message: metric(downloads),
|
||||
color: downloadCountColor(downloads),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ which, crate, version }) {
|
||||
const json = await this.fetch({ crate, version })
|
||||
|
||||
if (json.errors) {
|
||||
/* a call like
|
||||
https://crates.io/api/v1/crates/libc/0.1
|
||||
or
|
||||
https://crates.io/api/v1/crates/libc/0.1.76
|
||||
returns a 200 OK with an errors object */
|
||||
return { message: json.errors[0].detail }
|
||||
}
|
||||
|
||||
let downloads
|
||||
if (which === 'dv') {
|
||||
downloads = json.version
|
||||
? json.version.downloads
|
||||
: json.versions[0].downloads
|
||||
} else {
|
||||
downloads = json.crate ? json.crate.downloads : json.version.downloads
|
||||
}
|
||||
return this.constructor.render({ which, downloads, version })
|
||||
}
|
||||
}
|
||||
44
services/crates/crates-downloads.tester.js
Normal file
44
services/crates/crates-downloads.tester.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
const ServiceTester = require('../service-tester')
|
||||
const { isMetric } = require('../test-validators')
|
||||
|
||||
const t = new ServiceTester({
|
||||
id: 'crates',
|
||||
title: 'crates.io',
|
||||
pathPrefix: '/crates',
|
||||
})
|
||||
module.exports = t
|
||||
|
||||
t.create('total downloads')
|
||||
.get('/d/libc.json')
|
||||
.expectJSONTypes({ name: 'downloads', value: isMetric })
|
||||
|
||||
t.create('total downloads (with version)')
|
||||
.get('/d/libc/0.2.31.json')
|
||||
.expectJSONTypes({
|
||||
name: 'downloads@0.2.31',
|
||||
value: isMetric,
|
||||
})
|
||||
|
||||
t.create('downloads for version')
|
||||
.get('/dv/libc.json')
|
||||
.expectJSONTypes({
|
||||
name: 'downloads@latest',
|
||||
value: isMetric,
|
||||
})
|
||||
|
||||
t.create('downloads for version (with version)')
|
||||
.get('/dv/libc/0.2.31.json')
|
||||
.expectJSONTypes({
|
||||
name: 'downloads@0.2.31',
|
||||
value: isMetric,
|
||||
})
|
||||
|
||||
t.create('downloads (invalid version)')
|
||||
.get('/d/libc/7.json')
|
||||
.expectJSON({ name: 'crates.io', value: 'invalid semver: 7' })
|
||||
|
||||
t.create('downloads (not found)')
|
||||
.get('/d/not-a-real-package.json')
|
||||
.expectJSON({ name: 'crates.io', value: 'not found' })
|
||||
61
services/crates/crates-license.service.js
Normal file
61
services/crates/crates-license.service.js
Normal file
@@ -0,0 +1,61 @@
|
||||
'use strict'
|
||||
|
||||
const { BaseCratesService, keywords } = require('./crates-base')
|
||||
|
||||
module.exports = class CratesLicense extends BaseCratesService {
|
||||
static get category() {
|
||||
return 'license'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'crates/l',
|
||||
format: '([A-Za-z0-9_-]+)(?:/([0-9.]+))?',
|
||||
capture: ['crate', 'version'],
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: ':crate',
|
||||
namedParams: { crate: 'rustc-serialize' },
|
||||
staticExample: this.render({ license: 'MIT/Apache-2.0' }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: ':crate/:version',
|
||||
namedParams: { crate: 'rustc-serialize', version: '0.3.24' },
|
||||
staticExample: this.render({ license: 'MIT/Apache-2.0' }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ license }) {
|
||||
return {
|
||||
label: 'license',
|
||||
message: license,
|
||||
color: 'blue',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ crate, version }) {
|
||||
const json = await this.fetch({ crate, version })
|
||||
|
||||
if (json.errors) {
|
||||
/* a call like
|
||||
https://crates.io/api/v1/crates/libc/0.1
|
||||
or
|
||||
https://crates.io/api/v1/crates/libc/0.1.76
|
||||
returns a 200 OK with an errors object */
|
||||
return { message: json.errors[0].detail }
|
||||
}
|
||||
|
||||
return this.constructor.render({
|
||||
license: json.version ? json.version.license : json.versions[0].license,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,9 @@ t.create('license')
|
||||
.expectJSON({ name: 'license', value: 'MIT OR Apache-2.0' })
|
||||
|
||||
t.create('license (with version)')
|
||||
.get('/libc/0.2.31.json')
|
||||
.get('/libc/0.2.44.json')
|
||||
.expectJSON({ name: 'license', value: 'MIT OR Apache-2.0' })
|
||||
|
||||
t.create('license (not found)')
|
||||
.get('/not-a-real-package.json')
|
||||
.expectJSON({ name: 'crates.io', value: 'not found' })
|
||||
|
||||
39
services/crates/crates-version.service.js
Normal file
39
services/crates/crates-version.service.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
const { renderVersionBadge } = require('../../lib/version')
|
||||
const { BaseCratesService, keywords } = require('./crates-base')
|
||||
|
||||
module.exports = class CratesVersion extends BaseCratesService {
|
||||
static get category() {
|
||||
return 'version'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'crates/v',
|
||||
pattern: ':crate',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
namedParams: { crate: 'rustc-serialize' },
|
||||
staticExample: this.render({ version: '0.3.24' }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ version }) {
|
||||
return renderVersionBadge({ version })
|
||||
}
|
||||
|
||||
async handle({ crate }) {
|
||||
const json = await this.fetch({ crate })
|
||||
return this.constructor.render({
|
||||
version: json.version ? json.version.num : json.crate.max_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
19
services/crates/crates-version.tester.js
Normal file
19
services/crates/crates-version.tester.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const ServiceTester = require('../service-tester')
|
||||
const { isSemver } = require('../test-validators')
|
||||
|
||||
const t = new ServiceTester({
|
||||
id: 'crates',
|
||||
title: 'crates.io',
|
||||
pathPrefix: '/crates/v',
|
||||
})
|
||||
module.exports = t
|
||||
|
||||
t.create('version')
|
||||
.get('/libc.json')
|
||||
.expectJSONTypes({ name: 'crates.io', value: isSemver })
|
||||
|
||||
t.create('version (not found)')
|
||||
.get('/not-a-real-package.json')
|
||||
.expectJSON({ name: 'crates.io', value: 'not found' })
|
||||
@@ -1,179 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
const {
|
||||
downloadCount: downloadCountColor,
|
||||
version: versionColor,
|
||||
} = require('../../lib/color-formatters')
|
||||
const { metric, addv: versionText } = require('../../lib/text-formatters')
|
||||
|
||||
class CratesDownloads extends LegacyService {
|
||||
static get category() {
|
||||
return 'downloads'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'crates',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
previewUrl: 'd/rustc-serialize',
|
||||
keywords: ['Rust'],
|
||||
},
|
||||
{
|
||||
title: 'Crates.io',
|
||||
previewUrl: 'dv/rustc-serialize',
|
||||
keywords: ['Rust'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler() {}
|
||||
}
|
||||
|
||||
class CratesVersion extends LegacyService {
|
||||
static get category() {
|
||||
return 'version'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'crates/v',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
previewUrl: 'rustc-serialize',
|
||||
keywords: ['Rust'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler() {}
|
||||
}
|
||||
|
||||
class CratesLicense extends LegacyService {
|
||||
static get category() {
|
||||
return 'license'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'crates/l',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
previewUrl: 'rustc-serialize',
|
||||
keywords: ['Rust'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler() {}
|
||||
}
|
||||
|
||||
class Crates extends LegacyService {
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
camp.route(
|
||||
/^\/crates\/(d|v|dv|l)\/([A-Za-z0-9_-]+)(?:\/([0-9.]+))?\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const mode = match[1] // d - downloads (total or for version), v - (latest) version, dv - downloads (for latest version)
|
||||
const crate = match[2] // crate name, e.g. rustc-serialize
|
||||
let version = match[3] // crate version in semver format, optional, e.g. 0.1.2
|
||||
const format = match[4]
|
||||
const modes = {
|
||||
d: {
|
||||
name: 'downloads',
|
||||
version: true,
|
||||
process: function(data, badgeData) {
|
||||
const downloads = data.crate
|
||||
? data.crate.downloads
|
||||
: data.version.downloads
|
||||
version = data.version && data.version.num
|
||||
badgeData.text[1] =
|
||||
metric(downloads) + (version ? ` version ${version}` : '')
|
||||
badgeData.colorscheme = downloadCountColor(downloads)
|
||||
},
|
||||
},
|
||||
dv: {
|
||||
name: 'downloads',
|
||||
version: true,
|
||||
process: function(data, badgeData) {
|
||||
const downloads = data.version
|
||||
? data.version.downloads
|
||||
: data.versions[0].downloads
|
||||
version = data.version && data.version.num
|
||||
badgeData.text[1] =
|
||||
metric(downloads) +
|
||||
(version ? ` version ${version}` : ' latest version')
|
||||
badgeData.colorscheme = downloadCountColor(downloads)
|
||||
},
|
||||
},
|
||||
v: {
|
||||
name: 'crates.io',
|
||||
version: true,
|
||||
process: function(data, badgeData) {
|
||||
version = data.version ? data.version.num : data.crate.max_version
|
||||
badgeData.text[1] = versionText(version)
|
||||
badgeData.colorscheme = versionColor(version)
|
||||
},
|
||||
},
|
||||
l: {
|
||||
name: 'license',
|
||||
version: false,
|
||||
process: function(data, badgeData) {
|
||||
badgeData.text[1] = data.versions[0].license
|
||||
badgeData.colorscheme = 'blue'
|
||||
},
|
||||
},
|
||||
}
|
||||
const behavior = modes[mode]
|
||||
let apiUrl = `https://crates.io/api/v1/crates/${crate}`
|
||||
if (version != null && behavior.version) {
|
||||
apiUrl += `/${version}`
|
||||
}
|
||||
|
||||
const badgeData = getBadgeData(behavior.name, data)
|
||||
request(
|
||||
apiUrl,
|
||||
{ headers: { Accept: 'application/json' } },
|
||||
(err, res, buffer) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = 'inaccessible'
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(buffer)
|
||||
behavior.process(data, badgeData)
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CratesDownloads,
|
||||
CratesVersion,
|
||||
CratesLicense,
|
||||
Crates,
|
||||
}
|
||||
Reference in New Issue
Block a user