diff --git a/core/base-service/base.js b/core/base-service/base.js
index 270c3e20867b7..96efddbf920e2 100644
--- a/core/base-service/base.js
+++ b/core/base-service/base.js
@@ -189,6 +189,22 @@ class BaseService {
this.examples.forEach((example, index) =>
validateExample(example, index, this),
)
+
+ // ensure openApi spec matches route
+ if (this.openApi) {
+ const preparedRoute = prepareRoute(this.route)
+ for (const [key, value] of Object.entries(this.openApi)) {
+ let example = key
+ for (const param of value.get.parameters) {
+ example = example.replace(`{${param.name}}`, param.example)
+ }
+ if (!example.match(preparedRoute.regex)) {
+ throw new Error(
+ `Inconsistent Open Api spec and Route found for service ${this.name}`,
+ )
+ }
+ }
+ }
}
static getDefinition() {
diff --git a/core/base-service/index.js b/core/base-service/index.js
index f249090501724..5808b6211d56a 100644
--- a/core/base-service/index.js
+++ b/core/base-service/index.js
@@ -15,6 +15,7 @@ import {
Deprecated,
ImproperlyConfigured,
} from './errors.js'
+import { pathParam, pathParams, queryParam, queryParams } from './openapi.js'
export {
BaseService,
@@ -32,4 +33,8 @@ export {
InvalidParameter,
ImproperlyConfigured,
Deprecated,
+ pathParam,
+ pathParams,
+ queryParam,
+ queryParams,
}
diff --git a/core/base-service/loader.js b/core/base-service/loader.js
index 7cd272e0d0500..9b72355436299 100644
--- a/core/base-service/loader.js
+++ b/core/base-service/loader.js
@@ -83,6 +83,18 @@ async function loadServiceClasses(servicePaths) {
},
)
+ const routeSummaries = []
+ serviceClasses.forEach(function (serviceClass) {
+ if (serviceClass.openApi) {
+ for (const route of Object.values(serviceClass.openApi)) {
+ routeSummaries.push(route.get.summary)
+ }
+ }
+ })
+ assertNamesUnique(routeSummaries, {
+ message: 'Duplicate route summary found',
+ })
+
return serviceClasses
}
diff --git a/core/base-service/openapi.js b/core/base-service/openapi.js
index c357e5aed7960..92847b71f52ae 100644
--- a/core/base-service/openapi.js
+++ b/core/base-service/openapi.js
@@ -1,3 +1,9 @@
+/**
+ * Functions for publishing the shields.io URL schema as an OpenAPI Document
+ *
+ * @module
+ */
+
const baseUrl = process.env.BASE_URL
const globalParamRefs = [
{ $ref: '#/components/parameters/style' },
@@ -332,4 +338,132 @@ function category2openapi(category, services) {
return spec
}
-export { category2openapi }
+/**
+ * Helper function for assembling an OpenAPI path parameter object
+ *
+ * @param {module:core/base-service/openapi~PathParamInput} param Input param
+ * @returns {module:core/base-service/openapi~OpenApiParam} OpenAPI Parameter Object
+ * @see https://swagger.io/specification/#parameter-object
+ */
+function pathParam({
+ name,
+ example,
+ schema = { type: 'string' },
+ description,
+}) {
+ return { name, in: 'path', required: true, schema, example, description }
+}
+
+/**
+ * Helper function for assembling an array of OpenAPI path parameter objects
+ * The code
+ * ```
+ * const params = pathParams(
+ * { name: 'name1', example: 'example1' },
+ * { name: 'name2', example: 'example2' },
+ * )
+ * ```
+ * is equivilent to
+ * ```
+ * const params = [
+ * pathParam({ name: 'name1', example: 'example1' }),
+ * pathParam({ name: 'name2', example: 'example2' }),
+ * ]
+ * ```
+ *
+ * @param {...module:core/base-service/openapi~PathParamInput} params Input params
+ * @returns {Array.} Array of OpenAPI Parameter Objects
+ * @see {@link module:core/base-service/openapi~pathParam}
+ */
+function pathParams(...params) {
+ return params.map(param => pathParam(param))
+}
+
+/**
+ * Helper function for assembling an OpenAPI query parameter object
+ *
+ * @param {module:core/base-service/openapi~QueryParamInput} param Input param
+ * @returns {module:core/base-service/openapi~OpenApiParam} OpenAPI Parameter Object
+ * @see https://swagger.io/specification/#parameter-object
+ */
+function queryParam({
+ name,
+ example,
+ schema = { type: 'string' },
+ required = false,
+ description,
+}) {
+ const param = { name, in: 'query', required, schema, example, description }
+ if (example === null && schema.type === 'boolean') {
+ param.allowEmptyValue = true
+ }
+ return param
+}
+
+/**
+ * Helper function for assembling an array of OpenAPI query parameter objects
+ * The code
+ * ```
+ * const params = queryParams(
+ * { name: 'name1', example: 'example1' },
+ * { name: 'name2', example: 'example2' },
+ * )
+ * ```
+ * is equivilent to
+ * ```
+ * const params = [
+ * queryParam({ name: 'name1', example: 'example1' }),
+ * queryParams({ name: 'name2', example: 'example2' }),
+ * ]
+ * ```
+ *
+ * @param {...module:core/base-service/openapi~QueryParamInput} params Input params
+ * @returns {Array.} Array of OpenAPI Parameter Objects
+ * @see {@link module:core/base-service/openapi~queryParam}
+ */
+function queryParams(...params) {
+ return params.map(param => queryParam(param))
+}
+
+/**
+ * @typedef {object} PathParamInput
+ * @property {string} name The name of the parameter. Parameter names are case sensitive
+ * @property {string} example Example of a valid value for this parameter
+ * @property {object} [schema={ type: 'string' }] Parameter schema.
+ * An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
+ * specifying the parameter type.
+ * Normally this should be omitted as all path parameters are strings.
+ * Use this when we also want to pass an enum of valid parameters
+ * to be presented as a drop-down in the frontend. e.g:
+ * `{'type': 'string', 'enum': ['github', 'bitbucket'}` (Optional)
+ * @property {string} description A brief description of the parameter (Optional)
+ */
+
+/**
+ * @typedef {object} QueryParamInput
+ * @property {string} name The name of the parameter. Parameter names are case sensitive
+ * @property {string|null} example Example of a valid value for this parameter
+ * @property {object} [schema={ type: 'string' }] Parameter schema.
+ * An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
+ * specifying the parameter type. This can normally be omitted.
+ * Query params are usually strings. (Optional)
+ * @property {boolean} [required=false] Determines whether this parameter is mandatory (Optional)
+ * @property {string} description A brief description of the parameter (Optional)
+ */
+
+/**
+ * OpenAPI Parameter Object
+ *
+ * @typedef {object} OpenApiParam
+ * @property {string} name The name of the parameter
+ * @property {string|null} example Example of a valid value for this parameter
+ * @property {('path'|'query')} in The location of the parameter
+ * @property {object} schema Parameter schema.
+ * An [OpenAPI Schema object](https://swagger.io/specification/#schema-object)
+ * specifying the parameter type.
+ * @property {boolean} required Determines whether this parameter is mandatory
+ * @property {string} description A brief description of the parameter
+ * @property {boolean} allowEmptyValue If true, allows the ability to pass an empty value to this parameter
+ */
+
+export { category2openapi, pathParam, pathParams, queryParam, queryParams }
diff --git a/core/base-service/openapi.spec.js b/core/base-service/openapi.spec.js
index a70d803716df1..0577f078e682c 100644
--- a/core/base-service/openapi.spec.js
+++ b/core/base-service/openapi.spec.js
@@ -1,5 +1,11 @@
import chai from 'chai'
-import { category2openapi } from './openapi.js'
+import {
+ category2openapi,
+ pathParam,
+ pathParams,
+ queryParam,
+ queryParams,
+} from './openapi.js'
import BaseJsonService from './base-json.js'
const { expect } = chai
@@ -376,3 +382,148 @@ describe('category2openapi', function () {
).to.deep.equal(expected)
})
})
+
+describe('pathParam, pathParams', function () {
+ it('generates a pathParam with defaults', function () {
+ const input = { name: 'name', example: 'example' }
+ const expected = {
+ name: 'name',
+ in: 'path',
+ required: true,
+ schema: {
+ type: 'string',
+ },
+ example: 'example',
+ description: undefined,
+ }
+ expect(pathParam(input)).to.deep.equal(expected)
+ expect(pathParams(input)[0]).to.deep.equal(expected)
+ })
+
+ it('generates a pathParam with custom args', function () {
+ const input = {
+ name: 'name',
+ example: true,
+ schema: { type: 'boolean' },
+ description: 'long desc',
+ }
+ const expected = {
+ name: 'name',
+ in: 'path',
+ required: true,
+ schema: {
+ type: 'boolean',
+ },
+ example: true,
+ description: 'long desc',
+ }
+ expect(pathParam(input)).to.deep.equal(expected)
+ expect(pathParams(input)[0]).to.deep.equal(expected)
+ })
+
+ it('generates multiple pathParams', function () {
+ expect(
+ pathParams(
+ { name: 'name1', example: 'example1' },
+ { name: 'name2', example: 'example2' },
+ ),
+ ).to.deep.equal([
+ {
+ name: 'name1',
+ in: 'path',
+ required: true,
+ schema: {
+ type: 'string',
+ },
+ example: 'example1',
+ description: undefined,
+ },
+ {
+ name: 'name2',
+ in: 'path',
+ required: true,
+ schema: {
+ type: 'string',
+ },
+ example: 'example2',
+ description: undefined,
+ },
+ ])
+ })
+})
+
+describe('queryParam, queryParams', function () {
+ it('generates a queryParam with defaults', function () {
+ const input = { name: 'name', example: 'example' }
+ const expected = {
+ name: 'name',
+ in: 'query',
+ required: false,
+ schema: { type: 'string' },
+ example: 'example',
+ description: undefined,
+ }
+ expect(queryParam(input)).to.deep.equal(expected)
+ expect(queryParams(input)[0]).to.deep.equal(expected)
+ })
+
+ it('generates queryParam with custom args', function () {
+ const input = {
+ name: 'name',
+ example: 'example',
+ required: true,
+ description: 'long desc',
+ }
+ const expected = {
+ name: 'name',
+ in: 'query',
+ required: true,
+ schema: { type: 'string' },
+ example: 'example',
+ description: 'long desc',
+ }
+ expect(queryParam(input)).to.deep.equal(expected)
+ expect(queryParams(input)[0]).to.deep.equal(expected)
+ })
+
+ it('generates a queryParam with boolean/null example', function () {
+ const input = { name: 'name', example: null, schema: { type: 'boolean' } }
+ const expected = {
+ name: 'name',
+ in: 'query',
+ required: false,
+ schema: { type: 'boolean' },
+ allowEmptyValue: true,
+ example: null,
+ description: undefined,
+ }
+ expect(queryParam(input)).to.deep.equal(expected)
+ expect(queryParams(input)[0]).to.deep.equal(expected)
+ })
+
+ it('generates multiple queryParams', function () {
+ expect(
+ queryParams(
+ { name: 'name1', example: 'example1' },
+ { name: 'name2', example: 'example2' },
+ ),
+ ).to.deep.equal([
+ {
+ name: 'name1',
+ in: 'query',
+ required: false,
+ schema: { type: 'string' },
+ example: 'example1',
+ description: undefined,
+ },
+ {
+ name: 'name2',
+ in: 'query',
+ required: false,
+ schema: { type: 'string' },
+ example: 'example2',
+ description: undefined,
+ },
+ ])
+ })
+})
diff --git a/core/base-service/service-definitions.js b/core/base-service/service-definitions.js
index 6687e5bd61fa4..c4beb656361c6 100644
--- a/core/base-service/service-definitions.js
+++ b/core/base-service/service-definitions.js
@@ -48,7 +48,7 @@ const serviceDefinition = Joi.object({
Joi.object({
get: Joi.object({
summary: Joi.string().required(),
- description: Joi.string().required(),
+ description: Joi.string(),
parameters: Joi.array()
.items(
Joi.object({
@@ -56,8 +56,12 @@ const serviceDefinition = Joi.object({
description: Joi.string(),
in: Joi.string().valid('query', 'path').required(),
required: Joi.boolean().required(),
- schema: Joi.object({ type: Joi.string().required() }).required(),
- example: Joi.string(),
+ schema: Joi.object({
+ type: Joi.string().required(),
+ enum: Joi.array(),
+ }).required(),
+ allowEmptyValue: Joi.boolean(),
+ example: Joi.string().allow(null),
}),
)
.min(1)
@@ -67,8 +71,8 @@ const serviceDefinition = Joi.object({
),
}).required()
-function assertValidServiceDefinition(example, message = undefined) {
- Joi.assert(example, serviceDefinition, message)
+function assertValidServiceDefinition(service, message = undefined) {
+ Joi.assert(service, serviceDefinition, message)
}
const serviceDefinitionExport = Joi.object({
diff --git a/services/amo/amo-downloads.service.js b/services/amo/amo-downloads.service.js
index ae61a534e0106..d8ca97447c50d 100644
--- a/services/amo/amo-downloads.service.js
+++ b/services/amo/amo-downloads.service.js
@@ -1,8 +1,8 @@
import { renderDownloadsBadge } from '../downloads.js'
-import { redirector } from '../index.js'
-import { BaseAmoService, keywords } from './amo-base.js'
+import { redirector, pathParams } from '../index.js'
+import { BaseAmoService } from './amo-base.js'
-const documentation = `
+const description = `
Previously \`amo/d\` provided a “total downloads” badge. However,
[updates to the v3 API](https://github.com/badges/shields/issues/3079)
only give us weekly downloads. The route \`amo/d\` redirects to \`amo/dw\`.
@@ -12,15 +12,15 @@ class AmoWeeklyDownloads extends BaseAmoService {
static category = 'downloads'
static route = { base: 'amo/dw', pattern: ':addonId' }
- static examples = [
- {
- title: 'Mozilla Add-on',
- namedParams: { addonId: 'dustman' },
- staticPreview: this.render({ downloads: 120 }),
- keywords,
- documentation,
+ static openApi = {
+ '/amo/dw/{addonId}': {
+ get: {
+ summary: 'Mozilla Add-on Downloads',
+ description,
+ parameters: pathParams({ name: 'addonId', example: 'dustman' }),
+ },
},
- ]
+ }
static _cacheLength = 21600
diff --git a/services/amo/amo-rating.service.js b/services/amo/amo-rating.service.js
index 57ad89be902ab..5930c1dd0f38b 100644
--- a/services/amo/amo-rating.service.js
+++ b/services/amo/amo-rating.service.js
@@ -1,27 +1,26 @@
import { starRating } from '../text-formatters.js'
import { floorCount as floorCountColor } from '../color-formatters.js'
-import { BaseAmoService, keywords } from './amo-base.js'
+import { pathParams } from '../index.js'
+import { BaseAmoService } from './amo-base.js'
export default class AmoRating extends BaseAmoService {
static category = 'rating'
static route = { base: 'amo', pattern: ':format(stars|rating)/:addonId' }
- static examples = [
- {
- title: 'Mozilla Add-on',
- pattern: 'rating/:addonId',
- namedParams: { addonId: 'dustman' },
- staticPreview: this.render({ format: 'rating', rating: 4 }),
- keywords,
+ static openApi = {
+ '/amo/rating/{addonId}': {
+ get: {
+ summary: 'Mozilla Add-on Rating',
+ parameters: pathParams({ name: 'addonId', example: 'dustman' }),
+ },
},
- {
- title: 'Mozilla Add-on',
- pattern: 'stars/:addonId',
- namedParams: { addonId: 'dustman' },
- staticPreview: this.render({ format: 'stars', rating: 4 }),
- keywords,
+ '/amo/stars/{addonId}': {
+ get: {
+ summary: 'Mozilla Add-on Stars',
+ parameters: pathParams({ name: 'addonId', example: 'dustman' }),
+ },
},
- ]
+ }
static _cacheLength = 7200
diff --git a/services/amo/amo-users.service.js b/services/amo/amo-users.service.js
index 0a841525bbb21..0a4b4be526dd7 100644
--- a/services/amo/amo-users.service.js
+++ b/services/amo/amo-users.service.js
@@ -1,18 +1,19 @@
import { renderDownloadsBadge } from '../downloads.js'
-import { BaseAmoService, keywords } from './amo-base.js'
+import { pathParams } from '../index.js'
+import { BaseAmoService } from './amo-base.js'
export default class AmoUsers extends BaseAmoService {
static category = 'downloads'
static route = { base: 'amo/users', pattern: ':addonId' }
- static examples = [
- {
- title: 'Mozilla Add-on',
- namedParams: { addonId: 'dustman' },
- staticPreview: this.render({ users: 750 }),
- keywords,
+ static openApi = {
+ '/amo/users/{addonId}': {
+ get: {
+ summary: 'Mozilla Add-on Users',
+ parameters: pathParams({ name: 'addonId', example: 'dustman' }),
+ },
},
- ]
+ }
static _cacheLength = 21600
diff --git a/services/amo/amo-version.service.js b/services/amo/amo-version.service.js
index 96394bba7fc44..219692437d0d4 100644
--- a/services/amo/amo-version.service.js
+++ b/services/amo/amo-version.service.js
@@ -1,18 +1,19 @@
import { renderVersionBadge } from '../version.js'
-import { BaseAmoService, keywords } from './amo-base.js'
+import { pathParams } from '../index.js'
+import { BaseAmoService } from './amo-base.js'
export default class AmoVersion extends BaseAmoService {
static category = 'version'
static route = { base: 'amo/v', pattern: ':addonId' }
- static examples = [
- {
- title: 'Mozilla Add-on',
- namedParams: { addonId: 'dustman' },
- staticPreview: renderVersionBadge({ version: '2.1.0' }),
- keywords,
+ static openApi = {
+ '/amo/v/{addonId}': {
+ get: {
+ summary: 'Mozilla Add-on Version',
+ parameters: pathParams({ name: 'addonId', example: 'dustman' }),
+ },
},
- ]
+ }
async handle({ addonId }) {
const data = await this.fetch({ addonId })
diff --git a/services/ansible/ansible-collection.service.js b/services/ansible/ansible-collection.service.js
index ae7cb07062970..131f86206eac8 100644
--- a/services/ansible/ansible-collection.service.js
+++ b/services/ansible/ansible-collection.service.js
@@ -1,5 +1,5 @@
import Joi from 'joi'
-import { BaseJsonService } from '../index.js'
+import { BaseJsonService, pathParams } from '../index.js'
const ansibleCollectionSchema = Joi.object({
name: Joi.string().required(),
@@ -12,15 +12,14 @@ class AnsibleGalaxyCollectionName extends BaseJsonService {
static category = 'other'
static route = { base: 'ansible/collection', pattern: ':collectionId' }
- static examples = [
- {
- title: 'Ansible Collection',
- namedParams: { collectionId: '278' },
- staticPreview: this.render({
- name: 'community.general',
- }),
+ static openApi = {
+ '/ansible/collection/{collectionId}': {
+ get: {
+ summary: 'Ansible Collection',
+ parameters: pathParams({ name: 'collectionId', example: '278' }),
+ },
},
- ]
+ }
static defaultBadgeData = { label: 'collection' }
diff --git a/services/ansible/ansible-quality.service.js b/services/ansible/ansible-quality.service.js
index 6af7ae16ca588..249d456e04fa1 100644
--- a/services/ansible/ansible-quality.service.js
+++ b/services/ansible/ansible-quality.service.js
@@ -1,6 +1,6 @@
import Joi from 'joi'
import { floorCount } from '../color-formatters.js'
-import { BaseJsonService, InvalidResponse } from '../index.js'
+import { BaseJsonService, InvalidResponse, pathParams } from '../index.js'
const ansibleContentSchema = Joi.object({
quality_score: Joi.number().allow(null).required(),
@@ -20,15 +20,14 @@ export default class AnsibleGalaxyContentQualityScore extends AnsibleGalaxyConte
static category = 'analysis'
static route = { base: 'ansible/quality', pattern: ':projectId' }
- static examples = [
- {
- title: 'Ansible Quality Score',
- namedParams: {
- projectId: '432',
+ static openApi = {
+ '/ansible/quality/{projectId}': {
+ get: {
+ summary: 'Ansible Quality Score',
+ parameters: pathParams({ name: 'projectId', example: '432' }),
},
- staticPreview: this.render({ qualityScore: 4.125 }),
},
- ]
+ }
static defaultBadgeData = { label: 'quality' }
diff --git a/services/ansible/ansible-role.service.js b/services/ansible/ansible-role.service.js
index ffc27193dc13d..acf5ccce42bee 100644
--- a/services/ansible/ansible-role.service.js
+++ b/services/ansible/ansible-role.service.js
@@ -1,7 +1,7 @@
import Joi from 'joi'
import { renderDownloadsBadge } from '../downloads.js'
import { nonNegativeInteger } from '../validators.js'
-import { BaseJsonService } from '../index.js'
+import { BaseJsonService, pathParams } from '../index.js'
const ansibleRoleSchema = Joi.object({
download_count: nonNegativeInteger,
@@ -27,13 +27,14 @@ class AnsibleGalaxyRoleDownloads extends AnsibleGalaxyRole {
static category = 'downloads'
static route = { base: 'ansible/role/d', pattern: ':roleId' }
- static examples = [
- {
- title: 'Ansible Role',
- namedParams: { roleId: '3078' },
- staticPreview: renderDownloadsBadge({ downloads: 76 }),
+ static openApi = {
+ '/ansible/role/d/{roleId}': {
+ get: {
+ summary: 'Ansible Role',
+ parameters: pathParams({ name: 'roleId', example: '3078' }),
+ },
},
- ]
+ }
static defaultBadgeData = { label: 'role downloads' }
@@ -47,15 +48,14 @@ class AnsibleGalaxyRoleName extends AnsibleGalaxyRole {
static category = 'other'
static route = { base: 'ansible/role', pattern: ':roleId' }
- static examples = [
- {
- title: 'Ansible Role',
- namedParams: { roleId: '3078' },
- staticPreview: this.render({
- name: 'ansible-roles.sublimetext3_packagecontrol',
- }),
+ static openApi = {
+ '/ansible/role/{roleId}': {
+ get: {
+ summary: 'Ansible Galaxy Role Name',
+ parameters: pathParams({ name: 'roleId', example: '3078' }),
+ },
},
- ]
+ }
static defaultBadgeData = { label: 'role' }
diff --git a/services/appveyor/appveyor-build.service.js b/services/appveyor/appveyor-build.service.js
index 29cb395835394..cc71150448669 100644
--- a/services/appveyor/appveyor-build.service.js
+++ b/services/appveyor/appveyor-build.service.js
@@ -1,23 +1,31 @@
import { renderBuildStatusBadge } from '../build-status.js'
+import { pathParams } from '../index.js'
import AppVeyorBase from './appveyor-base.js'
export default class AppVeyorBuild extends AppVeyorBase {
static route = this.buildRoute('appveyor/build')
- static examples = [
- {
- title: 'AppVeyor',
- pattern: ':user/:repo',
- namedParams: { user: 'gruntjs', repo: 'grunt' },
- staticPreview: this.render({ status: 'success' }),
+ static openApi = {
+ '/appveyor/build/{user}/{repo}': {
+ get: {
+ summary: 'AppVeyor Build',
+ parameters: pathParams(
+ { name: 'user', example: 'gruntjs' },
+ { name: 'repo', example: 'grunt' },
+ ),
+ },
},
- {
- title: 'AppVeyor branch',
- pattern: ':user/:repo/:branch',
- namedParams: { user: 'gruntjs', repo: 'grunt', branch: 'master' },
- staticPreview: this.render({ status: 'success' }),
+ '/appveyor/build/{user}/{repo}/{branch}': {
+ get: {
+ summary: 'AppVeyor Build (with branch)',
+ parameters: pathParams(
+ { name: 'user', example: 'gruntjs' },
+ { name: 'repo', example: 'grunt' },
+ { name: 'branch', example: 'master' },
+ ),
+ },
},
- ]
+ }
static render({ status }) {
return renderBuildStatusBadge({ status })
diff --git a/services/appveyor/appveyor-job-build.service.js b/services/appveyor/appveyor-job-build.service.js
index fc856aa7f25a2..59f5278ec8c38 100644
--- a/services/appveyor/appveyor-job-build.service.js
+++ b/services/appveyor/appveyor-job-build.service.js
@@ -1,5 +1,5 @@
import { renderBuildStatusBadge } from '../build-status.js'
-import { NotFound } from '../index.js'
+import { NotFound, pathParams } from '../index.js'
import AppVeyorBase from './appveyor-base.js'
export default class AppVeyorJobBuild extends AppVeyorBase {
@@ -8,29 +8,29 @@ export default class AppVeyorJobBuild extends AppVeyorBase {
pattern: ':user/:repo/:job/:branch*',
}
- static examples = [
- {
- title: 'AppVeyor Job',
- pattern: ':user/:repo/:job',
- namedParams: {
- user: 'wpmgprostotema',
- repo: 'voicetranscoder',
- job: 'Linux',
+ static openApi = {
+ '/appveyor/job/build/{user}/{repo}/{job}': {
+ get: {
+ summary: 'AppVeyor Job',
+ parameters: pathParams(
+ { name: 'user', example: 'wpmgprostotema' },
+ { name: 'repo', example: 'voicetranscoder' },
+ { name: 'job', example: 'Linux' },
+ ),
},
- staticPreview: renderBuildStatusBadge({ status: 'success' }),
},
- {
- title: 'AppVeyor Job branch',
- pattern: ':user/:repo/:job/:branch',
- namedParams: {
- user: 'wpmgprostotema',
- repo: 'voicetranscoder',
- job: 'Windows',
- branch: 'master',
+ '/appveyor/job/build/{user}/{repo}/{job}/{branch}': {
+ get: {
+ summary: 'AppVeyor Job (with branch)',
+ parameters: pathParams(
+ { name: 'user', example: 'wpmgprostotema' },
+ { name: 'repo', example: 'voicetranscoder' },
+ { name: 'job', example: 'Windows' },
+ { name: 'branch', example: 'master' },
+ ),
},
- staticPreview: renderBuildStatusBadge({ status: 'success' }),
},
- ]
+ }
transform({ data, jobName }) {
if (!('build' in data)) {
diff --git a/services/appveyor/appveyor-tests.service.js b/services/appveyor/appveyor-tests.service.js
index 05df133dfc843..2bef4b4cc0798 100644
--- a/services/appveyor/appveyor-tests.service.js
+++ b/services/appveyor/appveyor-tests.service.js
@@ -1,18 +1,12 @@
import {
testResultQueryParamSchema,
+ testResultOpenApiQueryParams,
renderTestResultBadge,
- documentation,
+ documentation as description,
} from '../test-results.js'
+import { pathParams } from '../index.js'
import AppVeyorBase from './appveyor-base.js'
-const commonPreviewProps = {
- passed: 477,
- failed: 2,
- skipped: 0,
- total: 479,
- isCompact: false,
-}
-
export default class AppVeyorTests extends AppVeyorBase {
static category = 'test-results'
static route = {
@@ -20,63 +14,35 @@ export default class AppVeyorTests extends AppVeyorBase {
queryParamSchema: testResultQueryParamSchema,
}
- static examples = [
- {
- title: 'AppVeyor tests',
- pattern: ':user/:repo',
- namedParams: {
- user: 'NZSmartie',
- repo: 'coap-net-iu0to',
- },
- staticPreview: this.render(commonPreviewProps),
- documentation,
- },
- {
- title: 'AppVeyor tests (branch)',
- pattern: ':user/:repo/:branch',
- namedParams: {
- user: 'NZSmartie',
- repo: 'coap-net-iu0to',
- branch: 'master',
+ static openApi = {
+ '/appveyor/tests/{user}/{repo}': {
+ get: {
+ summary: 'AppVeyor tests',
+ description,
+ parameters: [
+ ...pathParams(
+ { name: 'user', example: 'NZSmartie' },
+ { name: 'repo', example: 'coap-net-iu0to' },
+ ),
+ ...testResultOpenApiQueryParams,
+ ],
},
- staticPreview: this.render(commonPreviewProps),
- documentation,
},
- {
- title: 'AppVeyor tests (compact)',
- pattern: ':user/:repo',
- namedParams: {
- user: 'NZSmartie',
- repo: 'coap-net-iu0to',
- },
- queryParams: { compact_message: null },
- staticPreview: this.render({
- ...commonPreviewProps,
- isCompact: true,
- }),
- documentation,
- },
- {
- title: 'AppVeyor tests with custom labels',
- pattern: ':user/:repo',
- namedParams: {
- user: 'NZSmartie',
- repo: 'coap-net-iu0to',
- },
- queryParams: {
- passed_label: 'good',
- failed_label: 'bad',
- skipped_label: 'n/a',
+ '/appveyor/tests/{user}/{repo}/{branch}': {
+ get: {
+ summary: 'AppVeyor tests (with branch)',
+ description,
+ parameters: [
+ ...pathParams(
+ { name: 'user', example: 'NZSmartie' },
+ { name: 'repo', example: 'coap-net-iu0to' },
+ { name: 'branch', example: 'master' },
+ ),
+ ...testResultOpenApiQueryParams,
+ ],
},
- staticPreview: this.render({
- ...commonPreviewProps,
- passedLabel: 'good',
- failedLabel: 'bad',
- skippedLabel: 'n/a',
- }),
- documentation,
},
- ]
+ }
static defaultBadgeData = {
label: 'tests',
diff --git a/services/dynamic/dynamic-json.service.js b/services/dynamic/dynamic-json.service.js
index 68ffd4f6dc54d..f730395073471 100644
--- a/services/dynamic/dynamic-json.service.js
+++ b/services/dynamic/dynamic-json.service.js
@@ -1,5 +1,5 @@
import { MetricNames } from '../../core/base-service/metric-helper.js'
-import { BaseJsonService } from '../index.js'
+import { BaseJsonService, queryParams } from '../index.js'
import { createRoute } from './dynamic-helpers.js'
import jsonPath from './json-path.js'
@@ -14,13 +14,11 @@ export default class DynamicJson extends jsonPath(BaseJsonService) {
The Dynamic JSON Badge allows you to extract an arbitrary value from any
JSON Document using a JSONPath selector and show it on a badge.
`,
- parameters: [
+ parameters: queryParams(
{
name: 'url',
description: 'The URL to a JSON document',
- in: 'query',
required: true,
- schema: { type: 'string' },
example:
'https://github.com/badges/shields/raw/master/package.json',
},
@@ -28,28 +26,20 @@ export default class DynamicJson extends jsonPath(BaseJsonService) {
name: 'query',
description:
'A JSONPath expression that will be used to query the document',
- in: 'query',
required: true,
- schema: { type: 'string' },
example: '$.name',
},
{
name: 'prefix',
description: 'Optional prefix to append to the value',
- in: 'query',
- required: false,
- schema: { type: 'string' },
example: '[',
},
{
name: 'suffix',
description: 'Optional suffix to append to the value',
- in: 'query',
- required: false,
- schema: { type: 'string' },
example: ']',
},
- ],
+ ),
},
},
}
diff --git a/services/dynamic/dynamic-xml.service.js b/services/dynamic/dynamic-xml.service.js
index fd4216c5a0302..cd84d2f547868 100644
--- a/services/dynamic/dynamic-xml.service.js
+++ b/services/dynamic/dynamic-xml.service.js
@@ -2,7 +2,12 @@ import { DOMParser } from '@xmldom/xmldom'
import xpath from 'xpath'
import { MetricNames } from '../../core/base-service/metric-helper.js'
import { renderDynamicBadge, httpErrors } from '../dynamic-common.js'
-import { BaseService, InvalidResponse, InvalidParameter } from '../index.js'
+import {
+ BaseService,
+ InvalidResponse,
+ InvalidParameter,
+ queryParams,
+} from '../index.js'
import { createRoute } from './dynamic-helpers.js'
// This service extends BaseService because it uses a different XML parser
@@ -23,41 +28,31 @@ export default class DynamicXml extends BaseService {
The Dynamic XML Badge allows you to extract an arbitrary value from any
XML Document using an XPath selector and show it on a badge.
`,
- parameters: [
+ parameters: queryParams(
{
name: 'url',
description: 'The URL to a XML document',
- in: 'query',
required: true,
- schema: { type: 'string' },
example: 'https://httpbin.org/xml',
},
{
name: 'query',
description:
'A XPath expression that will be used to query the document',
- in: 'query',
required: true,
- schema: { type: 'string' },
example: '//slideshow/slide[1]/title',
},
{
name: 'prefix',
description: 'Optional prefix to append to the value',
- in: 'query',
- required: false,
- schema: { type: 'string' },
example: '[',
},
{
name: 'suffix',
description: 'Optional suffix to append to the value',
- in: 'query',
- required: false,
- schema: { type: 'string' },
example: ']',
},
- ],
+ ),
},
},
}
diff --git a/services/dynamic/dynamic-yaml.service.js b/services/dynamic/dynamic-yaml.service.js
index c1a5ab29ce229..0857c8ef144b4 100644
--- a/services/dynamic/dynamic-yaml.service.js
+++ b/services/dynamic/dynamic-yaml.service.js
@@ -1,5 +1,5 @@
import { MetricNames } from '../../core/base-service/metric-helper.js'
-import { BaseYamlService } from '../index.js'
+import { BaseYamlService, queryParams } from '../index.js'
import { createRoute } from './dynamic-helpers.js'
import jsonPath from './json-path.js'
@@ -14,13 +14,11 @@ export default class DynamicYaml extends jsonPath(BaseYamlService) {
The Dynamic YAML Badge allows you to extract an arbitrary value from any
YAML Document using a JSONPath selector and show it on a badge.
`,
- parameters: [
+ parameters: queryParams(
{
name: 'url',
description: 'The URL to a YAML document',
- in: 'query',
required: true,
- schema: { type: 'string' },
example:
'https://raw.githubusercontent.com/badges/shields/master/.github/dependabot.yml',
},
@@ -28,28 +26,20 @@ export default class DynamicYaml extends jsonPath(BaseYamlService) {
name: 'query',
description:
'A JSONPath expression that will be used to query the document',
- in: 'query',
required: true,
- schema: { type: 'string' },
example: '$.version',
},
{
name: 'prefix',
description: 'Optional prefix to append to the value',
- in: 'query',
- required: false,
- schema: { type: 'string' },
example: '[',
},
{
name: 'suffix',
description: 'Optional suffix to append to the value',
- in: 'query',
- required: false,
- schema: { type: 'string' },
example: ']',
},
- ],
+ ),
},
},
}
diff --git a/services/endpoint/endpoint.service.js b/services/endpoint/endpoint.service.js
index 46bc6c8412615..33d4788b02804 100644
--- a/services/endpoint/endpoint.service.js
+++ b/services/endpoint/endpoint.service.js
@@ -3,7 +3,7 @@ import Joi from 'joi'
import { httpErrors } from '../dynamic-common.js'
import { optionalUrl } from '../validators.js'
import { fetchEndpointData } from '../endpoint-common.js'
-import { BaseJsonService, InvalidParameter } from '../index.js'
+import { BaseJsonService, InvalidParameter, queryParams } from '../index.js'
const blockedDomains = ['github.com', 'shields.io']
@@ -135,16 +135,12 @@ export default class Endpoint extends BaseJsonService {
get: {
summary: 'Endpoint Badge',
description,
- parameters: [
- {
- name: 'url',
- description: 'The URL to your JSON endpoint',
- in: 'query',
- required: true,
- schema: { type: 'string' },
- example: 'https://shields.redsparr0w.com/2473/monday',
- },
- ],
+ parameters: queryParams({
+ name: 'url',
+ description: 'The URL to your JSON endpoint',
+ required: true,
+ example: 'https://shields.redsparr0w.com/2473/monday',
+ }),
},
},
}
diff --git a/services/test-results.js b/services/test-results.js
index 84f6831762087..93e21c3f36a9a 100644
--- a/services/test-results.js
+++ b/services/test-results.js
@@ -1,4 +1,5 @@
import Joi from 'joi'
+import { queryParams } from './index.js'
const testResultQueryParamSchema = Joi.object({
compact_message: Joi.equal(''),
@@ -7,6 +8,17 @@ const testResultQueryParamSchema = Joi.object({
skipped_label: Joi.string(),
}).required()
+const testResultOpenApiQueryParams = queryParams(
+ {
+ name: 'compact_message',
+ example: null,
+ schema: { type: 'boolean' },
+ },
+ { name: 'passed_label', example: 'good' },
+ { name: 'failed_label', example: 'bad' },
+ { name: 'skipped_label', example: 'n/a' },
+)
+
function renderTestResultMessage({
passed,
failed,
@@ -89,13 +101,13 @@ const documentation = `
For example, if you want to use a different terminology:
-
+
?passed_label=good&failed_label=bad&skipped_label=n%2Fa
Or symbols:
-
+
?compact_message&passed_label=💃&failed_label=🤦♀️&skipped_label=🤷
@@ -106,6 +118,7 @@ const documentation = `
export {
testResultQueryParamSchema,
+ testResultOpenApiQueryParams,
renderTestResultMessage,
renderTestResultBadge,
documentation,