Add [CratesUserDownloads] service and tester (#10619)

* add jsdoc for crates fetch func

* add BaseCratesUserService for user stats api route

part of solution for #10614

* Add CratesUserDownloads service and tester

This commit adds the CratesUserDownloads service and tester files. The CratesUserDownloads service shows the user total downloads at Crates.io.

as requested by #10614

* render userid in code block

* add non-exsistent user CratesUserDownloads test

userid for API usage is int32, therefor to minimize chance of user taking the id used the max value for int32 is used.

* fixed typo
This commit is contained in:
jNullj
2024-10-20 23:03:54 +03:00
committed by GitHub
parent e7d76b117e
commit 66631524c5
3 changed files with 77 additions and 1 deletions

View File

@@ -24,9 +24,21 @@ const versionResponseSchema = Joi.object({
version: versionSchema.required(),
}).required()
const userStatsSchema = Joi.object({
total_downloads: nonNegativeInteger.required(),
}).required()
class BaseCratesService extends BaseJsonService {
static defaultBadgeData = { label: 'crates.io' }
/**
* Fetches data from the crates.io API.
*
* @param {object} options - The options for the request
* @param {string} options.crate - The crate name.
* @param {string} [options.version] - The crate version number (optional).
* @returns {Promise<object>} the JSON response from the API.
*/
async fetch({ crate, version }) {
const url = version
? `https://crates.io/api/v1/crates/${crate}/${version}`
@@ -54,7 +66,23 @@ class BaseCratesService extends BaseJsonService {
}
}
class BaseCratesUserService extends BaseJsonService {
static defaultBadgeData = { label: 'crates.io' }
/**
* Fetches data from the crates.io API.
*
* @param {object} options - The options for the request
* @param {string} options.userId - The user ID.
* @returns {Promise<object>} the JSON response from the API.
*/
async fetch({ userId }) {
const url = `https://crates.io/api/v1/users/${userId}/stats`
return this._requestJson({ schema: userStatsSchema, url })
}
}
const description =
'[Crates.io](https://crates.io/) is a package registry for Rust.'
export { BaseCratesService, description }
export { BaseCratesService, BaseCratesUserService, description }

View File

@@ -0,0 +1,32 @@
import { renderDownloadsBadge } from '../downloads.js'
import { pathParams } from '../index.js'
import { BaseCratesUserService, description } from './crates-base.js'
export default class CratesUserDownloads extends BaseCratesUserService {
static category = 'downloads'
static route = {
base: 'crates',
pattern: 'udt/:userId',
}
static openApi = {
'/crates/udt/{userId}': {
get: {
summary: 'Crates.io User Total Downloads',
description,
parameters: pathParams({
name: 'userId',
example: '3027',
description:
'The user ID can be found using `https://crates.io/api/v1/users/{username}`',
}),
},
},
}
async handle({ userId }) {
const json = await this.fetch({ userId })
const { total_downloads: downloads } = json
return renderDownloadsBadge({ downloads, labelOverride: 'downloads' })
}
}

View File

@@ -0,0 +1,16 @@
import { isMetric } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('total user downloads')
.get('/udt/3027.json')
.expectBadge({ label: 'downloads', message: isMetric })
// non-existent user returns 0 downloads with 200 OK status code rather than 404.
t.create('total user downloads (user not found)')
.get('/udt/2147483647.json') // 2147483647 is the maximum valid user id as API uses i32
.expectBadge({ label: 'downloads', message: '0' })
t.create('total user downloads (invalid)')
.get('/udt/999999999999999999999999.json')
.expectBadge({ label: 'crates.io', message: 'invalid' })