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

refactor arch user repositories [aur] service #2086

Merged
merged 4 commits into from
Oct 6, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions lib/all-badge-examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -984,11 +984,6 @@ const allBadgeExamples = [
name: 'License',
},
examples: [
{
title: 'AUR',
previewUrl: '/aur/license/yaourt.svg',
keywords: ['aur'],
},
{
title: 'GitHub',
previewUrl: '/github/license/mashape/apistatus.svg',
Expand Down Expand Up @@ -1063,11 +1058,6 @@ const allBadgeExamples = [
'/chrome-web-store/rating-count/ogffaloegjglncjfehdfplabnoondfjo.svg',
keywords: ['chrome'],
},
{
title: 'AUR',
previewUrl: '/aur/votes/yaourt.svg',
keywords: ['aur'],
},
{
title: 'Mozilla Add-on',
previewUrl: '/amo/rating/dustman.svg',
Expand Down Expand Up @@ -1376,11 +1366,6 @@ const allBadgeExamples = [
previewUrl: '/dub/v/vibe-d.svg',
keywords: ['dub'],
},
{
title: 'AUR',
previewUrl: '/aur/version/yaourt.svg',
keywords: ['aur'],
},
{
title: 'Chrome Web Store',
previewUrl: '/chrome-web-store/v/ogffaloegjglncjfehdfplabnoondfjo.svg',
Expand Down
227 changes: 167 additions & 60 deletions services/aur/aur.service.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,174 @@
'use strict'

const LegacyService = require('../legacy-service')
const {
makeBadgeData: getBadgeData,
makeLabel: getLabel,
} = require('../../lib/badge-data')
const { checkErrorResponse } = require('../../lib/error-helper')
const Joi = require('joi')
const { floorCount: floorCountColor } = require('../../lib/color-formatters')
const { addv: versionText } = require('../../lib/text-formatters')
const BaseJsonService = require('../base-json')
const { NotFound } = require('../errors')
const { nonNegativeInteger } = require('../validators')

// For the Arch user repository (AUR).
module.exports = class Aur extends LegacyService {
static registerLegacyRouteHandler({ camp, cache }) {
camp.route(
/^\/aur\/(version|votes|license)\/(.*)\.(svg|png|gif|jpg|json)$/,
cache((data, match, sendBadge, request) => {
const info = match[1]
const pkg = match[2]
const format = match[3]
const apiUrl = 'https://aur.archlinux.org/rpc.php?type=info&arg=' + pkg
const badgeData = getBadgeData('aur', data)
request(apiUrl, (err, res, buffer) => {
if (checkErrorResponse(badgeData, err, res)) {
sendBadge(format, badgeData)
return
}
try {
const parsedBuffer = JSON.parse(buffer)
const parsedData = parsedBuffer.results
if (parsedBuffer.resultcount === 0) {
// Note the 'not found' response from Arch Linux is:
// status code = 200,
// body = {"version":1,"type":"info","resultcount":0,"results":[]}
badgeData.text[1] = 'not found'
sendBadge(format, badgeData)
return
}

if (info === 'version') {
badgeData.text[1] = versionText(parsedData.Version)
if (parsedData.OutOfDate === null) {
badgeData.colorscheme = 'blue'
} else {
badgeData.colorscheme = 'orange'
}
} else if (info === 'votes') {
const votes = parsedData.NumVotes
badgeData.text[0] = getLabel('votes', data)
badgeData.text[1] = votes
badgeData.colorscheme = floorCountColor(votes, 2, 20, 60)
} else if (info === 'license') {
const license = parsedData.License
badgeData.text[0] = getLabel('license', data)
badgeData.text[1] = license
badgeData.colorscheme = 'blue'
}
sendBadge(format, badgeData)
} catch (e) {
badgeData.text[1] = 'invalid'
sendBadge(format, badgeData)
}
})
})
)
const aurSchema = Joi.object({
resultcount: nonNegativeInteger,
results: Joi.alternatives(
Joi.array()
.min(0)
.max(0)
Copy link
Member

Choose a reason for hiding this comment

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

I believe you could simply use length(0) here.

.required(),
Joi.object({
License: Joi.string().required(),
NumVotes: nonNegativeInteger,
Version: Joi.string().required(),
OutOfDate: nonNegativeInteger.allow(null),
}).required()
),
}).required()

class BaseAurService extends BaseJsonService {
async fetch({ pkg }) {
return this._requestJson({
schema: aurSchema,
url: `https://aur.archlinux.org/rpc.php?type=info&arg=${pkg}`,
})
}

static get defaultBadgeData() {
return { label: 'aur' }
}

static _validate(data, schema) {
if (data.resultcount === 0) {
// Note the 'not found' response from Arch Linux is:
// status code = 200,
// body = {"version":1,"type":"info","resultcount":0,"results":[]}
throw new NotFound()
}
return super._validate(data, schema)
}
}

class AurLicense extends BaseAurService {
static render({ license }) {
return { message: license, color: 'blue' }
}

async handle({ pkg }) {
const json = await this.fetch({ pkg })
return this.constructor.render({ license: json.results.License })
}

static get defaultBadgeData() {
return { label: 'license' }
}

static get category() {
return 'license'
}

static get url() {
return {
base: 'aur/license',
format: '(.+)',
capture: ['pkg'],
}
}

static get examples() {
return [
{
title: `AUR license`,
urlPattern: ':package',
exampleUrl: 'yaourt',
staticExample: this.render({ license: 'GPL' }),
},
]
}
}

class AurVotes extends BaseAurService {
static render({ votes }) {
return {
message: votes,
color: floorCountColor(votes, 2, 20, 60),
}
}

async handle({ pkg }) {
const json = await this.fetch({ pkg })
return this.constructor.render({ votes: json.results.NumVotes })
}

static get defaultBadgeData() {
return { label: 'votes' }
}

static get category() {
return 'rating'
}

static get url() {
return {
base: 'aur/votes',
format: '(.+)',
capture: ['pkg'],
}
}

static get examples() {
return [
{
title: `AUR votes`,
urlPattern: ':package',
exampleUrl: 'yaourt',
staticExample: this.render({ license: '3029' }),
},
]
}
}

class AurVersion extends BaseAurService {
static render({ version, outOfDate }) {
const color = outOfDate === null ? 'blue' : 'orange'
return { message: versionText(version), color: color }
Copy link
Member Author

Choose a reason for hiding this comment

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

I've intentionally not used renderVersionBadge() here because we're using colour to convey a different meaning here

}

async handle({ pkg }) {
const json = await this.fetch({ pkg })
return this.constructor.render({
version: json.results.Version,
outOfDate: json.results.OutOfDate,
})
}

static get defaultBadgeData() {
return { label: 'version' }
}

static get category() {
return 'version'
}

static get url() {
return {
base: 'aur/version',
format: '(.+)',
capture: ['pkg'],
}
}

static get examples() {
return [
{
title: `AUR version`,
urlPattern: ':package',
exampleUrl: 'yaourt',
staticExample: this.render({ version: 'v1.9-1', outOfDate: null }),
},
]
}
}

module.exports = {
AurLicense,
AurVersion,
AurVotes,
}
80 changes: 5 additions & 75 deletions services/aur/aur.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const ServiceTester = require('../service-tester')
const {
isVPlusDottedVersionNClausesWithOptionalSuffix,
} = require('../test-validators')
const { invalidJSON } = require('../response-fixtures')

const t = new ServiceTester({ id: 'aur', title: 'Arch Linux AUR' })
module.exports = t
Expand All @@ -16,7 +15,7 @@ t.create('version (valid)')
.get('/version/yaourt.json?style=_shields_test')
.expectJSONTypes(
Joi.object().keys({
name: 'aur',
name: 'version',
Copy link
Member

Choose a reason for hiding this comment

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

I'm somewhat confused here. Why has the label changed to version?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. Looking over the examples we usually use the platform name as the label on version badges so I shouldn't have changed this.

value: isVPlusDottedVersionNClausesWithOptionalSuffix,
colorB: '#007ec6',
})
Expand All @@ -26,38 +25,15 @@ t.create('version (valid, out of date)')
.get('/version/gog-gemini-rue.json?style=_shields_test')
.expectJSONTypes(
Joi.object().keys({
name: 'aur',
name: 'version',
value: isVPlusDottedVersionNClausesWithOptionalSuffix,
colorB: '#fe7d37',
})
)

t.create('version (not found)')
.get('/version/not-a-package.json')
.expectJSON({ name: 'aur', value: 'not found' })

t.create('version (connection error)')
.get('/version/yaourt.json')
.networkOff()
.expectJSON({ name: 'aur', value: 'inaccessible' })

t.create('version (unexpected response)')
.get('/version/yaourt.json')
.intercept(nock =>
nock('https://aur.archlinux.org')
.get('/rpc.php?type=info&arg=yaourt')
.reply(invalidJSON)
)
.expectJSON({ name: 'aur', value: 'invalid' })

t.create('version (error response)')
.get('/version/yaourt.json')
.intercept(nock =>
nock('https://aur.archlinux.org')
.get('/rpc.php?type=info&arg=yaourt')
.reply(500, '{"error":"oh noes!!"}')
)
.expectJSON({ name: 'aur', value: 'invalid' })
.expectJSON({ name: 'version', value: 'not found' })

// votes tests

Expand All @@ -72,30 +48,7 @@ t.create('votes (valid)')

t.create('votes (not found)')
.get('/votes/not-a-package.json')
.expectJSON({ name: 'aur', value: 'not found' })

t.create('votes (connection error)')
.get('/votes/yaourt.json')
.networkOff()
.expectJSON({ name: 'aur', value: 'inaccessible' })

t.create('votes (unexpected response)')
.get('/votes/yaourt.json')
.intercept(nock =>
nock('https://aur.archlinux.org')
.get('/rpc.php?type=info&arg=yaourt')
.reply(invalidJSON)
)
.expectJSON({ name: 'aur', value: 'invalid' })

t.create('votes (error response)')
.get('/votes/yaourt.json')
.intercept(nock =>
nock('https://aur.archlinux.org')
.get('/rpc.php?type=info&arg=yaourt')
.reply(500, '{"error":"oh noes!!"}')
)
.expectJSON({ name: 'aur', value: 'invalid' })
.expectJSON({ name: 'votes', value: 'not found' })

// license tests

Expand All @@ -105,27 +58,4 @@ t.create('license (valid)')

t.create('license (not found)')
.get('/license/not-a-package.json')
.expectJSON({ name: 'aur', value: 'not found' })

t.create('license (connection error)')
.get('/license/yaourt.json')
.networkOff()
.expectJSON({ name: 'aur', value: 'inaccessible' })

t.create('license (unexpected response)')
.get('/license/yaourt.json')
.intercept(nock =>
nock('https://aur.archlinux.org')
.get('/rpc.php?type=info&arg=yaourt')
.reply(invalidJSON)
)
.expectJSON({ name: 'aur', value: 'invalid' })

t.create('license (error response)')
.get('/license/yaourt.json')
.intercept(nock =>
nock('https://aur.archlinux.org')
.get('/rpc.php?type=info&arg=yaourt')
.reply(500, '{"error":"oh noes!!"}')
)
.expectJSON({ name: 'aur', value: 'invalid' })
.expectJSON({ name: 'license', value: 'not found' })