From bbf4183b04aa123f242ad15757b16de2dc722431 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Sat, 29 Jul 2023 20:54:49 +0300 Subject: [PATCH] refactor(cdnjs): Use zod schema and `Result` class (#23588) --- .../cdnjs/__snapshots__/index.spec.ts.snap | 1 - lib/modules/datasource/cdnjs/index.ts | 125 ++++++++++++------ lib/modules/datasource/cdnjs/types.ts | 14 -- lib/modules/datasource/schema.ts | 6 + 4 files changed, 92 insertions(+), 54 deletions(-) delete mode 100644 lib/modules/datasource/cdnjs/types.ts create mode 100644 lib/modules/datasource/schema.ts diff --git a/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap index e1aead3be8d6fd..c779cc09bbdd79 100644 --- a/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap @@ -6,7 +6,6 @@ exports[`modules/datasource/cdnjs/index getReleases filters releases by asset pr "registryUrl": "https://api.cdnjs.com/", "releases": [ { - "newDigest": undefined, "version": "0.7.5", }, ], diff --git a/lib/modules/datasource/cdnjs/index.ts b/lib/modules/datasource/cdnjs/index.ts index 5988d1b12d395b..95f4952b615760 100644 --- a/lib/modules/datasource/cdnjs/index.ts +++ b/lib/modules/datasource/cdnjs/index.ts @@ -1,7 +1,30 @@ +import { ZodError, z } from 'zod'; +import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; +import type { HttpError } from '../../../util/http'; +import { Result } from '../../../util/result'; import { Datasource } from '../datasource'; -import type { GetReleasesConfig, ReleaseResult } from '../types'; -import type { CdnjsResponse } from './types'; +import { ReleasesConfig } from '../schema'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; + +const Homepage = z.string().optional().catch(undefined); + +const Repository = z + .object({ + type: z.literal('git'), + url: z.string(), + }) + .transform(({ url }) => url) + .optional() + .catch(undefined); + +const Assets = z.array( + z.object({ + version: z.string(), + files: z.string().array(), + sri: z.record(z.string()).optional(), + }) +); export class CdnJsDatasource extends Datasource { static readonly id = 'cdnjs'; @@ -16,44 +39,68 @@ export class CdnJsDatasource extends Datasource { override readonly caching = true; - // this.handleErrors will always throw - - async getReleases({ - packageName, - registryUrl, - }: GetReleasesConfig): Promise { - // Each library contains multiple assets, so we cache at the library level instead of per-asset - const library = packageName.split('/')[0]; - // TODO: types (#7154) - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - const url = `${registryUrl}libraries/${library}?fields=homepage,repository,assets`; - let result: ReleaseResult | null = null; - try { - const { assets, homepage, repository } = ( - await this.http.getJson(url) - ).body; - if (!assets) { - return null; - } - const assetName = packageName.replace(`${library}/`, ''); - const releases = assets - .filter(({ files }) => files.includes(assetName)) - .map(({ version, sri }) => ({ version, newDigest: sri?.[assetName] })); - - result = { releases }; - - if (homepage) { - result.homepage = homepage; - } - if (repository?.url) { - result.sourceUrl = repository.url; - } - } catch (err) { - if (err.statusCode !== 404) { - throw new ExternalHostError(err); - } + async getReleases(config: GetReleasesConfig): Promise { + const result = Result.wrap(ReleasesConfig.safeParse(config)) + .transform(({ packageName, registryUrl }) => { + const [library] = packageName.split('/'); + const assetName = packageName.replace(`${library}/`, ''); + + const url = `${registryUrl}libraries/${library}?fields=homepage,repository,assets`; + + const schema = z.object({ + homepage: Homepage, + repository: Repository, + assets: Assets.transform((assets) => + assets + .filter(({ files }) => files.includes(assetName)) + .map(({ version, sri }) => { + const res: Release = { version }; + + const newDigest = sri?.[assetName]; + if (newDigest) { + res.newDigest = newDigest; + } + + return res; + }) + ), + }); + + return this.http.getJsonSafe(url, schema); + }) + .transform(({ assets, homepage, repository }): ReleaseResult => { + const releases: Release[] = assets; + + const res: ReleaseResult = { releases }; + + if (homepage) { + res.homepage = homepage; + } + + if (repository) { + res.sourceUrl = repository; + } + + return res; + }); + + const { val, err } = await result.unwrap(); + + if (err instanceof ZodError) { + logger.debug({ err }, 'cdnjs: validation error'); + return null; + } + + if (err) { this.handleGenericErrors(err); } - return result; + + return val; + } + + override handleHttpErrors(err: HttpError): void { + if (err.response?.statusCode !== 404) { + throw new ExternalHostError(err); + } } } diff --git a/lib/modules/datasource/cdnjs/types.ts b/lib/modules/datasource/cdnjs/types.ts deleted file mode 100644 index 97ff84563dd371..00000000000000 --- a/lib/modules/datasource/cdnjs/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -interface CdnjsAsset { - version: string; - files: string[]; - sri?: Record; -} - -export interface CdnjsResponse { - homepage?: string; - repository?: { - type: 'git' | unknown; - url?: string; - }; - assets?: CdnjsAsset[]; -} diff --git a/lib/modules/datasource/schema.ts b/lib/modules/datasource/schema.ts new file mode 100644 index 00000000000000..3f31fbaa964ee7 --- /dev/null +++ b/lib/modules/datasource/schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const ReleasesConfig = z.object({ + packageName: z.string(), + registryUrl: z.string(), +});