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

Convert examples arrays to openApi objects (part 1) (#9320)

* add helper functions for generating Open API path/query params with defaults

* tweak Open API schema

- make description optional
- allow null example + allowEmptyValue (for boolean query params)

* convert examples --> openApi in amo

* convert examples --> openApi in ansible

* convert examples --> openApi in appveyor build/job

* add re-usable Open API query param for test-results badges

we can use these for all the 'test results' badges

* convert examples --> openApi in appveyor tests

* DRY up existing dynamic/endpoint param definitions

* DRY up queryParam

* allow enum param in serviceDefinition schema

* improve misleading param name

* check route and openApi are consistent on service load

* fix mistake in ansible role route

* documentation --> description

* add pathParams and queryParams helpers +docstrings

* give everything a search-friendly summary, check for duplicate summary

* prettier fixup
This commit is contained in:
chris48s 2023-07-31 12:22:33 +01:00 committed by GitHub
parent 1bfda7a54b
commit 57c2ba0d68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 506 additions and 227 deletions

View File

@ -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() {

View File

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

View File

@ -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
}

View File

@ -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.<module:core/base-service/openapi~OpenApiParam>} 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.<module:core/base-service/openapi~OpenApiParam>} 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 }

View File

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

View File

@ -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({

View File

@ -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 &ldquo;total downloads&rdquo; 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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

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

View File

@ -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)) {

View File

@ -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',
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 (branch)',
pattern: ':user/:repo/:branch',
namedParams: {
user: 'NZSmartie',
repo: 'coap-net-iu0to',
branch: 'master',
'/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),
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',
},
staticPreview: this.render({
...commonPreviewProps,
passedLabel: 'good',
failedLabel: 'bad',
skippedLabel: 'n/a',
}),
documentation,
},
]
}
static defaultBadgeData = {
label: 'tests',

View File

@ -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.
</p>`,
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 <a href="https://jsonpath.com/">JSONPath</a> 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: ']',
},
],
),
},
},
}

View File

@ -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.
</p>`,
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 <a href="http://xpather.com/">XPath</a> 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: ']',
},
],
),
},
},
}

View File

@ -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.
</p>`,
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 <a href="https://jsonpath.com/">JSONPath</a> 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: ']',
},
],
),
},
},
}

View File

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

View File

@ -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 = `
<p>
For example, if you want to use a different terminology:
<br>
<br />
<code>?passed_label=good&failed_label=bad&skipped_label=n%2Fa</code>
</p>
<p>
Or symbols:
<br>
<br />
<code>?compact_message&passed_label=💃&failed_label=🤦&skipped_label=🤷</code>
</p>
@ -106,6 +118,7 @@ const documentation = `
export {
testResultQueryParamSchema,
testResultOpenApiQueryParams,
renderTestResultMessage,
renderTestResultBadge,
documentation,