From 0e2083ee02217a58e3a07feb46876b5971c1991f Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Sun, 23 Jul 2023 13:27:14 +0300 Subject: [PATCH] refactor(rubygems): Extract v1 API handling (#23474) --- lib/modules/datasource/rubygems/common.ts | 30 +++++++++ .../datasource/rubygems/metadata-cache.ts | 63 ++++++++----------- lib/modules/datasource/rubygems/schema.ts | 14 ++--- 3 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 lib/modules/datasource/rubygems/common.ts diff --git a/lib/modules/datasource/rubygems/common.ts b/lib/modules/datasource/rubygems/common.ts new file mode 100644 index 00000000000000..97a7e78ed9233d --- /dev/null +++ b/lib/modules/datasource/rubygems/common.ts @@ -0,0 +1,30 @@ +import { assignKeys } from '../../../util/assign-keys'; +import type { Http, SafeJsonError } from '../../../util/http'; +import type { AsyncResult } from '../../../util/result'; +import { joinUrlParts as join } from '../../../util/url'; +import type { Release, ReleaseResult } from '../types'; +import { GemMetadata, GemVersions } from './schema'; + +export function getV1Releases( + http: Http, + registryUrl: string, + packageName: string +): AsyncResult { + const fileName = `${packageName}.json`; + const versionsUrl = join(registryUrl, '/api/v1/versions', fileName); + const metadataUrl = join(registryUrl, '/api/v1/gems', fileName); + + const addMetadata = (releases: Release[]): Promise => + http + .getJsonSafe(metadataUrl, GemMetadata) + .transform((metadata) => + assignKeys({ releases } as ReleaseResult, metadata, [ + 'changelogUrl', + 'sourceUrl', + 'homepage', + ]) + ) + .unwrap({ releases }); + + return http.getJsonSafe(versionsUrl, GemVersions).transform(addMetadata); +} diff --git a/lib/modules/datasource/rubygems/metadata-cache.ts b/lib/modules/datasource/rubygems/metadata-cache.ts index 53ca592f869dd9..74a553cf172874 100644 --- a/lib/modules/datasource/rubygems/metadata-cache.ts +++ b/lib/modules/datasource/rubygems/metadata-cache.ts @@ -1,10 +1,10 @@ import hasha from 'hasha'; -import { logger } from '../../../logger'; import * as packageCache from '../../../util/cache/package'; import type { Http } from '../../../util/http'; -import { joinUrlParts, parseUrl } from '../../../util/url'; +import { AsyncResult, Result } from '../../../util/result'; +import { parseUrl } from '../../../util/url'; import type { ReleaseResult } from '../types'; -import { GemMetadata, GemVersions } from './schema'; +import { getV1Releases } from './common'; interface CacheRecord { hash: string; @@ -19,39 +19,21 @@ export class MetadataCache { packageName: string, versions: string[] ): Promise { - const hash = hasha(versions, { algorithm: 'sha256' }); const cacheNs = `datasource-rubygems`; const cacheKey = `metadata-cache:${registryUrl}:${packageName}`; - const oldCache = await packageCache.get(cacheNs, cacheKey); - if (oldCache?.hash === hash) { - return oldCache.data; - } - - try { - const { body: releases } = await this.http.getJson( - joinUrlParts(registryUrl, '/api/v1/versions', `${packageName}.json`), - GemVersions - ); - - const { body: metadata } = await this.http.getJson( - joinUrlParts(registryUrl, '/api/v1/gems', `${packageName}.json`), - GemMetadata - ); - - const data: ReleaseResult = { releases }; - - if (metadata.changelogUrl) { - data.changelogUrl = metadata.changelogUrl; - } - - if (metadata.sourceUrl) { - data.sourceUrl = metadata.sourceUrl; - } - - if (metadata.homepage) { - data.homepage = metadata.homepage; - } + const hash = hasha(versions, { algorithm: 'sha256' }); + const loadCache = (): AsyncResult => + Result.wrapNullable( + packageCache.get(cacheNs, cacheKey), + 'cache-not-found' + ).transform((cache) => { + return hash === cache.hash + ? Result.ok(cache.data) + : Result.err('cache-outdated'); + }); + + const saveCache = async (data: ReleaseResult): Promise => { const registryHostname = parseUrl(registryUrl)?.hostname; if (registryHostname === 'rubygems.org') { const newCache: CacheRecord = { hash, data }; @@ -66,10 +48,15 @@ export class MetadataCache { } return data; - } catch (err) { - logger.debug({ err }, 'Rubygems: failed to fetch metadata'); - const releases = versions.map((version) => ({ version })); - return { releases }; - } + }; + + return await loadCache() + .catch(() => + getV1Releases(this.http, registryUrl, packageName).transform(saveCache) + ) + .catch(() => + Result.ok({ releases: versions.map((version) => ({ version })) }) + ) + .unwrapOrThrow(); } } diff --git a/lib/modules/datasource/rubygems/schema.ts b/lib/modules/datasource/rubygems/schema.ts index 2790b3edcf1c88..760cf99f97153b 100644 --- a/lib/modules/datasource/rubygems/schema.ts +++ b/lib/modules/datasource/rubygems/schema.ts @@ -17,10 +17,10 @@ export const MarshalledVersionInfo = LooseArray( export const GemMetadata = z .object({ name: z.string(), - version: z.string().nullish().catch(null), - changelog_uri: z.string().nullish().catch(null), - homepage_uri: z.string().nullish().catch(null), - source_code_uri: z.string().nullish().catch(null), + version: z.string().optional().catch(undefined), + changelog_uri: z.string().optional().catch(undefined), + homepage_uri: z.string().optional().catch(undefined), + source_code_uri: z.string().optional().catch(undefined), }) .transform( ({ @@ -44,9 +44,9 @@ export const GemVersions = LooseArray( .object({ number: z.string(), created_at: z.string(), - platform: z.string().nullable().catch(null), - ruby_version: z.string().nullable().catch(null), - rubygems_version: z.string().nullable().catch(null), + platform: z.string().optional().catch(undefined), + ruby_version: z.string().optional().catch(undefined), + rubygems_version: z.string().optional().catch(undefined), metadata: z .object({ changelog_uri: z.string().optional().catch(undefined),