From 07adf6a1b3132e2cfef92ac71cd9363f3953dcbb Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 16:13:47 +0100 Subject: [PATCH 01/11] explicitly declare fetch() as async --- services/gem/gem.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/gem/gem.service.js b/services/gem/gem.service.js index 03a9033b6813f..575c8dd1c8576 100644 --- a/services/gem/gem.service.js +++ b/services/gem/gem.service.js @@ -56,7 +56,7 @@ class GemVersion extends BaseJsonService { } class GemDownloads extends BaseJsonService { - fetch(repo, info) { + async fetch(repo, info) { const endpoint = info === 'dv' ? 'versions/' : 'gems/' const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json` return this._requestJson({ From 8121a90b3263a95c3e6015ebfa64a1664cbfb427 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 16:22:55 +0100 Subject: [PATCH 02/11] split gem service into multiple files --- services/gem/gem-downloads.service.js | 131 ++++++++++++ services/gem/gem-owner.service.js | 49 +++++ services/gem/gem-rank.service.js | 76 +++++++ services/gem/gem-version.service.js | 48 +++++ services/gem/gem.service.js | 294 -------------------------- 5 files changed, 304 insertions(+), 294 deletions(-) create mode 100644 services/gem/gem-downloads.service.js create mode 100644 services/gem/gem-owner.service.js create mode 100644 services/gem/gem-rank.service.js create mode 100644 services/gem/gem-version.service.js delete mode 100644 services/gem/gem.service.js diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js new file mode 100644 index 0000000000000..08336f6f82821 --- /dev/null +++ b/services/gem/gem-downloads.service.js @@ -0,0 +1,131 @@ +'use strict' + +const semver = require('semver') +const Joi = require('joi') + +const { BaseJsonService } = require('../base') +const { InvalidResponse } = require('../errors') +const { + downloadCount: downloadCountColor, +} = require('../../lib/color-formatters') +const { metric } = require('../../lib/text-formatters') +const { latest: latestVersion } = require('../../lib/version') + +module.exports = class GemDownloads extends BaseJsonService { + async fetch(repo, info) { + const endpoint = info === 'dv' ? 'versions/' : 'gems/' + const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json` + return this._requestJson({ + url, + schema: Joi.any(), + }) + } + + _getLabel(version, info) { + if (version) { + return 'downloads@' + version + } else { + if (info === 'dtv') { + return 'downloads@latest' + } else { + return 'downloads' + } + } + } + + async handle({ info, rubygem }) { + const splitRubygem = rubygem.split('/') + const repo = splitRubygem[0] + let version = + splitRubygem.length > 1 ? splitRubygem[splitRubygem.length - 1] : null + version = version === 'stable' ? version : semver.valid(version) + const label = this._getLabel(version, info) + const json = await this.fetch(repo, info) + + let downloads + if (info === 'dt') { + downloads = metric(json.downloads) + } else if (info === 'dtv') { + downloads = metric(json.version_downloads) + } else if (info === 'dv') { + let versionData + if (version !== null && version === 'stable') { + const versions = json + .filter(function(ver) { + return ver.prerelease === false + }) + .map(function(ver) { + return ver.number + }) + // Found latest stable version. + const stableVersion = latestVersion(versions) + versionData = json.filter(function(ver) { + return ver.number === stableVersion + })[0] + downloads = metric(versionData.downloads_count) + } else if (version !== null) { + versionData = json.filter(function(ver) { + return ver.number === version + })[0] + + downloads = metric(versionData.downloads_count) + } else { + throw new InvalidResponse({ + underlyingError: new Error('version is null'), + }) + } + } else { + throw new InvalidResponse({ + underlyingError: new Error('info is invalid'), + }) + } + + return { + label: label, + message: downloads, + color: downloadCountColor(downloads), + } + } + + // Metadata + static get defaultBadgeData() { + return { label: 'downloads' } + } + + static get category() { + return 'downloads' + } + + static get url() { + return { + base: 'gem', + format: '(dt|dtv|dv)/(.+)', + capture: ['info', 'rubygem'], + } + } + + static get examples() { + return [ + { + title: 'Gem', + previewUrl: 'dv/rails/stable', + keywords: ['ruby'], + }, + { + title: 'Gem', + previewUrl: 'dv/rails/4.1.0', + keywords: ['ruby'], + }, + { + title: 'Gem', + previewUrl: 'dtv/rails', + keywords: ['ruby'], + }, + { + title: 'Gem', + previewUrl: 'dt/rails', + keywords: ['ruby'], + }, + ] + } +} diff --git a/services/gem/gem-owner.service.js b/services/gem/gem-owner.service.js new file mode 100644 index 0000000000000..a8187c5e44d09 --- /dev/null +++ b/services/gem/gem-owner.service.js @@ -0,0 +1,49 @@ +'use strict' + +const Joi = require('joi') + +const { BaseJsonService } = require('../base') +const { floorCount: floorCountColor } = require('../../lib/color-formatters') + +module.exports = class GemOwner extends BaseJsonService { + async handle({ user }) { + const url = `https://rubygems.org/api/v1/owners/${user}/gems.json` + const json = await this._requestJson({ + url, + schema: Joi.array(), + }) + const count = json.length + + return { + message: count, + color: floorCountColor(count, 10, 50, 100), + } + } + + // Metadata + static get defaultBadgeData() { + return { label: 'gems' } + } + + static get category() { + return 'other' + } + + static get url() { + return { + base: 'gem/u', + format: '(.+)', + capture: ['user'], + } + } + + static get examples() { + return [ + { + title: 'Gems', + previewUrl: 'raphink', + keywords: ['ruby'], + }, + ] + } +} diff --git a/services/gem/gem-rank.service.js b/services/gem/gem-rank.service.js new file mode 100644 index 0000000000000..6d47713a46dd7 --- /dev/null +++ b/services/gem/gem-rank.service.js @@ -0,0 +1,76 @@ +'use strict' + +const Joi = require('joi') + +const { BaseJsonService } = require('../base') +const { floorCount: floorCountColor } = require('../../lib/color-formatters') +const { ordinalNumber } = require('../../lib/text-formatters') + +module.exports = class GemRank extends BaseJsonService { + _getApiUrl(repo, totalRank, dailyRank) { + let endpoint + if (totalRank) { + endpoint = '/total_ranking.json' + } else if (dailyRank) { + endpoint = '/daily_ranking.json' + } + return `http://bestgems.org/api/v1/gems/${repo}${endpoint}` + } + + async handle({ info, repo }) { + const totalRank = info === 'rt' + const dailyRank = info === 'rd' + const url = this._getApiUrl(repo, totalRank, dailyRank) + const json = await this._requestJson({ + url, + schema: Joi.array(), + }) + + let rank + if (totalRank) { + rank = json[0].total_ranking + } else if (dailyRank) { + rank = json[0].daily_ranking + } + const count = Math.floor(100000 / rank) + let message = ordinalNumber(rank) + message += totalRank ? '' : ' daily' + + return { + message: message, + color: floorCountColor(count, 10, 50, 100), + } + } + + // Metadata + static get defaultBadgeData() { + return { label: 'rank' } + } + + static get category() { + return 'rating' + } + + static get url() { + return { + base: 'gem', + format: '(rt|rd)/(.+)', + capture: ['info', 'repo'], + } + } + + static get examples() { + return [ + { + title: 'Gems', + previewUrl: 'rt/puppet', + keywords: ['ruby'], + }, + { + title: 'Gems', + previewUrl: 'rd/facter', + keywords: ['ruby'], + }, + ] + } +} diff --git a/services/gem/gem-version.service.js b/services/gem/gem-version.service.js new file mode 100644 index 0000000000000..8a84b05c035e9 --- /dev/null +++ b/services/gem/gem-version.service.js @@ -0,0 +1,48 @@ +'use strict' + +const Joi = require('joi') + +const { BaseJsonService } = require('../base') +const { addv: versionText } = require('../../lib/text-formatters') +const { version: versionColor } = require('../../lib/color-formatters') + +module.exports = class GemVersion extends BaseJsonService { + async handle({ repo }) { + const url = `https://rubygems.org/api/v1/gems/${repo}.json` + const { version } = await this._requestJson({ + url, + schema: Joi.object(), + }) + return { + message: versionText(version), + color: versionColor(version), + } + } + + // Metadata + static get defaultBadgeData() { + return { label: 'gem' } + } + + static get category() { + return 'version' + } + + static get url() { + return { + base: 'gem/v', + format: '(.+)', + capture: ['repo'], + } + } + + static get examples() { + return [ + { + title: 'Gem', + previewUrl: 'formatador', + keywords: ['ruby'], + }, + ] + } +} diff --git a/services/gem/gem.service.js b/services/gem/gem.service.js deleted file mode 100644 index 575c8dd1c8576..0000000000000 --- a/services/gem/gem.service.js +++ /dev/null @@ -1,294 +0,0 @@ -'use strict' - -const semver = require('semver') -const Joi = require('joi') - -const { BaseJsonService } = require('../base') -const { InvalidResponse } = require('../errors') -const { addv: versionText } = require('../../lib/text-formatters') -const { version: versionColor } = require('../../lib/color-formatters') -const { - floorCount: floorCountColor, - downloadCount: downloadCountColor, -} = require('../../lib/color-formatters') -const { metric, ordinalNumber } = require('../../lib/text-formatters') -const { latest: latestVersion } = require('../../lib/version') - -class GemVersion extends BaseJsonService { - async handle({ repo }) { - const url = `https://rubygems.org/api/v1/gems/${repo}.json` - const { version } = await this._requestJson({ - url, - schema: Joi.object(), - }) - return { - message: versionText(version), - color: versionColor(version), - } - } - - // Metadata - static get defaultBadgeData() { - return { label: 'gem' } - } - - static get category() { - return 'version' - } - - static get url() { - return { - base: 'gem/v', - format: '(.+)', - capture: ['repo'], - } - } - - static get examples() { - return [ - { - title: 'Gem', - previewUrl: 'formatador', - keywords: ['ruby'], - }, - ] - } -} - -class GemDownloads extends BaseJsonService { - async fetch(repo, info) { - const endpoint = info === 'dv' ? 'versions/' : 'gems/' - const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json` - return this._requestJson({ - url, - schema: Joi.any(), - }) - } - - _getLabel(version, info) { - if (version) { - return 'downloads@' + version - } else { - if (info === 'dtv') { - return 'downloads@latest' - } else { - return 'downloads' - } - } - } - - async handle({ info, rubygem }) { - const splitRubygem = rubygem.split('/') - const repo = splitRubygem[0] - let version = - splitRubygem.length > 1 ? splitRubygem[splitRubygem.length - 1] : null - version = version === 'stable' ? version : semver.valid(version) - const label = this._getLabel(version, info) - const json = await this.fetch(repo, info) - - let downloads - if (info === 'dt') { - downloads = metric(json.downloads) - } else if (info === 'dtv') { - downloads = metric(json.version_downloads) - } else if (info === 'dv') { - let versionData - if (version !== null && version === 'stable') { - const versions = json - .filter(function(ver) { - return ver.prerelease === false - }) - .map(function(ver) { - return ver.number - }) - // Found latest stable version. - const stableVersion = latestVersion(versions) - versionData = json.filter(function(ver) { - return ver.number === stableVersion - })[0] - downloads = metric(versionData.downloads_count) - } else if (version !== null) { - versionData = json.filter(function(ver) { - return ver.number === version - })[0] - - downloads = metric(versionData.downloads_count) - } else { - throw new InvalidResponse({ - underlyingError: new Error('version is null'), - }) - } - } else { - throw new InvalidResponse({ - underlyingError: new Error('info is invalid'), - }) - } - - return { - label: label, - message: downloads, - color: downloadCountColor(downloads), - } - } - - // Metadata - static get defaultBadgeData() { - return { label: 'downloads' } - } - - static get category() { - return 'downloads' - } - - static get url() { - return { - base: 'gem', - format: '(dt|dtv|dv)/(.+)', - capture: ['info', 'rubygem'], - } - } - - static get examples() { - return [ - { - title: 'Gem', - previewUrl: 'dv/rails/stable', - keywords: ['ruby'], - }, - { - title: 'Gem', - previewUrl: 'dv/rails/4.1.0', - keywords: ['ruby'], - }, - { - title: 'Gem', - previewUrl: 'dtv/rails', - keywords: ['ruby'], - }, - { - title: 'Gem', - previewUrl: 'dt/rails', - keywords: ['ruby'], - }, - ] - } -} - -class GemOwner extends BaseJsonService { - async handle({ user }) { - const url = `https://rubygems.org/api/v1/owners/${user}/gems.json` - const json = await this._requestJson({ - url, - schema: Joi.array(), - }) - const count = json.length - - return { - message: count, - color: floorCountColor(count, 10, 50, 100), - } - } - - // Metadata - static get defaultBadgeData() { - return { label: 'gems' } - } - - static get category() { - return 'other' - } - - static get url() { - return { - base: 'gem/u', - format: '(.+)', - capture: ['user'], - } - } - - static get examples() { - return [ - { - title: 'Gems', - previewUrl: 'raphink', - keywords: ['ruby'], - }, - ] - } -} - -class GemRank extends BaseJsonService { - _getApiUrl(repo, totalRank, dailyRank) { - let endpoint - if (totalRank) { - endpoint = '/total_ranking.json' - } else if (dailyRank) { - endpoint = '/daily_ranking.json' - } - return `http://bestgems.org/api/v1/gems/${repo}${endpoint}` - } - - async handle({ info, repo }) { - const totalRank = info === 'rt' - const dailyRank = info === 'rd' - const url = this._getApiUrl(repo, totalRank, dailyRank) - const json = await this._requestJson({ - url, - schema: Joi.array(), - }) - - let rank - if (totalRank) { - rank = json[0].total_ranking - } else if (dailyRank) { - rank = json[0].daily_ranking - } - const count = Math.floor(100000 / rank) - let message = ordinalNumber(rank) - message += totalRank ? '' : ' daily' - - return { - message: message, - color: floorCountColor(count, 10, 50, 100), - } - } - - // Metadata - static get defaultBadgeData() { - return { label: 'rank' } - } - - static get category() { - return 'rating' - } - - static get url() { - return { - base: 'gem', - format: '(rt|rd)/(.+)', - capture: ['info', 'repo'], - } - } - - static get examples() { - return [ - { - title: 'Gems', - previewUrl: 'rt/puppet', - keywords: ['ruby'], - }, - { - title: 'Gems', - previewUrl: 'rd/facter', - keywords: ['ruby'], - }, - ] - } -} - -module.exports = { - GemVersion, - GemDownloads, - GemOwner, - GemRank, -} From 23890b30132a7857df526005a1d9117c90d97c89 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 16:50:28 +0100 Subject: [PATCH 03/11] add validation to apm, appveyor, cdnjs, clojars & gem services --- services/apm/apm.service.js | 16 +++++++++++++++- services/appveyor/appveyor.service.js | 8 +++++++- services/cdnjs/cdnjs.service.js | 11 +++++++---- services/clojars/clojars.service.js | 6 +++++- services/gem/gem-downloads.service.js | 23 ++++++++++++++++++++++- services/gem/gem-rank.service.js | 20 +++++++++++++++++++- services/gem/gem-version.service.js | 9 ++++++++- 7 files changed, 83 insertions(+), 10 deletions(-) diff --git a/services/apm/apm.service.js b/services/apm/apm.service.js index 3f4ccbea25441..6c7158f939db7 100644 --- a/services/apm/apm.service.js +++ b/services/apm/apm.service.js @@ -6,10 +6,24 @@ const { InvalidResponse } = require('../errors') const { version: versionColor } = require('../../lib/color-formatters') const { metric, addv } = require('../../lib/text-formatters') +const count = Joi.number() + .integer() + .min(0) + .required() +const apmSchema = Joi.object({ + downloads: count, + releases: Joi.object({ + latest: Joi.string().required(), + }), + metadata: Joi.object({ + license: Joi.string().required(), + }), +}) + class BaseAPMService extends BaseJsonService { async fetch(repo) { return this._requestJson({ - schema: Joi.object(), + schema: apmSchema, url: `https://atom.io/api/packages/${repo}`, notFoundMessage: 'package not found', }) diff --git a/services/appveyor/appveyor.service.js b/services/appveyor/appveyor.service.js index 220cc3d2b280e..ec4c9f4b98b92 100644 --- a/services/appveyor/appveyor.service.js +++ b/services/appveyor/appveyor.service.js @@ -3,6 +3,12 @@ const Joi = require('joi') const { BaseJsonService } = require('../base') +const appVeyorSchema = Joi.object({ + build: Joi.object({ + status: Joi.string().required(), + }), +}).required() + module.exports = class AppVeyor extends BaseJsonService { async handle({ repo, branch }) { let url = `https://ci.appveyor.com/api/projects/${repo}` @@ -12,7 +18,7 @@ module.exports = class AppVeyor extends BaseJsonService { const { build: { status }, } = await this._requestJson({ - schema: Joi.object(), + schema: appVeyorSchema, url, notFoundMessage: 'project not found or access denied', }) diff --git a/services/cdnjs/cdnjs.service.js b/services/cdnjs/cdnjs.service.js index 52861c82754db..33c3acc20802d 100644 --- a/services/cdnjs/cdnjs.service.js +++ b/services/cdnjs/cdnjs.service.js @@ -6,12 +6,16 @@ const { NotFound } = require('../errors') const { addv: versionText } = require('../../lib/text-formatters') const { version: versionColor } = require('../../lib/color-formatters') +const cdnjsSchema = Joi.object({ + version: Joi.string(), +}).required() + module.exports = class Cdnjs extends BaseJsonService { async handle({ library }) { const url = `https://api.cdnjs.com/libraries/${library}?fields=version` const json = await this._requestJson({ url, - schema: Joi.any(), + schema: cdnjsSchema, }) if (Object.keys(json).length === 0) { @@ -19,11 +23,10 @@ module.exports = class Cdnjs extends BaseJsonService { status code = 200, body = {} */ throw new NotFound() } - const version = json.version || 0 return { - message: versionText(version), - color: versionColor(version), + message: versionText(json.version), + color: versionColor(json.version), } } diff --git a/services/clojars/clojars.service.js b/services/clojars/clojars.service.js index a02509b727701..f69df6d2a5ef8 100644 --- a/services/clojars/clojars.service.js +++ b/services/clojars/clojars.service.js @@ -5,12 +5,16 @@ const { BaseJsonService } = require('../base') const { NotFound } = require('../errors') const { version: versionColor } = require('../../lib/color-formatters') +const clojarsSchema = Joi.object({ + version: Joi.string(), +}).required() + module.exports = class Clojars extends BaseJsonService { async handle({ clojar }) { const url = `https://clojars.org/${clojar}/latest-version.json` const json = await this._requestJson({ url, - schema: Joi.any(), + schema: clojarsSchema, }) if (Object.keys(json).length === 0) { diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js index 08336f6f82821..20415ac3a5533 100644 --- a/services/gem/gem-downloads.service.js +++ b/services/gem/gem-downloads.service.js @@ -11,13 +11,34 @@ const { const { metric } = require('../../lib/text-formatters') const { latest: latestVersion } = require('../../lib/version') +const count = Joi.number() + .integer() + .min(0) + .required() +const downloadsSchema = Joi.alternatives().try( + Joi.object({ + downloads: count, + version_downloads: count, + }).required(), + Joi.array() + .items( + Joi.object({ + prerelease: Joi.boolean().required(), + number: Joi.string().required(), + downloads_count: count, + }) + ) + .min(1) + .required() +) + module.exports = class GemDownloads extends BaseJsonService { async fetch(repo, info) { const endpoint = info === 'dv' ? 'versions/' : 'gems/' const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json` return this._requestJson({ url, - schema: Joi.any(), + schema: downloadsSchema, }) } diff --git a/services/gem/gem-rank.service.js b/services/gem/gem-rank.service.js index 6d47713a46dd7..36524d98d0c42 100644 --- a/services/gem/gem-rank.service.js +++ b/services/gem/gem-rank.service.js @@ -6,6 +6,24 @@ const { BaseJsonService } = require('../base') const { floorCount: floorCountColor } = require('../../lib/color-formatters') const { ordinalNumber } = require('../../lib/text-formatters') +const count = Joi.number() + .integer() + .min(0) + .required() +const rankSchema = Joi.array() + .items( + Joi.alternatives().try( + Joi.object({ + total_ranking: count, + }), + Joi.object({ + daily_ranking: count, + }) + ) + ) + .min(1) + .required() + module.exports = class GemRank extends BaseJsonService { _getApiUrl(repo, totalRank, dailyRank) { let endpoint @@ -23,7 +41,7 @@ module.exports = class GemRank extends BaseJsonService { const url = this._getApiUrl(repo, totalRank, dailyRank) const json = await this._requestJson({ url, - schema: Joi.array(), + schema: rankSchema, }) let rank diff --git a/services/gem/gem-version.service.js b/services/gem/gem-version.service.js index 8a84b05c035e9..615f0d3cca9e2 100644 --- a/services/gem/gem-version.service.js +++ b/services/gem/gem-version.service.js @@ -6,12 +6,19 @@ const { BaseJsonService } = require('../base') const { addv: versionText } = require('../../lib/text-formatters') const { version: versionColor } = require('../../lib/color-formatters') +// Response should contain a string key 'version' +// In most cases this will be a SemVer +// but the registry doesn't actually enforce this +const versionSchema = Joi.object({ + version: Joi.string().required(), +}).required() + module.exports = class GemVersion extends BaseJsonService { async handle({ repo }) { const url = `https://rubygems.org/api/v1/gems/${repo}.json` const { version } = await this._requestJson({ url, - schema: Joi.object(), + schema: versionSchema, }) return { message: versionText(version), From 99fc83682c3b0cb63a94ddc32b047fcf806e4476 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 16:51:59 +0100 Subject: [PATCH 04/11] fix the apm examples --- services/apm/apm.service.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/apm/apm.service.js b/services/apm/apm.service.js index 6c7158f939db7..1a253a8cc074c 100644 --- a/services/apm/apm.service.js +++ b/services/apm/apm.service.js @@ -61,7 +61,7 @@ class APMDownloads extends BaseAPMService { static get examples() { return [ { - previewUrl: 'dm/vim-mode', + previewUrl: 'vim-mode', keywords: ['atom'], }, ] @@ -95,7 +95,7 @@ class APMVersion extends BaseAPMService { static get examples() { return [ { - previewUrl: 'v/vim-mode', + previewUrl: 'vim-mode', keywords: ['atom'], }, ] @@ -133,7 +133,7 @@ class APMLicense extends BaseAPMService { static get examples() { return [ { - previewUrl: 'l/vim-mode', + previewUrl: 'vim-mode', keywords: ['atom'], }, ] From cf047960af3113d262bac7fa2d8c1bf80a8685c0 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 16:53:04 +0100 Subject: [PATCH 05/11] DRY up --- services/apm/apm.service.js | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/services/apm/apm.service.js b/services/apm/apm.service.js index 1a253a8cc074c..c851299c9ccea 100644 --- a/services/apm/apm.service.js +++ b/services/apm/apm.service.js @@ -32,6 +32,15 @@ class BaseAPMService extends BaseJsonService { static get defaultBadgeData() { return { label: 'apm' } } + + static get examples() { + return [ + { + previewUrl: 'vim-mode', + keywords: ['atom'], + }, + ] + } } class APMDownloads extends BaseAPMService { @@ -57,15 +66,6 @@ class APMDownloads extends BaseAPMService { capture: ['repo'], } } - - static get examples() { - return [ - { - previewUrl: 'vim-mode', - keywords: ['atom'], - }, - ] - } } class APMVersion extends BaseAPMService { @@ -91,15 +91,6 @@ class APMVersion extends BaseAPMService { capture: ['repo'], } } - - static get examples() { - return [ - { - previewUrl: 'vim-mode', - keywords: ['atom'], - }, - ] - } } class APMLicense extends BaseAPMService { @@ -129,15 +120,6 @@ class APMLicense extends BaseAPMService { capture: ['repo'], } } - - static get examples() { - return [ - { - previewUrl: 'vim-mode', - keywords: ['atom'], - }, - ] - } } module.exports = { From ea0bac158e4ab303f22083fcbcb348cc835accb9 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 17:02:06 +0100 Subject: [PATCH 06/11] extract a commonly used validator --- services/apm/apm.service.js | 7 ++----- services/gem/gem-downloads.service.js | 11 ++++------- services/gem/gem-rank.service.js | 9 +++------ services/npm/npm-downloads.service.js | 6 ++---- services/validators.js | 12 ++++++++++++ 5 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 services/validators.js diff --git a/services/apm/apm.service.js b/services/apm/apm.service.js index c851299c9ccea..381ab5dc86e9e 100644 --- a/services/apm/apm.service.js +++ b/services/apm/apm.service.js @@ -5,13 +5,10 @@ const { BaseJsonService } = require('../base') const { InvalidResponse } = require('../errors') const { version: versionColor } = require('../../lib/color-formatters') const { metric, addv } = require('../../lib/text-formatters') +const { positiveInteger } = require('../validators.js') -const count = Joi.number() - .integer() - .min(0) - .required() const apmSchema = Joi.object({ - downloads: count, + downloads: positiveInteger, releases: Joi.object({ latest: Joi.string().required(), }), diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js index 20415ac3a5533..e99ff4db128ae 100644 --- a/services/gem/gem-downloads.service.js +++ b/services/gem/gem-downloads.service.js @@ -10,22 +10,19 @@ const { } = require('../../lib/color-formatters') const { metric } = require('../../lib/text-formatters') const { latest: latestVersion } = require('../../lib/version') +const { positiveInteger } = require('../validators.js') -const count = Joi.number() - .integer() - .min(0) - .required() const downloadsSchema = Joi.alternatives().try( Joi.object({ - downloads: count, - version_downloads: count, + downloads: positiveInteger, + version_downloads: positiveInteger, }).required(), Joi.array() .items( Joi.object({ prerelease: Joi.boolean().required(), number: Joi.string().required(), - downloads_count: count, + downloads_count: positiveInteger, }) ) .min(1) diff --git a/services/gem/gem-rank.service.js b/services/gem/gem-rank.service.js index 36524d98d0c42..012273dfdb20a 100644 --- a/services/gem/gem-rank.service.js +++ b/services/gem/gem-rank.service.js @@ -5,19 +5,16 @@ const Joi = require('joi') const { BaseJsonService } = require('../base') const { floorCount: floorCountColor } = require('../../lib/color-formatters') const { ordinalNumber } = require('../../lib/text-formatters') +const { positiveInteger } = require('../validators.js') -const count = Joi.number() - .integer() - .min(0) - .required() const rankSchema = Joi.array() .items( Joi.alternatives().try( Joi.object({ - total_ranking: count, + total_ranking: positiveInteger, }), Joi.object({ - daily_ranking: count, + daily_ranking: positiveInteger, }) ) ) diff --git a/services/npm/npm-downloads.service.js b/services/npm/npm-downloads.service.js index a8e0be3576dd5..88fc1b4a9baf3 100644 --- a/services/npm/npm-downloads.service.js +++ b/services/npm/npm-downloads.service.js @@ -3,13 +3,11 @@ const Joi = require('joi') const { BaseJsonService } = require('../base') const { metric } = require('../../lib/text-formatters') +const { positiveInteger } = require('../validators.js') // https://github.com/npm/registry/blob/master/docs/download-counts.md#output const pointResponseSchema = Joi.object({ - downloads: Joi.number() - .integer() - .min(0) - .required(), + downloads: positiveInteger, }).required() // https://github.com/npm/registry/blob/master/docs/download-counts.md#output-1 diff --git a/services/validators.js b/services/validators.js new file mode 100644 index 0000000000000..d03cfd366dd42 --- /dev/null +++ b/services/validators.js @@ -0,0 +1,12 @@ +'use strict' + +const Joi = require('joi') + +const positiveInteger = Joi.number() + .integer() + .min(0) + .required() + +module.exports = { + positiveInteger, +} From 00f4176b7a49e607a235c278c9d9f3409dc74f5c Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 19:28:05 +0100 Subject: [PATCH 07/11] give validator more accurate name I iz good at maths :D --- services/apm/apm.service.js | 4 ++-- services/gem/gem-downloads.service.js | 8 ++++---- services/gem/gem-rank.service.js | 6 +++--- services/npm/npm-downloads.service.js | 4 ++-- services/validators.js | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/services/apm/apm.service.js b/services/apm/apm.service.js index 381ab5dc86e9e..0725124e9c30f 100644 --- a/services/apm/apm.service.js +++ b/services/apm/apm.service.js @@ -5,10 +5,10 @@ const { BaseJsonService } = require('../base') const { InvalidResponse } = require('../errors') const { version: versionColor } = require('../../lib/color-formatters') const { metric, addv } = require('../../lib/text-formatters') -const { positiveInteger } = require('../validators.js') +const { nonNegativeInteger } = require('../validators.js') const apmSchema = Joi.object({ - downloads: positiveInteger, + downloads: nonNegativeInteger, releases: Joi.object({ latest: Joi.string().required(), }), diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js index e99ff4db128ae..4747efea8407d 100644 --- a/services/gem/gem-downloads.service.js +++ b/services/gem/gem-downloads.service.js @@ -10,19 +10,19 @@ const { } = require('../../lib/color-formatters') const { metric } = require('../../lib/text-formatters') const { latest: latestVersion } = require('../../lib/version') -const { positiveInteger } = require('../validators.js') +const { nonNegativeInteger } = require('../validators.js') const downloadsSchema = Joi.alternatives().try( Joi.object({ - downloads: positiveInteger, - version_downloads: positiveInteger, + downloads: nonNegativeInteger, + version_downloads: nonNegativeInteger, }).required(), Joi.array() .items( Joi.object({ prerelease: Joi.boolean().required(), number: Joi.string().required(), - downloads_count: positiveInteger, + downloads_count: nonNegativeInteger, }) ) .min(1) diff --git a/services/gem/gem-rank.service.js b/services/gem/gem-rank.service.js index 012273dfdb20a..3d17883833318 100644 --- a/services/gem/gem-rank.service.js +++ b/services/gem/gem-rank.service.js @@ -5,16 +5,16 @@ const Joi = require('joi') const { BaseJsonService } = require('../base') const { floorCount: floorCountColor } = require('../../lib/color-formatters') const { ordinalNumber } = require('../../lib/text-formatters') -const { positiveInteger } = require('../validators.js') +const { nonNegativeInteger } = require('../validators.js') const rankSchema = Joi.array() .items( Joi.alternatives().try( Joi.object({ - total_ranking: positiveInteger, + total_ranking: nonNegativeInteger, }), Joi.object({ - daily_ranking: positiveInteger, + daily_ranking: nonNegativeInteger, }) ) ) diff --git a/services/npm/npm-downloads.service.js b/services/npm/npm-downloads.service.js index 88fc1b4a9baf3..aae45cbcbcc6a 100644 --- a/services/npm/npm-downloads.service.js +++ b/services/npm/npm-downloads.service.js @@ -3,11 +3,11 @@ const Joi = require('joi') const { BaseJsonService } = require('../base') const { metric } = require('../../lib/text-formatters') -const { positiveInteger } = require('../validators.js') +const { nonNegativeInteger } = require('../validators.js') // https://github.com/npm/registry/blob/master/docs/download-counts.md#output const pointResponseSchema = Joi.object({ - downloads: positiveInteger, + downloads: nonNegativeInteger, }).required() // https://github.com/npm/registry/blob/master/docs/download-counts.md#output-1 diff --git a/services/validators.js b/services/validators.js index d03cfd366dd42..50723b609e1ab 100644 --- a/services/validators.js +++ b/services/validators.js @@ -2,11 +2,11 @@ const Joi = require('joi') -const positiveInteger = Joi.number() +const nonNegativeInteger = Joi.number() .integer() .min(0) .required() module.exports = { - positiveInteger, + nonNegativeInteger, } From 01981e5eacb3ab5f37714b53a71b8c0bec8ef17d Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 19:39:56 +0100 Subject: [PATCH 08/11] pass the right schema for the right endpoint instead of using Joi.alternatives().try() --- services/gem/gem-downloads.service.js | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js index 4747efea8407d..6d0fa3bf15d93 100644 --- a/services/gem/gem-downloads.service.js +++ b/services/gem/gem-downloads.service.js @@ -12,30 +12,30 @@ const { metric } = require('../../lib/text-formatters') const { latest: latestVersion } = require('../../lib/version') const { nonNegativeInteger } = require('../validators.js') -const downloadsSchema = Joi.alternatives().try( - Joi.object({ - downloads: nonNegativeInteger, - version_downloads: nonNegativeInteger, - }).required(), - Joi.array() - .items( - Joi.object({ - prerelease: Joi.boolean().required(), - number: Joi.string().required(), - downloads_count: nonNegativeInteger, - }) - ) - .min(1) - .required() -) +const gemsSchema = Joi.object({ + downloads: nonNegativeInteger, + version_downloads: nonNegativeInteger, +}).required() + +const versionsSchema = Joi.array() + .items( + Joi.object({ + prerelease: Joi.boolean().required(), + number: Joi.string().required(), + downloads_count: nonNegativeInteger, + }) + ) + .min(1) + .required() module.exports = class GemDownloads extends BaseJsonService { async fetch(repo, info) { const endpoint = info === 'dv' ? 'versions/' : 'gems/' + const schema = info === 'dv' ? versionsSchema : gemsSchema const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json` return this._requestJson({ url, - schema: downloadsSchema, + schema, }) } From 410b3293b7e95e3410ef428953304fe878439560 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 19:43:32 +0100 Subject: [PATCH 09/11] make array() required --- services/gem/gem-owner.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/gem/gem-owner.service.js b/services/gem/gem-owner.service.js index a8187c5e44d09..2ac085499f0a6 100644 --- a/services/gem/gem-owner.service.js +++ b/services/gem/gem-owner.service.js @@ -5,12 +5,14 @@ const Joi = require('joi') const { BaseJsonService } = require('../base') const { floorCount: floorCountColor } = require('../../lib/color-formatters') +const ownerSchema = Joi.array().required() + module.exports = class GemOwner extends BaseJsonService { async handle({ user }) { const url = `https://rubygems.org/api/v1/owners/${user}/gems.json` const json = await this._requestJson({ url, - schema: Joi.array(), + schema: ownerSchema, }) const count = json.length From 796e6e754ae91708ffa762add6c51e7c378a1cf3 Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 20:36:07 +0100 Subject: [PATCH 10/11] add clarifying comments --- services/cdnjs/cdnjs.service.js | 1 + services/clojars/clojars.service.js | 1 + 2 files changed, 2 insertions(+) diff --git a/services/cdnjs/cdnjs.service.js b/services/cdnjs/cdnjs.service.js index 33c3acc20802d..6b9cf7596f673 100644 --- a/services/cdnjs/cdnjs.service.js +++ b/services/cdnjs/cdnjs.service.js @@ -7,6 +7,7 @@ const { addv: versionText } = require('../../lib/text-formatters') const { version: versionColor } = require('../../lib/color-formatters') const cdnjsSchema = Joi.object({ + // optional due to non-standard 'not found' condition version: Joi.string(), }).required() diff --git a/services/clojars/clojars.service.js b/services/clojars/clojars.service.js index f69df6d2a5ef8..43f3f36ebb352 100644 --- a/services/clojars/clojars.service.js +++ b/services/clojars/clojars.service.js @@ -6,6 +6,7 @@ const { NotFound } = require('../errors') const { version: versionColor } = require('../../lib/color-formatters') const clojarsSchema = Joi.object({ + // optional due to non-standard 'not found' condition version: Joi.string(), }).required() From 3cdf4a54b1a821a177654d3c57384f249db2510f Mon Sep 17 00:00:00 2001 From: chris48s Date: Fri, 10 Aug 2018 20:43:53 +0100 Subject: [PATCH 11/11] use 2 schemas instead of Joi.alternatives().try() for gem rank --- services/gem/gem-rank.service.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/services/gem/gem-rank.service.js b/services/gem/gem-rank.service.js index 3d17883833318..8e50f1b570d92 100644 --- a/services/gem/gem-rank.service.js +++ b/services/gem/gem-rank.service.js @@ -7,16 +7,19 @@ const { floorCount: floorCountColor } = require('../../lib/color-formatters') const { ordinalNumber } = require('../../lib/text-formatters') const { nonNegativeInteger } = require('../validators.js') -const rankSchema = Joi.array() +const totalSchema = Joi.array() .items( - Joi.alternatives().try( - Joi.object({ - total_ranking: nonNegativeInteger, - }), - Joi.object({ - daily_ranking: nonNegativeInteger, - }) - ) + Joi.object({ + total_ranking: nonNegativeInteger, + }) + ) + .min(1) + .required() +const dailySchema = Joi.array() + .items( + Joi.object({ + daily_ranking: nonNegativeInteger, + }) ) .min(1) .required() @@ -35,10 +38,11 @@ module.exports = class GemRank extends BaseJsonService { async handle({ info, repo }) { const totalRank = info === 'rt' const dailyRank = info === 'rd' + const schema = totalRank ? totalSchema : dailySchema const url = this._getApiUrl(repo, totalRank, dailyRank) const json = await this._requestJson({ url, - schema: rankSchema, + schema, }) let rank