Skip to content

Commit

Permalink
chore(snippet-manager): migrate from joi to zod MONGOSH-2010 (#2360)
Browse files Browse the repository at this point in the history
This aligns us with the validators we've standardized on in Compass,
improves TypeScript integration, and reduces executable size by 564 kB
and startup time by 1.5% (locally on an M3).
  • Loading branch information
addaleax authored Feb 7, 2025
1 parent 6d7e2e8 commit e9cdf0c
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 97 deletions.
50 changes: 11 additions & 39 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/snippet-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"bson": "^6.10.1",
"cross-spawn": "^7.0.5",
"escape-string-regexp": "^4.0.0",
"joi": "^17.4.0",
"zod": "^3.24.1",
"tar": "^6.1.15"
},
"devDependencies": {
Expand Down
5 changes: 3 additions & 2 deletions packages/snippet-manager/src/snippet-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,10 @@ describe('SnippetManager', function () {
await snippetManager.runSnippetCommand(['refresh']);
expect.fail('missed exception');
} catch (err: any) {
expect(err.message).to.equal(
`The specified index file ${indexURL} is not a valid index file: "indexFileVersion" must be less than or equal to 1`
expect(err.message).to.include(
`The specified index file ${indexURL} is not a valid index file:`
);
expect(err.message).to.include(`Number must be less than or equal to 1`);
}
});

Expand Down
92 changes: 37 additions & 55 deletions packages/snippet-manager/src/snippet-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { once } from 'events';
import tar from 'tar';
import zlib from 'zlib';
import bson from 'bson';
import joi from 'joi';
import { z } from 'zod';
import type {
AgentWithInitialize,
DevtoolsProxyOptions,
Expand All @@ -34,63 +34,46 @@ export interface SnippetOptions {
proxyOptions?: DevtoolsProxyOptions | AgentWithInitialize;
}

export interface ErrorMatcher {
matches: RegExp[];
message: string;
}

export interface SnippetDescription {
name: string;
snippetName: string;
installSpec?: string;
version: string;
description: string;
license: string;
readme: string;
errorMatchers?: ErrorMatcher[];
}

export interface SnippetIndexFile {
indexFileVersion: 1;
index: SnippetDescription[];
metadata: { homepage: string };
sourceURL: string;
}

interface NpmMetaDataResponse {
dist?: {
tarball?: string;
};
}

const indexFileSchema = joi.object({
indexFileVersion: joi.number().integer().max(1).required(),

metadata: joi.object({
homepage: joi.string(),
}),

index: joi
.array()
.required()
.items(
joi.object({
name: joi.string().required(),
snippetName: joi.string().required(),
installSpec: joi.string(),
version: joi.string().required(),
description: joi.string().required().allow(''),
license: joi.string().required(),
readme: joi.string().required().allow(''),
errorMatchers: joi.array().items(
joi.object({
message: joi.string().required(),
matches: joi.array().required().items(joi.object().regex()),
})
),
})
),
const regExpTag = Object.prototype.toString.call(/foo/);
const errorMatcherSchema = z.object({
message: z.string(),
matches: z.array(
z.custom<RegExp>((val) => Object.prototype.toString.call(val) === regExpTag)
),
});
const indexDescriptionSchema = z.object({
name: z.string(),
snippetName: z.string(),
installSpec: z.string().optional(),
version: z.string(),
description: z.string(),
license: z.string(),
readme: z.string(),
errorMatchers: z.array(errorMatcherSchema).optional(),
});
const indexFileSchema = z.object({
indexFileVersion: z.number().int().max(1),

metadata: z
.object({
homepage: z.string(),
})
.passthrough(),

index: z.array(indexDescriptionSchema),
});

export type ErrorMatcher = z.infer<typeof errorMatcherSchema>;
export type SnippetIndexFile = z.infer<typeof indexFileSchema> & {
sourceURL: string;
};
export type SnippetDescription = z.infer<typeof indexDescriptionSchema>;

async function unpackBSON<T = any>(data: Buffer): Promise<T> {
return bson.deserialize(await brotliDecompress(data)) as T;
Expand Down Expand Up @@ -361,9 +344,8 @@ export class SnippetManager implements ShellPlugin {
`The specified index file ${url} could not be parsed: ${err.message}`
);
}
const { error } = indexFileSchema.validate(data, {
allowUnknown: true,
});
const { error, data: parsedData } =
indexFileSchema.safeParse(data);
if (error) {
this.messageBus.emit('mongosh-snippets:fetch-index-error', {
action: 'validate-fetched',
Expand All @@ -374,7 +356,7 @@ export class SnippetManager implements ShellPlugin {
`The specified index file ${url} is not a valid index file: ${error.message}`
);
}
return { ...data, sourceURL: url };
return { ...parsedData, sourceURL: url };
})
);
// If possible, write the result to disk for caching.
Expand Down

0 comments on commit e9cdf0c

Please sign in to comment.