Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate response json in [apm appveyor cdnjs clojars gem] also affects [npm] #1883

Merged
merged 11 commits into from
Aug 10, 2018
49 changes: 21 additions & 28 deletions services/apm/apm.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ const { BaseJsonService } = require('../base')
const { InvalidResponse } = require('../errors')
const { version: versionColor } = require('../../lib/color-formatters')
const { metric, addv } = require('../../lib/text-formatters')
const { nonNegativeInteger } = require('../validators.js')

const apmSchema = Joi.object({
downloads: nonNegativeInteger,
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',
})
Expand All @@ -18,6 +29,15 @@ class BaseAPMService extends BaseJsonService {
static get defaultBadgeData() {
return { label: 'apm' }
}

static get examples() {
return [
{
previewUrl: 'vim-mode',
keywords: ['atom'],
},
]
}
Copy link
Member

Choose a reason for hiding this comment

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

❤️

}

class APMDownloads extends BaseAPMService {
Expand All @@ -43,15 +63,6 @@ class APMDownloads extends BaseAPMService {
capture: ['repo'],
}
}

static get examples() {
return [
{
previewUrl: 'dm/vim-mode',
keywords: ['atom'],
},
]
}
}

class APMVersion extends BaseAPMService {
Expand All @@ -77,15 +88,6 @@ class APMVersion extends BaseAPMService {
capture: ['repo'],
}
}

static get examples() {
return [
{
previewUrl: 'v/vim-mode',
keywords: ['atom'],
},
]
}
}

class APMLicense extends BaseAPMService {
Expand Down Expand Up @@ -115,15 +117,6 @@ class APMLicense extends BaseAPMService {
capture: ['repo'],
}
}

static get examples() {
return [
{
previewUrl: 'l/vim-mode',
keywords: ['atom'],
},
]
}
}

module.exports = {
Expand Down
8 changes: 7 additions & 1 deletion services/appveyor/appveyor.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand All @@ -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',
})
Expand Down
12 changes: 8 additions & 4 deletions services/cdnjs/cdnjs.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ const { NotFound } = require('../errors')
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()

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) {
/* Note the 'not found' response from cdnjs is:
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),
}
}

Expand Down
7 changes: 6 additions & 1 deletion services/clojars/clojars.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ const { BaseJsonService } = require('../base')
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()

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) {
Expand Down
149 changes: 149 additions & 0 deletions services/gem/gem-downloads.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
'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')
const { nonNegativeInteger } = require('../validators.js')

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,
})
}

_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'],
},
]
}
}
51 changes: 51 additions & 0 deletions services/gem/gem-owner.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict'

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: ownerSchema,
})
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'],
},
]
}
}
Loading