From 6679d7ed9b67c46b96eedd72165564b5a36ac593 Mon Sep 17 00:00:00 2001
From: Sergei Zharinov <zharinov@users.noreply.github.com>
Date: Tue, 17 Jan 2023 07:47:02 +0300
Subject: [PATCH] refactor(packagist): Refactor schema (#19868)

---
 .../datasource/packagist/schema.spec.ts       | 67 ++++++++--------
 lib/modules/datasource/packagist/schema.ts    | 79 ++++++++++---------
 2 files changed, 71 insertions(+), 75 deletions(-)

diff --git a/lib/modules/datasource/packagist/schema.spec.ts b/lib/modules/datasource/packagist/schema.spec.ts
index 679d5c1471..2bae8ee23f 100644
--- a/lib/modules/datasource/packagist/schema.spec.ts
+++ b/lib/modules/datasource/packagist/schema.spec.ts
@@ -1,6 +1,7 @@
+import type { ReleaseResult } from '../types';
 import {
   ComposerRelease,
-  ComposerReleaseArray,
+  ComposerReleases,
   parsePackagesResponse,
   parsePackagesResponses,
 } from './schema';
@@ -14,60 +15,56 @@ describe('modules/datasource/packagist/schema', () => {
       expect(() => ComposerRelease.parse({})).toThrow();
       expect(() => ComposerRelease.parse({ version: null })).toThrow();
       expect(() => ComposerRelease.parse({ version: null })).toThrow();
-      expect(() => ComposerRelease.parse({ version: '' })).toThrow();
-      expect(() => ComposerRelease.parse({ version: 'dev-main' })).toThrow();
     });
 
     it('parses', () => {
-      const defaultResult = {
-        version: '1.2.3',
-        homepage: null,
-        source: null,
-        time: null,
-      };
+      expect(ComposerRelease.parse({ version: '' })).toEqual({ version: '' });
+      expect(ComposerRelease.parse({ version: 'dev-main' })).toEqual({
+        version: 'dev-main',
+      });
 
-      expect(ComposerRelease.parse({ version: '1.2.3' })).toEqual(
-        defaultResult
-      );
+      expect(ComposerRelease.parse({ version: '1.2.3' })).toEqual({
+        version: '1.2.3',
+      });
 
       expect(ComposerRelease.parse({ version: '1.2.3', homepage: 42 })).toEqual(
-        defaultResult
+        { version: '1.2.3', homepage: null }
       );
 
       expect(
         ComposerRelease.parse({ version: '1.2.3', homepage: 'example.com' })
-      ).toEqual({ ...defaultResult, homepage: 'example.com' });
+      ).toEqual({ version: '1.2.3', homepage: 'example.com' });
 
       expect(
         ComposerRelease.parse({ version: '1.2.3', source: 'nonsense' })
-      ).toEqual(defaultResult);
+      ).toEqual({ version: '1.2.3', source: null });
 
       expect(
         ComposerRelease.parse({ version: '1.2.3', source: { url: 'foobar' } })
-      ).toEqual({ ...defaultResult, source: 'foobar' });
+      ).toEqual({ version: '1.2.3', source: { url: 'foobar' } });
 
       expect(
         ComposerRelease.parse({ version: '1.2.3', time: '12345' })
-      ).toEqual({ ...defaultResult, time: '12345' });
+      ).toEqual({ version: '1.2.3', time: '12345' });
     });
   });
 
-  describe('ComposerReleaseArray', () => {
+  describe('ComposerReleases', () => {
     it('rejects', () => {
-      expect(() => ComposerReleaseArray.parse(null)).toThrow();
-      expect(() => ComposerReleaseArray.parse(undefined)).toThrow();
-      expect(() => ComposerReleaseArray.parse('')).toThrow();
-      expect(() => ComposerReleaseArray.parse({})).toThrow();
+      expect(() => ComposerReleases.parse(null)).toThrow();
+      expect(() => ComposerReleases.parse(undefined)).toThrow();
+      expect(() => ComposerReleases.parse('')).toThrow();
+      expect(() => ComposerReleases.parse({})).toThrow();
     });
 
     it('parses', () => {
-      expect(ComposerReleaseArray.parse([])).toEqual([]);
-      expect(ComposerReleaseArray.parse([null])).toEqual([]);
-      expect(ComposerReleaseArray.parse([1, 2, 3])).toEqual([]);
-      expect(ComposerReleaseArray.parse(['foobar'])).toEqual([]);
-      expect(ComposerReleaseArray.parse([{ version: '1.2.3' }])).toEqual([
-        { version: '1.2.3', homepage: null, source: null, time: null },
-      ]);
+      expect(ComposerReleases.parse([])).toEqual([]);
+      expect(ComposerReleases.parse([null])).toEqual([]);
+      expect(ComposerReleases.parse([1, 2, 3])).toEqual([]);
+      expect(ComposerReleases.parse(['foobar'])).toEqual([]);
+      expect(
+        ComposerReleases.parse([{ version: '1.2.3' }, { version: 'dev-main' }])
+      ).toEqual([{ version: '1.2.3' }, { version: 'dev-main' }]);
     });
   });
 
@@ -84,9 +81,7 @@ describe('modules/datasource/packagist/schema', () => {
             'baz/qux': [{ version: '4.5.6' }],
           },
         })
-      ).toEqual([
-        { version: '1.2.3', homepage: null, source: null, time: null },
-      ]);
+      ).toEqual([{ version: '1.2.3' }]);
     });
   });
 
@@ -140,15 +135,15 @@ describe('modules/datasource/packagist/schema', () => {
               ],
             },
           },
-        ])
+        ] satisfies { packages: Record<string, ComposerRelease[]> }[])
       ).toEqual({
-        homepage: 'https://example.com/3',
-        sourceUrl: 'git@example.com:foo/bar-3',
+        homepage: 'https://example.com/1',
+        sourceUrl: 'git@example.com:foo/bar-1',
         releases: [
           { version: '1.1.1', gitRef: 'v1.1.1', releaseTimestamp: '111' },
           { version: '3.3.3', gitRef: 'v3.3.3', releaseTimestamp: '333' },
         ],
-      });
+      } satisfies ReleaseResult);
     });
   });
 });
diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts
index afaacc61d4..881b001257 100644
--- a/lib/modules/datasource/packagist/schema.ts
+++ b/lib/modules/datasource/packagist/schema.ts
@@ -1,28 +1,31 @@
 import { z } from 'zod';
-import { api as versioning } from '../../versioning/composer';
+import { logger } from '../../../logger';
 import type { Release, ReleaseResult } from '../types';
 
-export const ComposerRelease = z.object({
-  version: z
-    .string()
-    .refine((v) => versioning.isSingleVersion(v), 'Invalid version'),
-  homepage: z.string().nullable().catch(null),
-  source: z
-    .object({
-      url: z.string(),
-    })
-    .transform((x) => x.url)
-    .nullable()
-    .catch(null),
-  time: z.string().nullable().catch(null),
-});
+export const ComposerRelease = z
+  .object({
+    version: z.string(),
+  })
+  .merge(
+    z
+      .object({
+        homepage: z.string().nullable().catch(null),
+        source: z
+          .object({
+            url: z.string(),
+          })
+          .nullable()
+          .catch(null),
+        time: z.string().nullable().catch(null),
+      })
+      .partial()
+  );
+export type ComposerRelease = z.infer<typeof ComposerRelease>;
 
-export const ComposerReleaseArray = z
+export const ComposerReleases = z
   .array(ComposerRelease.nullable().catch(null))
-  .transform((xs) =>
-    xs.filter((x): x is z.infer<typeof ComposerRelease> => x !== null)
-  );
-export type ComposerReleaseArray = z.infer<typeof ComposerReleaseArray>;
+  .transform((xs) => xs.filter((x): x is ComposerRelease => x !== null));
+export type ComposerReleases = z.infer<typeof ComposerReleases>;
 
 export const ComposerPackagesResponse = z.object({
   packages: z.record(z.unknown()),
@@ -31,21 +34,18 @@ export const ComposerPackagesResponse = z.object({
 export function parsePackagesResponse(
   packageName: string,
   packagesResponse: unknown
-): ComposerReleaseArray {
-  const packagesResponseParsed =
-    ComposerPackagesResponse.safeParse(packagesResponse);
-  if (!packagesResponseParsed.success) {
+): ComposerReleases {
+  try {
+    const { packages } = ComposerPackagesResponse.parse(packagesResponse);
+    const releases = ComposerReleases.parse(packages[packageName]);
+    return releases;
+  } catch (err) {
+    logger.debug(
+      { packageName, err },
+      `Error parsing packagist response for ${packageName}`
+    );
     return [];
   }
-
-  const { packages } = packagesResponseParsed.data;
-  const releaseArray = packages[packageName];
-  const releaseArrayParsed = ComposerReleaseArray.safeParse(releaseArray);
-  if (!releaseArrayParsed.success) {
-    return [];
-  }
-
-  return releaseArrayParsed.data;
 }
 
 export function parsePackagesResponses(
@@ -53,9 +53,8 @@ export function parsePackagesResponses(
   packagesResponses: unknown[]
 ): ReleaseResult | null {
   const releases: Release[] = [];
-  let maxVersion: string | null = null;
-  let homepage: string | null = null;
-  let sourceUrl: string | null = null;
+  let homepage: string | null | undefined;
+  let sourceUrl: string | null | undefined;
 
   for (const packagesResponse of packagesResponses) {
     const releaseArray = parsePackagesResponse(packageName, packagesResponse);
@@ -71,10 +70,12 @@ export function parsePackagesResponses(
 
       releases.push(dep);
 
-      if (!maxVersion || versioning.isGreaterThan(version, maxVersion)) {
-        maxVersion = version;
+      if (!homepage && composerRelease.homepage) {
         homepage = composerRelease.homepage;
-        sourceUrl = composerRelease.source;
+      }
+
+      if (!sourceUrl && composerRelease.source?.url) {
+        sourceUrl = composerRelease.source.url;
       }
     }
   }
-- 
GitLab