Skip to content

Commit 8e396ed

Browse files
committed
Validate KV metadata with Zod schema
1 parent c7b5838 commit 8e396ed

3 files changed

Lines changed: 84 additions & 54 deletions

File tree

src/routes/library.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const handleGetLibraryVersion = async (ctx: Context) => {
5959
// Load SRI data if needed
6060
if ('sri' in response) {
6161
// Get SRI for version
62-
const latestSriData = await libraryVersionSri(lib.name, ctx.req.param('version')).catch(() => {});
62+
const latestSriData = await libraryVersionSri(lib.name, ctx.req.param('version')).catch(() => ({}));
6363
response.sri = sriForVersion(lib.name, ctx.req.param('version'), version, latestSriData);
6464
}
6565

@@ -115,7 +115,7 @@ const handleGetLibrary = async (ctx: Context) => {
115115
const assets = await libraryVersion(lib.name, lib.version);
116116

117117
// Fetch the SRI data, ignore errors as they'll be reported by sriForVersion
118-
const sriData = await libraryVersionSri(lib.name, lib.version).catch(() => {});
118+
const sriData = await libraryVersionSri(lib.name, lib.version).catch(() => ({}));
119119

120120
// Produce the assets array with just the latest version
121121
response.assets = [ {
@@ -143,7 +143,7 @@ const handleGetLibrary = async (ctx: Context) => {
143143
// If no SRI value yet, fetch
144144
if (!response.sri) {
145145
// Get SRI for version, ignore errors as they'll be reported by sriForVersion
146-
const latestSriData = await libraryVersionSri(lib.name, lib.version).catch(() => {});
146+
const latestSriData = await libraryVersionSri(lib.name, lib.version).catch(() => ({}));
147147
response.sri = sriForVersion(
148148
lib.name,
149149
lib.version,

src/utils/kvMetadata.schema.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as z from 'zod';
2+
3+
export const librariesSchema = z.array(z.string());
4+
5+
export type Libraries = z.infer<typeof librariesSchema>;
6+
7+
export const librarySchema = z.object({
8+
name: z.string(),
9+
filename: z.string(),
10+
version: z.string(),
11+
description: z.string(),
12+
keywords: z.array(z.string()),
13+
homepage: z.string().optional(),
14+
license: z.string().optional(),
15+
author: z.string(),
16+
repository: z.object({
17+
type: z.string(),
18+
url: z.string(),
19+
}).nullable(),
20+
autoupdate: z.union([
21+
z.object({
22+
type: z.string(),
23+
target: z.string(),
24+
}),
25+
z.object({
26+
source: z.string(),
27+
target: z.string(),
28+
}),
29+
]).optional(),
30+
});
31+
32+
export type Library = z.infer<typeof librarySchema>;
33+
34+
export const libraryVersionsSchema = z.array(z.string());
35+
36+
export type LibraryVersions = z.infer<typeof libraryVersionsSchema>;
37+
38+
export const libraryVersionSchema = z.array(z.string());
39+
40+
export type LibraryVersion = z.infer<typeof libraryVersionSchema>;
41+
42+
export const libraryVersionSriSchema = z.record(z.string(), z.string());
43+
44+
export type LibraryVersionSri = z.infer<typeof libraryVersionSriSchema>;

src/utils/kvMetadata.ts

Lines changed: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,67 @@
1-
import * as Sentry from '@sentry/cloudflare';
21
import { env } from 'cloudflare:workers';
32

43
import fetchJson from './fetchJson.ts';
54
import sortVersions from './sort.ts';
5+
import {
6+
librariesSchema,
7+
librarySchema,
8+
libraryVersionSchema,
9+
libraryVersionSriSchema,
10+
libraryVersionsSchema,
11+
} from './kvMetadata.schema.ts';
612

713
const kvBase = env.METADATA_BASE || 'https://metadata.speedcdnjs.com';
814

915
/**
1016
* Get a list of libraries.
11-
*
12-
* @return {Promise<string[]>}
1317
*/
14-
export const libraries = () => fetchJson(`${kvBase}/packages`);
15-
16-
/**
17-
* @template {Object} T
18-
*
19-
* Validate the data we get from KV for a library.
20-
*
21-
* @param {string} library Requested library name.
22-
* @param {T} data Returned library data to validate.
23-
* @return {T & { assets: [] }}
24-
*/
25-
const kvLibraryValidate = (library, data) => {
26-
// Assets might not exist if there are none, but we should make it an empty array by default
27-
data.assets = data.assets || [];
28-
29-
// Non-breaking issues
30-
if (library !== data.name) {
31-
console.info('Name mismatch', library, data.name);
32-
data.name = library;
33-
}
34-
35-
// Breaking issues
36-
if (!data.version) {
37-
console.error('Version missing', data.name, data);
38-
Sentry.withScope(scope => {
39-
scope.setExtra('data', data);
40-
Sentry.captureException(new Error('Version missing in package data'));
41-
});
42-
throw new Error('Version missing in package data');
43-
}
44-
45-
return data;
46-
};
18+
export const libraries = () =>
19+
fetchJson(`${kvBase}/packages`)
20+
.then(librariesSchema.parse);
4721

4822
/**
4923
* Get the metadata for a library.
5024
*
51-
* @param {string} name Name of the library to fetch.
52-
* @return {Promise<Object>}
25+
* @param name Name of the library to fetch.
5326
*/
54-
export const library = name => fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}`)
55-
.then(data => kvLibraryValidate(name, data));
27+
export const library = (name: string) =>
28+
fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}`)
29+
.then(res => {
30+
console.log(res);
31+
return res;
32+
})
33+
.then(librarySchema.parse)
34+
.then(res => {
35+
console.log(res);
36+
return res;
37+
});
5638

5739
/**
5840
* Get the versions for a library.
5941
*
60-
* @param {string} name Name of the library to fetch.
61-
* @return {Promise<string[]>}
42+
* @param name Name of the library to fetch.
6243
*/
63-
export const libraryVersions = name => fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/versions`).then(sortVersions);
44+
export const libraryVersions = (name: string) =>
45+
fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/versions`)
46+
.then(libraryVersionsSchema.parse)
47+
.then(sortVersions);
6448

6549
/**
6650
* Get the assets for a library version.
6751
*
68-
* @param {string} name Name of the library to fetch.
69-
* @param {string} version Version of the library to fetch.
70-
* @return {Promise<string[]>}
52+
* @param name Name of the library to fetch.
53+
* @param version Version of the library to fetch.
7154
*/
72-
export const libraryVersion = (name, version) => fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/versions/${encodeURIComponent(version)}`);
55+
export const libraryVersion = (name: string, version: string) =>
56+
fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/versions/${encodeURIComponent(version)}`)
57+
.then(libraryVersionSchema.parse);
7358

7459
/**
7560
* Get the SRI data for a library's version.
7661
*
77-
* @param {string} name Name of the library to fetch.
78-
* @param {string} version Version of the library to fetch.
79-
* @return {Promise<Object<string, string>>}
62+
* @param name Name of the library to fetch.
63+
* @param version Version of the library to fetch.
8064
*/
81-
export const libraryVersionSri = (name, version) => fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/sris/${encodeURIComponent(version)}`);
65+
export const libraryVersionSri = (name: string, version: string) =>
66+
fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/sris/${encodeURIComponent(version)}`)
67+
.then(libraryVersionSriSchema.parse);

0 commit comments

Comments
 (0)