Skip to content

Commit

Permalink
Add [CratesUserDownloads] service and tester (#10619)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jNullj authored Oct 20, 2024
1 parent e7d76b1 commit 6663152
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 1 deletion.
30 changes: 29 additions & 1 deletion services/crates/crates-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand Down Expand Up @@ -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 }
32 changes: 32 additions & 0 deletions services/crates/crates-user-downloads.service.js
Original file line number Diff line number Diff line change
@@ -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' })
}
}
16 changes: 16 additions & 0 deletions services/crates/crates-user-downloads.tester.js
Original file line number Diff line number Diff line change
@@ -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' })

0 comments on commit 6663152

Please sign in to comment.