1
0
mirror of https://github.com/badges/shields.git synced 2025-04-18 19:44:04 +03:00

migrate examples to openApi part 11: enums; affects [codefactor conda depfu homebrew jsdelivr reddit sourceforge testspace vaadin github] (#9437)

* WIP enums

* WIP moar enums

* add a helper function for extracting enum from route pattern

* add enum schemas to services

* review and improve service names

* convert some more services with enums

* review and improve service names

* fix issue/pull request detail
This commit is contained in:
chris48s 2023-08-30 17:14:18 +01:00 committed by GitHub
parent fdeda3e3f9
commit e8157100b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 163 deletions

View File

@ -21,6 +21,7 @@ import {
} from './errors.js'
import { validateExample, transformExample } from './examples.js'
import { fetch } from './got.js'
import { getEnum } from './openapi.js'
import {
makeFullUrl,
assertValidRoute,
@ -102,6 +103,26 @@ class BaseService {
throw new Error(`Route not defined for ${this.name}`)
}
/**
* Extract an array of allowed values from this service's route pattern
* for a given route parameter
*
* @param {string} param The name of a param in this service's route pattern
* @returns {string[]} Array of allowed values for this param
*/
static getEnum(param) {
if (!('pattern' in this.route)) {
throw new Error('getEnum() requires route to have a .pattern property')
}
const enumeration = getEnum(this.route.pattern, param)
if (!Array.isArray(enumeration)) {
throw new Error(
`Could not extract enum for param ${param} from pattern ${this.route.pattern}`,
)
}
return enumeration
}
/**
* Configuration for the authentication helper that prepares credentials
* for upstream requests.

View File

@ -539,6 +539,7 @@ describe('BaseService', function () {
).to.not.contain('service_response_bytes_bucket')
})
})
describe('auth', function () {
class AuthService extends DummyService {
static auth = {
@ -592,4 +593,44 @@ describe('BaseService', function () {
})
})
})
describe('getEnum', function () {
class EnumService extends DummyService {
static route = {
base: 'foo',
pattern: ':namedParamA/:namedParamB(this|that)',
queryParamSchema,
}
}
it('returns an array of allowed values', async function () {
expect(EnumService.getEnum('namedParamB')).to.deep.equal(['this', 'that'])
})
it('throws if param name is invalid', async function () {
expect(() => EnumService.getEnum('notAValidParam')).to.throw(
'Could not extract enum for param notAValidParam from pattern :namedParamA/:namedParamB(this|that)',
)
})
it('throws if param name is not an enum', async function () {
expect(() => EnumService.getEnum('namedParamA')).to.throw(
'Could not extract enum for param namedParamA from pattern :namedParamA/:namedParamB(this|that)',
)
})
it('throws if route does not have a pattern', async function () {
class FormatService extends DummyService {
static route = {
base: 'foo',
format: '([^/]+?)',
queryParamSchema,
}
}
expect(() => FormatService.getEnum('notAValidParam')).to.throw(
'getEnum() requires route to have a .pattern property',
)
})
})
})

View File

@ -466,4 +466,11 @@ function queryParams(...params) {
* @property {boolean} allowEmptyValue If true, allows the ability to pass an empty value to this parameter
*/
export { category2openapi, pathParam, pathParams, queryParam, queryParams }
export {
category2openapi,
getEnum,
pathParam,
pathParams,
queryParam,
queryParams,
}

View File

@ -1,5 +1,5 @@
import Joi from 'joi'
import { BaseSvgScrapingService } from '../index.js'
import { BaseSvgScrapingService, pathParams } from '../index.js'
import { isValidGrade, gradeColor } from './codefactor-helpers.js'
const schema = Joi.object({
@ -13,18 +13,52 @@ export default class CodeFactorGrade extends BaseSvgScrapingService {
pattern: ':vcsType(github|bitbucket)/:user/:repo/:branch*',
}
static examples = [
{
title: 'CodeFactor Grade',
namedParams: {
vcsType: 'github',
user: 'microsoft',
repo: 'powertoys',
branch: 'main',
static openApi = {
'/codefactor/grade/{vcsType}/{user}/{repo}/{branch}': {
get: {
summary: 'CodeFactor Grade (with branch)',
parameters: pathParams(
{
name: 'vcsType',
example: 'github',
schema: { type: 'string', enum: this.getEnum('vcsType') },
},
{
name: 'user',
example: 'microsoft',
},
{
name: 'repo',
example: 'powertoys',
},
{
name: 'branch',
example: 'main',
},
),
},
staticPreview: this.render({ grade: 'B+' }),
},
]
'/codefactor/grade/{vcsType}/{user}/{repo}': {
get: {
summary: 'CodeFactor Grade',
parameters: pathParams(
{
name: 'vcsType',
example: 'github',
schema: { type: 'string', enum: this.getEnum('vcsType') },
},
{
name: 'user',
example: 'microsoft',
},
{
name: 'repo',
example: 'powertoys',
},
),
},
},
}
static defaultBadgeData = { label: 'code quality' }

View File

@ -1,3 +1,4 @@
import { pathParams } from '../index.js'
import { renderDownloadsBadge } from '../downloads.js'
import BaseCondaService from './conda-base.js'
@ -5,14 +6,28 @@ export default class CondaDownloads extends BaseCondaService {
static category = 'downloads'
static route = { base: 'conda', pattern: ':variant(d|dn)/:channel/:pkg' }
static examples = [
{
title: 'Conda',
namedParams: { channel: 'conda-forge', package: 'python' },
pattern: 'dn/:channel/:package',
staticPreview: this.render({ variant: 'dn', downloads: 5000000 }),
static openApi = {
'/conda/{variant}/{channel}/{package}': {
get: {
summary: 'Conda Downloads',
parameters: pathParams(
{
name: 'variant',
example: 'dn',
schema: { type: 'string', enum: this.getEnum('variant') },
},
{
name: 'channel',
example: 'conda-forge',
},
{
name: 'package',
example: 'python',
},
),
},
},
]
}
static render({ variant, downloads }) {
const labelOverride = variant === 'dn' ? 'downloads' : 'conda|downloads'

View File

@ -1,20 +1,32 @@
import { pathParams } from '../index.js'
import BaseCondaService from './conda-base.js'
export default class CondaPlatform extends BaseCondaService {
static category = 'platform-support'
static route = { base: 'conda', pattern: ':variant(p|pn)/:channel/:pkg' }
static examples = [
{
title: 'Conda',
namedParams: { channel: 'conda-forge', package: 'python' },
pattern: 'pn/:channel/:package',
staticPreview: this.render({
variant: 'pn',
platforms: ['linux-64', 'win-32', 'osx-64', 'win-64'],
}),
static openApi = {
'/conda/{variant}/{channel}/{package}': {
get: {
summary: 'Conda Platform',
parameters: pathParams(
{
name: 'variant',
example: 'pn',
schema: { type: 'string', enum: this.getEnum('variant') },
},
{
name: 'channel',
example: 'conda-forge',
},
{
name: 'package',
example: 'python',
},
),
},
},
]
}
static render({ variant, platforms }) {
return {

View File

@ -1,5 +1,10 @@
import Joi from 'joi'
import { BaseJsonService, InvalidParameter, redirector } from '../index.js'
import {
BaseJsonService,
InvalidParameter,
redirector,
pathParams,
} from '../index.js'
const depfuSchema = Joi.object({
text: Joi.string().required(),
@ -13,16 +18,24 @@ class Depfu extends BaseJsonService {
pattern: ':vcsType(github|gitlab)/:project+',
}
static examples = [
{
title: 'Depfu',
namedParams: { vcsType: 'github', project: 'depfu/example-ruby' },
staticPreview: this.render({
text: 'recent',
colorscheme: 'brightgreen',
}),
static openApi = {
'/depfu/dependencies/{vcsType}/{project}': {
get: {
summary: 'Depfu',
parameters: pathParams(
{
name: 'vcsType',
example: 'github',
schema: { type: 'string', enum: this.getEnum('vcsType') },
},
{
name: 'project',
example: 'depfu/example-ruby',
},
),
},
},
]
}
static defaultBadgeData = { label: 'dependencies' }

View File

@ -1,5 +1,6 @@
import Joi from 'joi'
import parseLinkHeader from 'parse-link-header'
import { pathParams } from '../index.js'
import { renderContributorBadge } from '../contributor-count.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, httpErrorsFor } from './github-helpers.js'
@ -14,18 +15,29 @@ export default class GithubContributors extends GithubAuthV3Service {
pattern: ':variant(contributors|contributors-anon)/:user/:repo',
}
static examples = [
{
title: 'GitHub contributors',
namedParams: {
variant: 'contributors',
user: 'cdnjs',
repo: 'cdnjs',
static openApi = {
'/github/{variant}/{user}/{repo}': {
get: {
summary: 'GitHub contributors',
description: documentation,
parameters: pathParams(
{
name: 'variant',
example: 'contributors',
schema: { type: 'string', enum: this.getEnum('variant') },
},
{
name: 'user',
example: 'cdnjs',
},
{
name: 'repo',
example: 'cdnjs',
},
),
},
staticPreview: this.render({ contributorCount: 397 }),
documentation,
},
]
}
static defaultBadgeData = { label: 'contributors' }

View File

@ -2,7 +2,7 @@ import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { formatDate, metric } from '../text-formatters.js'
import { age } from '../color-formatters.js'
import { InvalidResponse } from '../index.js'
import { InvalidResponse, pathParams } from '../index.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import {
documentation,
@ -179,35 +179,38 @@ export default class GithubIssueDetail extends GithubAuthV3Service {
':issueKind(issues|pulls)/detail/:property(state|title|author|label|comments|age|last-update|milestone)/:user/:repo/:number([0-9]+)',
}
static examples = [
{
title: 'GitHub issue/pull request detail',
namedParams: {
issueKind: 'issues',
property: 'state',
user: 'badges',
repo: 'shields',
number: '979',
static openApi = {
'/github/{issueKind}/detail/{property}/{user}/{repo}/{number}': {
get: {
summary: 'GitHub issue/pull request detail',
description: documentation,
parameters: pathParams(
{
name: 'issueKind',
example: 'issues',
schema: { type: 'string', enum: this.getEnum('issueKind') },
},
{
name: 'property',
example: 'state',
schema: { type: 'string', enum: this.getEnum('property') },
},
{
name: 'user',
example: 'badges',
},
{
name: 'repo',
example: 'shields',
},
{
name: 'number',
example: '979',
},
),
},
staticPreview: this.render({
property: 'state',
value: { state: 'closed' },
isPR: false,
number: '979',
}),
keywords: [
'state',
'title',
'author',
'label',
'comments',
'age',
'last update',
'milestone',
],
documentation,
},
]
}
static defaultBadgeData = {
label: 'issue/pull request',

View File

@ -1,4 +1,5 @@
import Joi from 'joi'
import { pathParams } from '../index.js'
import { metric } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { GithubAuthV3Service } from './github-auth-service.js'
@ -18,23 +19,33 @@ export default class GithubMilestoneDetail extends GithubAuthV3Service {
':variant(issues-closed|issues-open|issues-total|progress|progress-percent)/:user/:repo/:number([0-9]+)',
}
static examples = [
{
title: 'GitHub milestone',
namedParams: {
variant: 'issues-open',
user: 'badges',
repo: 'shields',
number: '1',
static openApi = {
'/github/milestones/{variant}/{user}/{repo}/{number}': {
get: {
summary: 'GitHub milestone details',
description: documentation,
parameters: pathParams(
{
name: 'variant',
example: 'issues-open',
schema: { type: 'string', enum: this.getEnum('variant') },
},
{
name: 'user',
example: 'badges',
},
{
name: 'repo',
example: 'shields',
},
{
name: 'number',
example: '1',
},
),
},
staticPreview: {
label: 'milestone issues',
message: '17/22',
color: 'blue',
},
documentation,
},
]
}
static defaultBadgeData = { label: 'milestones', color: 'informational' }

View File

@ -1,4 +1,5 @@
import Joi from 'joi'
import { pathParams } from '../index.js'
import { metric } from '../text-formatters.js'
import { GithubAuthV3Service } from './github-auth-service.js'
import { documentation, httpErrorsFor } from './github-helpers.js'
@ -18,22 +19,29 @@ export default class GithubMilestone extends GithubAuthV3Service {
pattern: ':variant(open|closed|all)/:user/:repo',
}
static examples = [
{
title: 'GitHub milestones',
namedParams: {
user: 'badges',
repo: 'shields',
variant: 'open',
static openApi = {
'/github/milestones/{variant}/{user}/{repo}': {
get: {
summary: 'GitHub number of milestones',
description: documentation,
parameters: pathParams(
{
name: 'variant',
example: 'open',
schema: { type: 'string', enum: this.getEnum('variant') },
},
{
name: 'user',
example: 'badges',
},
{
name: 'repo',
example: 'shields',
},
),
},
staticPreview: {
label: 'milestones',
message: '2',
color: 'red',
},
documentation,
},
]
}
static defaultBadgeData = {
label: 'milestones',

View File

@ -1,6 +1,6 @@
import Joi from 'joi'
import { renderDownloadsBadge } from '../downloads.js'
import { BaseJsonService } from '../index.js'
import { BaseJsonService, pathParams } from '../index.js'
import { nonNegativeInteger } from '../validators.js'
function getSchema({ formula }) {
@ -38,13 +38,24 @@ export default class HomebrewDownloads extends BaseJsonService {
pattern: 'installs/:interval(dm|dq|dy)/:formula',
}
static examples = [
{
title: 'homebrew downloads',
namedParams: { interval: 'dm', formula: 'cake' },
staticPreview: renderDownloadsBadge({ interval: 'month', downloads: 93 }),
static openApi = {
'/homebrew/installs/{interval}/{formula}': {
get: {
summary: 'homebrew downloads',
parameters: pathParams(
{
name: 'interval',
example: 'dm',
schema: { type: 'string', enum: this.getEnum('interval') },
},
{
name: 'formula',
example: 'cake',
},
),
},
},
]
}
static defaultBadgeData = { label: 'downloads' }

View File

@ -1,3 +1,4 @@
import { pathParams } from '../index.js'
import { schema, periodMap, BaseJsDelivrService } from './jsdelivr-base.js'
export default class JsDelivrHitsGitHub extends BaseJsDelivrService {
@ -6,17 +7,28 @@ export default class JsDelivrHitsGitHub extends BaseJsDelivrService {
pattern: ':period(hd|hw|hm|hy)/:user/:repo',
}
static examples = [
{
title: 'jsDelivr hits (GitHub)',
namedParams: {
period: 'hm',
user: 'jquery',
repo: 'jquery',
static openApi = {
'/jsdelivr/gh/{period}/{user}/{repo}': {
get: {
summary: 'jsDelivr hits (GitHub)',
parameters: pathParams(
{
name: 'period',
example: 'hm',
schema: { type: 'string', enum: this.getEnum('period') },
},
{
name: 'user',
example: 'jquery',
},
{
name: 'repo',
example: 'jquery',
},
),
},
staticPreview: this.render({ period: 'hm', hits: 9809876 }),
},
]
}
async fetch({ period, user, repo }) {
return this._requestJson({

View File

@ -1,7 +1,7 @@
import Joi from 'joi'
import { anyInteger } from '../validators.js'
import { metric } from '../text-formatters.js'
import { BaseJsonService } from '../index.js'
import { BaseJsonService, pathParams } from '../index.js'
const schema = Joi.object({
data: Joi.object({
@ -18,18 +18,24 @@ export default class RedditUserKarma extends BaseJsonService {
pattern: ':variant(link|comment|combined)/:user',
}
static examples = [
{
title: 'Reddit User Karma',
namedParams: { variant: 'combined', user: 'example' },
staticPreview: {
label: 'combined karma',
message: 56,
color: 'brightgreen',
style: 'social',
static openApi = {
'/reddit/user-karma/{variant}/{user}': {
get: {
summary: 'Reddit User Karma',
parameters: pathParams(
{
name: 'variant',
example: 'combined',
schema: { type: 'string', enum: this.getEnum('variant') },
},
{
name: 'user',
example: 'example',
},
),
},
},
]
}
static _cacheLength = 7200

View File

@ -1,7 +1,7 @@
import Joi from 'joi'
import { metric } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { BaseJsonService } from '../index.js'
import { BaseJsonService, pathParams } from '../index.js'
const schema = Joi.object({
count: nonNegativeInteger.required(),
@ -15,16 +15,24 @@ export default class SourceforgeOpenTickets extends BaseJsonService {
pattern: ':project/:type(bugs|feature-requests)',
}
static examples = [
{
title: 'Sourceforge Open Tickets',
namedParams: {
type: 'bugs',
project: 'sevenzip',
static openApi = {
'/sourceforge/open-tickets/{project}/{type}': {
get: {
summary: 'Sourceforge Open Tickets',
parameters: pathParams(
{
name: 'project',
example: 'sevenzip',
},
{
name: 'type',
example: 'bugs',
schema: { type: 'string', enum: this.getEnum('type') },
},
),
},
staticPreview: this.render({ count: 1338 }),
},
]
}
static defaultBadgeData = {
label: 'open tickets',

View File

@ -1,3 +1,4 @@
import { pathParams } from '../index.js'
import { metric as metricCount } from '../text-formatters.js'
import TestspaceBase from './testspace-base.js'
@ -8,21 +9,32 @@ export default class TestspaceTestCount extends TestspaceBase {
':metric(total|passed|failed|skipped|errored|untested)/:org/:project/:space+',
}
static examples = [
{
title: 'Testspace tests',
namedParams: {
metric: 'passed',
org: 'swellaby',
project: 'swellaby:testspace-sample',
space: 'main',
static openApi = {
'/testspace/{metric}/{org}/{project}/{space}': {
get: {
summary: 'Testspace tests',
parameters: pathParams(
{
name: 'metric',
example: 'passed',
schema: { type: 'string', enum: this.getEnum('metric') },
},
{
name: 'org',
example: 'swellaby',
},
{
name: 'project',
example: 'swellaby:testspace-sample',
},
{
name: 'space',
example: 'main',
},
),
},
staticPreview: this.render({
metric: 'passed',
value: 31,
}),
},
]
}
static render({ value, metric }) {
let color = 'informational'

View File

@ -1,3 +1,4 @@
import { pathParams } from '../index.js'
import { starRating } from '../text-formatters.js'
import { floorCount as floorCountColor } from '../color-formatters.js'
import { BaseVaadinDirectoryService } from './vaadin-directory-base.js'
@ -10,15 +11,24 @@ export default class VaadinDirectoryRating extends BaseVaadinDirectoryService {
pattern: ':format(star|stars|rating)/:packageName',
}
static examples = [
{
title: 'Vaadin Directory',
pattern: ':format(stars|rating)/:packageName',
namedParams: { format: 'rating', packageName: 'vaadinvaadin-grid' },
staticPreview: this.render({ format: 'rating', score: 4.75 }),
keywords: ['vaadin-directory'],
static openApi = {
'/vaadin-directory/{format}/{packageName}': {
get: {
summary: 'Vaadin Directory Rating',
parameters: pathParams(
{
name: 'format',
example: 'rating',
schema: { type: 'string', enum: this.getEnum('format') },
},
{
name: 'packageName',
example: 'vaadinvaadin-grid',
},
),
},
},
]
}
static defaultBadgeData = {
label: 'rating',