* allow serviceData to override cacheSeconds with a longer value * prevent [endpoint] json cacheSeconds property exceeding service default * allow ShieldsRuntimeError to specify a cacheSeconds property By default error responses use the cacheLength of the service class throwing the error. This allows error to tell the handling layer the maxAge that should be set on the error badge response. * add customExceptions param This 1. allows us to specify custom properties to pass to the exception constructor if we throw any of the standard got errors e.g: `ETIMEDOUT`, `ECONNRESET`, etc 2. uses a custom `cacheSeconds` property (if set on the exception) to set the response maxAge * customExceptions --> systemErrors * errorMessages --> httpErrors
79 lines
2.4 KiB
JavaScript
79 lines
2.4 KiB
JavaScript
/**
|
|
* @module
|
|
*/
|
|
|
|
import Joi from 'joi'
|
|
import jp from 'jsonpath'
|
|
import { renderDynamicBadge, httpErrors } from '../dynamic-common.js'
|
|
import { InvalidParameter, InvalidResponse } from '../index.js'
|
|
|
|
/**
|
|
* Dynamic service class factory which wraps {@link module:core/base-service/base~BaseService} with support of {@link https://jsonpath.com/|JSONPath}.
|
|
*
|
|
* @param {Function} superclass class to extend
|
|
* @returns {Function} wrapped class
|
|
*/
|
|
export default superclass =>
|
|
class extends superclass {
|
|
static category = 'dynamic'
|
|
static defaultBadgeData = { label: 'custom badge' }
|
|
|
|
/**
|
|
* Request data from an upstream API, transform it to JSON and validate against a schema
|
|
*
|
|
* @param {object} attrs Refer to individual attrs
|
|
* @param {Joi} attrs.schema Joi schema to validate the response transformed to JSON
|
|
* @param {string} attrs.url URL to request
|
|
* @param {object} [attrs.httpErrors={}] Key-value map of status codes
|
|
* and custom error messages e.g: `{ 404: 'package not found' }`.
|
|
* This can be used to extend or override the
|
|
* [default](https://github.com/badges/shields/blob/master/services/dynamic-common.js#L8)
|
|
* @returns {object} Parsed response
|
|
*/
|
|
async fetch({ schema, url, httpErrors }) {
|
|
throw new Error(
|
|
`fetch() function not implemented for ${this.constructor.name}`
|
|
)
|
|
}
|
|
|
|
async handle(namedParams, { url, query: pathExpression, prefix, suffix }) {
|
|
const data = await this.fetch({
|
|
schema: Joi.any(),
|
|
url,
|
|
httpErrors,
|
|
})
|
|
|
|
// JSONPath only works on objects and arrays.
|
|
// https://github.com/badges/shields/issues/4018
|
|
if (typeof data !== 'object') {
|
|
throw new InvalidResponse({
|
|
prettyMessage: 'resource must contain an object or array',
|
|
})
|
|
}
|
|
|
|
let values
|
|
try {
|
|
values = jp.query(data, pathExpression)
|
|
} catch (e) {
|
|
const { message } = e
|
|
if (
|
|
message.startsWith('Lexical error') ||
|
|
message.startsWith('Parse error') ||
|
|
message.includes('Unexpected token')
|
|
) {
|
|
throw new InvalidParameter({
|
|
prettyMessage: 'unparseable jsonpath query',
|
|
})
|
|
} else {
|
|
throw e
|
|
}
|
|
}
|
|
|
|
if (!values.length) {
|
|
throw new InvalidResponse({ prettyMessage: 'no result' })
|
|
}
|
|
|
|
return renderDynamicBadge({ value: values, prefix, suffix })
|
|
}
|
|
}
|