From bf57547e55892eb7a1752ebd8b56e2cb89ec0db4 Mon Sep 17 00:00:00 2001
From: Sergei Zharinov <zharinov@users.noreply.github.com>
Date: Thu, 15 Jun 2023 12:27:03 +0300
Subject: [PATCH] refactor(rubygems): Remove unnecessary datasource class
 (#22785)

---
 lib/modules/datasource/rubygems/get.ts    | 208 ----------------------
 lib/modules/datasource/rubygems/index.ts  | 124 +++++++++++--
 lib/modules/datasource/rubygems/schema.ts |  82 +++++++++
 3 files changed, 194 insertions(+), 220 deletions(-)
 delete mode 100644 lib/modules/datasource/rubygems/get.ts
 create mode 100644 lib/modules/datasource/rubygems/schema.ts

diff --git a/lib/modules/datasource/rubygems/get.ts b/lib/modules/datasource/rubygems/get.ts
deleted file mode 100644
index 87e658b86f..0000000000
--- a/lib/modules/datasource/rubygems/get.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import { Marshal } from '@qnighy/marshal';
-import is from '@sindresorhus/is';
-import { z } from 'zod';
-import { logger } from '../../../logger';
-import { HttpError } from '../../../util/http';
-import { LooseArray } from '../../../util/schema-utils';
-import { getQueryString, joinUrlParts, parseUrl } from '../../../util/url';
-import { Datasource } from '../datasource';
-import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
-
-const MarshalledVersionInfo = LooseArray(
-  z
-    .object({
-      number: z.string(),
-    })
-    .transform(({ number: version }) => ({ version }))
-)
-  .transform((releases) => (releases.length === 0 ? null : { releases }))
-  .nullable()
-  .catch(null);
-
-const GemsInfo = z
-  .object({
-    name: z.string().transform((x) => x.toLowerCase()),
-    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),
-  })
-  .transform(
-    ({
-      name: packageName,
-      version,
-      changelog_uri: changelogUrl,
-      homepage_uri: homepage,
-      source_code_uri: sourceUrl,
-    }) => ({
-      packageName,
-      version,
-      changelogUrl,
-      homepage,
-      sourceUrl,
-    })
-  );
-type GemsInfo = z.infer<typeof GemsInfo>;
-
-const GemVersions = LooseArray(
-  z
-    .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),
-    })
-    .transform(
-      ({
-        number: version,
-        created_at: releaseTimestamp,
-        platform,
-        ruby_version: rubyVersion,
-        rubygems_version: rubygemsVersion,
-      }): Release => {
-        const result: Release = { version, releaseTimestamp };
-        const constraints: Record<string, string[]> = {};
-
-        if (platform) {
-          constraints.platform = [platform];
-        }
-
-        if (rubyVersion) {
-          constraints.ruby = [rubyVersion];
-        }
-
-        if (rubygemsVersion) {
-          constraints.rubygems = [rubygemsVersion];
-        }
-
-        if (!is.emptyObject(constraints)) {
-          result.constraints = constraints;
-        }
-
-        return result;
-      }
-    )
-);
-type GemVersions = z.infer<typeof GemVersions>;
-
-export class InternalRubyGemsDatasource extends Datasource {
-  constructor(override readonly id: string) {
-    super(id);
-  }
-
-  async getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {
-    const registryUrl = config.registryUrl;
-    // istanbul ignore if
-    if (!registryUrl) {
-      return null;
-    }
-
-    const packageName = config.packageName.toLowerCase();
-
-    const hostname = parseUrl(registryUrl)?.hostname;
-    return hostname === 'rubygems.pkg.github.com' || hostname === 'gitlab.com'
-      ? await this.getDependencyFallback(registryUrl, packageName)
-      : await this.getDependency(registryUrl, packageName);
-  }
-
-  async getDependencyFallback(
-    registryUrl: string,
-    packageName: string
-  ): Promise<ReleaseResult | null> {
-    const path = joinUrlParts(registryUrl, `/api/v1/dependencies`);
-    const query = getQueryString({ gems: packageName });
-    const url = `${path}?${query}`;
-    const { body: buffer } = await this.http.getBuffer(url);
-    const data = Marshal.parse(buffer);
-    return MarshalledVersionInfo.parse(data);
-  }
-
-  async fetchGemsInfo(
-    registryUrl: string,
-    packageName: string
-  ): Promise<GemsInfo | null> {
-    try {
-      const { body } = await this.http.getJson(
-        joinUrlParts(registryUrl, '/api/v1/gems', `${packageName}.json`),
-        GemsInfo
-      );
-      return body;
-    } catch (err) {
-      // fallback to deps api on 404
-      if (err instanceof HttpError && err.response?.statusCode === 404) {
-        return null;
-      }
-      throw err;
-    }
-  }
-
-  async fetchGemVersions(
-    registryUrl: string,
-    packageName: string
-  ): Promise<GemVersions | null> {
-    try {
-      const { body } = await this.http.getJson(
-        joinUrlParts(registryUrl, '/api/v1/versions', `${packageName}.json`),
-        GemVersions
-      );
-      return body;
-    } catch (err) {
-      if (err.statusCode === 400 || err.statusCode === 404) {
-        logger.debug(
-          { registry: registryUrl },
-          'versions endpoint returns error - falling back to info endpoint'
-        );
-        return null;
-      } else {
-        throw err;
-      }
-    }
-  }
-
-  async getDependency(
-    registryUrl: string,
-    packageName: string
-  ): Promise<ReleaseResult | null> {
-    const info = await this.fetchGemsInfo(registryUrl, packageName);
-    if (!info) {
-      return await this.getDependencyFallback(registryUrl, packageName);
-    }
-
-    if (info.packageName !== packageName) {
-      logger.warn(
-        { lookup: packageName, returned: info.packageName },
-        'Lookup name does not match the returned name.'
-      );
-      return null;
-    }
-
-    let releases: Release[] | null = null;
-    const gemVersions = await this.fetchGemVersions(registryUrl, packageName);
-    if (gemVersions?.length) {
-      releases = gemVersions;
-    } else if (info.version) {
-      releases = [{ version: info.version }];
-    }
-
-    if (!releases) {
-      return null;
-    }
-
-    const result: ReleaseResult = { releases };
-
-    if (info.changelogUrl) {
-      result.changelogUrl = info.changelogUrl;
-    }
-
-    if (info.homepage) {
-      result.homepage = info.homepage;
-    }
-
-    if (info.sourceUrl) {
-      result.sourceUrl = info.sourceUrl;
-    }
-
-    return result;
-  }
-}
diff --git a/lib/modules/datasource/rubygems/index.ts b/lib/modules/datasource/rubygems/index.ts
index 3d141d8678..1c58d762d6 100644
--- a/lib/modules/datasource/rubygems/index.ts
+++ b/lib/modules/datasource/rubygems/index.ts
@@ -1,10 +1,13 @@
+import { Marshal } from '@qnighy/marshal';
 import { PAGE_NOT_FOUND_ERROR } from '../../../constants/error-messages';
+import { logger } from '../../../logger';
 import { cache } from '../../../util/cache/package/decorator';
-import { parseUrl } from '../../../util/url';
+import { HttpError } from '../../../util/http';
+import { getQueryString, joinUrlParts, parseUrl } from '../../../util/url';
 import * as rubyVersioning from '../../versioning/ruby';
 import { Datasource } from '../datasource';
-import type { GetReleasesConfig, ReleaseResult } from '../types';
-import { InternalRubyGemsDatasource } from './get';
+import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
+import { GemVersions, GemsInfo, MarshalledVersionInfo } from './schema';
 import { VersionsDatasource } from './versions-datasource';
 
 export class RubyGemsDatasource extends Datasource {
@@ -13,9 +16,6 @@ export class RubyGemsDatasource extends Datasource {
   constructor() {
     super(RubyGemsDatasource.id);
     this.versionsDatasource = new VersionsDatasource(RubyGemsDatasource.id);
-    this.internalRubyGemsDatasource = new InternalRubyGemsDatasource(
-      RubyGemsDatasource.id
-    );
   }
 
   override readonly defaultRegistryUrls = ['https://rubygems.org'];
@@ -26,8 +26,6 @@ export class RubyGemsDatasource extends Datasource {
 
   private readonly versionsDatasource: VersionsDatasource;
 
-  private readonly internalRubyGemsDatasource: InternalRubyGemsDatasource;
-
   @cache({
     namespace: `datasource-${RubyGemsDatasource.id}`,
     key: ({ registryUrl, packageName }: GetReleasesConfig) =>
@@ -54,12 +52,114 @@ export class RubyGemsDatasource extends Datasource {
         error.message === PAGE_NOT_FOUND_ERROR &&
         parseUrl(registryUrl)?.hostname !== 'rubygems.org'
       ) {
-        return this.internalRubyGemsDatasource.getReleases({
-          packageName,
-          registryUrl,
-        });
+        const pkgName = packageName.toLowerCase();
+        const hostname = parseUrl(registryUrl)?.hostname;
+        return hostname === 'rubygems.pkg.github.com' ||
+          hostname === 'gitlab.com'
+          ? await this.getDependencyFallback(registryUrl, pkgName)
+          : await this.getDependency(registryUrl, pkgName);
       }
       throw error;
     }
   }
+
+  async getDependencyFallback(
+    registryUrl: string,
+    packageName: string
+  ): Promise<ReleaseResult | null> {
+    const path = joinUrlParts(registryUrl, `/api/v1/dependencies`);
+    const query = getQueryString({ gems: packageName });
+    const url = `${path}?${query}`;
+    const { body: buffer } = await this.http.getBuffer(url);
+    const data = Marshal.parse(buffer);
+    return MarshalledVersionInfo.parse(data);
+  }
+
+  async fetchGemsInfo(
+    registryUrl: string,
+    packageName: string
+  ): Promise<GemsInfo | null> {
+    try {
+      const { body } = await this.http.getJson(
+        joinUrlParts(registryUrl, '/api/v1/gems', `${packageName}.json`),
+        GemsInfo
+      );
+      return body;
+    } catch (err) {
+      // fallback to deps api on 404
+      if (err instanceof HttpError && err.response?.statusCode === 404) {
+        return null;
+      }
+      throw err;
+    }
+  }
+
+  async fetchGemVersions(
+    registryUrl: string,
+    packageName: string
+  ): Promise<GemVersions | null> {
+    try {
+      const { body } = await this.http.getJson(
+        joinUrlParts(registryUrl, '/api/v1/versions', `${packageName}.json`),
+        GemVersions
+      );
+      return body;
+    } catch (err) {
+      if (err.statusCode === 400 || err.statusCode === 404) {
+        logger.debug(
+          { registry: registryUrl },
+          'versions endpoint returns error - falling back to info endpoint'
+        );
+        return null;
+      } else {
+        throw err;
+      }
+    }
+  }
+
+  async getDependency(
+    registryUrl: string,
+    packageName: string
+  ): Promise<ReleaseResult | null> {
+    const info = await this.fetchGemsInfo(registryUrl, packageName);
+    if (!info) {
+      return await this.getDependencyFallback(registryUrl, packageName);
+    }
+
+    if (info.packageName !== packageName) {
+      logger.warn(
+        { lookup: packageName, returned: info.packageName },
+        'Lookup name does not match the returned name.'
+      );
+      return null;
+    }
+
+    let releases: Release[] | null = null;
+    const gemVersions = await this.fetchGemVersions(registryUrl, packageName);
+    if (gemVersions?.length) {
+      releases = gemVersions;
+    } else if (info.version) {
+      releases = [{ version: info.version }];
+    }
+
+    if (!releases) {
+      return null;
+    }
+
+    const result: ReleaseResult = { releases };
+
+    if (info.changelogUrl) {
+      result.changelogUrl = info.changelogUrl;
+    }
+
+    if (info.homepage) {
+      result.homepage = info.homepage;
+    }
+
+    if (info.sourceUrl) {
+      result.sourceUrl = info.sourceUrl;
+    }
+
+    return result;
+  }
 }
diff --git a/lib/modules/datasource/rubygems/schema.ts b/lib/modules/datasource/rubygems/schema.ts
new file mode 100644
index 0000000000..3931a40c0c
--- /dev/null
+++ b/lib/modules/datasource/rubygems/schema.ts
@@ -0,0 +1,82 @@
+import is from '@sindresorhus/is';
+import { z } from 'zod';
+import { LooseArray } from '../../../util/schema-utils';
+import type { Release } from '../types';
+
+export const MarshalledVersionInfo = LooseArray(
+  z
+    .object({
+      number: z.string(),
+    })
+    .transform(({ number: version }) => ({ version }))
+)
+  .transform((releases) => (releases.length === 0 ? null : { releases }))
+  .nullable()
+  .catch(null);
+
+export const GemsInfo = z
+  .object({
+    name: z.string().transform((x) => x.toLowerCase()),
+    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),
+  })
+  .transform(
+    ({
+      name: packageName,
+      version,
+      changelog_uri: changelogUrl,
+      homepage_uri: homepage,
+      source_code_uri: sourceUrl,
+    }) => ({
+      packageName,
+      version,
+      changelogUrl,
+      homepage,
+      sourceUrl,
+    })
+  );
+export type GemsInfo = z.infer<typeof GemsInfo>;
+
+export const GemVersions = LooseArray(
+  z
+    .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),
+    })
+    .transform(
+      ({
+        number: version,
+        created_at: releaseTimestamp,
+        platform,
+        ruby_version: rubyVersion,
+        rubygems_version: rubygemsVersion,
+      }): Release => {
+        const result: Release = { version, releaseTimestamp };
+        const constraints: Record<string, string[]> = {};
+
+        if (platform) {
+          constraints.platform = [platform];
+        }
+
+        if (rubyVersion) {
+          constraints.ruby = [rubyVersion];
+        }
+
+        if (rubygemsVersion) {
+          constraints.rubygems = [rubygemsVersion];
+        }
+
+        if (!is.emptyObject(constraints)) {
+          result.constraints = constraints;
+        }
+
+        return result;
+      }
+    )
+);
+export type GemVersions = z.infer<typeof GemVersions>;
-- 
GitLab