Skip to content

Commit

Permalink
refactor(packagist): Use catch-all schema for releases fields (#20401)
Browse files Browse the repository at this point in the history
  • Loading branch information
zharinov authored Feb 14, 2023
1 parent 4a9ba9e commit 84e50dc
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 96 deletions.
33 changes: 4 additions & 29 deletions lib/modules/datasource/packagist/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as composerVersioning from '../../versioning/composer';
import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import * as schema from './schema';
import { extractDepReleases } from './schema';
import type {
AllPackages,
PackageMeta,
Expand Down Expand Up @@ -118,30 +119,6 @@ export class PackagistDatasource extends Datasource {
return packagistFile;
}

/* istanbul ignore next */
private static extractDepReleases(
composerReleases: unknown
): ReleaseResult | null {
const parsedRecord =
schema.ComposerReleasesRecord.safeParse(composerReleases);
if (parsedRecord.success) {
return schema.extractReleaseResult(Object.values(parsedRecord.data));
}

const parsedArray =
schema.ComposerReleasesArray.safeParse(composerReleases);
if (parsedArray.success) {
logger.once.info('Packagist: extracting releases from array');
return schema.extractReleaseResult(parsedArray.data);
}

logger.once.info(
{ composerReleases },
'Packagist: unknown format to extract from'
);
return null;
}

@cache({
namespace: `datasource-${PackagistDatasource.id}`,
key: (regUrl: string) => regUrl,
Expand Down Expand Up @@ -179,7 +156,7 @@ export class PackagistDatasource extends Datasource {
tasks.push(async () => {
const res = await this.getPackagistFile(regUrl, file);
for (const [key, val] of Object.entries(res.packages ?? {})) {
includesPackages[key] = PackagistDatasource.extractDepReleases(val);
includesPackages[key] = extractDepReleases(val);
}
});
}
Expand Down Expand Up @@ -240,9 +217,7 @@ export class PackagistDatasource extends Datasource {
includesPackages,
} = allPackages;
if (packages?.[packageName]) {
const dep = PackagistDatasource.extractDepReleases(
packages[packageName]
);
const dep = extractDepReleases(packages[packageName]);
return dep;
}
if (includesPackages?.[packageName]) {
Expand All @@ -268,7 +243,7 @@ export class PackagistDatasource extends Datasource {
// TODO: fix types (#9610)
const versions = (await this.http.getJson<any>(pkgUrl, opts)).body
.packages[packageName];
const dep = PackagistDatasource.extractDepReleases(versions);
const dep = extractDepReleases(versions);
logger.trace({ dep }, 'dep');
return dep;
} catch (err) /* istanbul ignore next */ {
Expand Down
60 changes: 12 additions & 48 deletions lib/modules/datasource/packagist/schema.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { ReleaseResult } from '../types';
import {
ComposerRelease,
ComposerReleasesArray,
ComposerReleasesRecord,
ComposerReleases,
MinifiedArray,
parsePackagesResponse,
parsePackagesResponses,
Expand Down Expand Up @@ -111,57 +110,22 @@ describe('modules/datasource/packagist/schema', () => {
});
});

describe('ComposerReleasesArray', () => {
it('rejects ComposerReleasesArray', () => {
expect(() => ComposerReleasesArray.parse(null)).toThrow();
expect(() => ComposerReleasesArray.parse(undefined)).toThrow();
expect(() => ComposerReleasesArray.parse('')).toThrow();
expect(() => ComposerReleasesArray.parse({})).toThrow();
});

it('parses ComposerReleasesArray', () => {
expect(ComposerReleasesArray.parse([])).toEqual([]);
expect(ComposerReleasesArray.parse([null])).toEqual([]);
expect(ComposerReleasesArray.parse([1, 2, 3])).toEqual([]);
expect(ComposerReleasesArray.parse(['foobar'])).toEqual([]);
describe('ComposerReleases', () => {
it('parses ComposerReleases', () => {
expect(ComposerReleases.parse(null)).toBeEmptyArray();
expect(ComposerReleases.parse(undefined)).toBeEmptyArray();
expect(ComposerReleases.parse('')).toBeEmptyArray();
expect(ComposerReleases.parse({})).toBeEmptyArray();
expect(ComposerReleases.parse([])).toBeEmptyArray();
expect(ComposerReleases.parse([null])).toBeEmptyArray();
expect(ComposerReleases.parse([1, 2, 3])).toBeEmptyArray();
expect(ComposerReleases.parse(['foobar'])).toBeEmptyArray();
expect(
ComposerReleasesArray.parse([
{ version: '1.2.3' },
{ version: 'dev-main' },
])
ComposerReleases.parse([{ version: '1.2.3' }, { version: 'dev-main' }])
).toEqual([{ version: '1.2.3' }, { version: 'dev-main' }]);
});
});

describe('ComposerReleasesRecord', () => {
it('rejects ComposerReleasesRecord', () => {
expect(() => ComposerReleasesRecord.parse(null)).toThrow();
expect(() => ComposerReleasesRecord.parse(undefined)).toThrow();
expect(() => ComposerReleasesRecord.parse('')).toThrow();
expect(() => ComposerReleasesRecord.parse([])).toThrow();
});

it('parses ComposerReleasesRecord', () => {
expect(ComposerReleasesRecord.parse({})).toEqual({});
expect(ComposerReleasesRecord.parse({ foo: null })).toEqual({});
expect(ComposerReleasesRecord.parse({ foo: 1, bar: 2, baz: 3 })).toEqual(
{}
);
expect(ComposerReleasesRecord.parse({ foo: 'bar' })).toEqual({});
expect(
ComposerReleasesRecord.parse({
'0.0.1': { foo: 'bar' },
'0.0.2': { version: '0.0.1' },
'1.2.3': { version: '1.2.3' },
'dev-main': { version: 'dev-main' },
})
).toEqual({
'1.2.3': { version: '1.2.3' },
'dev-main': { version: 'dev-main' },
});
});
});

describe('parsePackageResponse', () => {
it('parses package response', () => {
expect(parsePackagesResponse('foo/bar', null)).toEqual([]);
Expand Down
38 changes: 19 additions & 19 deletions lib/modules/datasource/packagist/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,16 @@ export const ComposerRelease = z
);
export type ComposerRelease = z.infer<typeof ComposerRelease>;

export const ComposerReleasesArray = z
.array(ComposerRelease.nullable().catch(null))
export const ComposerReleases = z
.union([
z.array(ComposerRelease.nullable().catch(null)),
z
.record(ComposerRelease.nullable().catch(null))
.transform((map) => Object.values(map)),
])
.catch([])
.transform((xs) => xs.filter((x): x is ComposerRelease => x !== null));
export type ComposerReleasesArray = z.infer<typeof ComposerReleasesArray>;

export const ComposerReleasesRecord = z
.record(ComposerRelease.nullable().catch(null))
.transform((map) => {
const res: Record<string, ComposerRelease> = {};
for (const [key, value] of Object.entries(map)) {
if (value !== null && value.version === key) {
res[key] = value;
}
}
return res;
});
export type ComposerReleasesRecord = z.infer<typeof ComposerReleasesRecord>;
export type ComposerReleases = z.infer<typeof ComposerReleases>;

export const ComposerPackagesResponse = z.object({
packages: z.record(z.unknown()),
Expand All @@ -90,11 +83,11 @@ export const ComposerPackagesResponse = z.object({
export function parsePackagesResponse(
packageName: string,
packagesResponse: unknown
): ComposerReleasesArray {
): ComposerReleases {
try {
const { packages } = ComposerPackagesResponse.parse(packagesResponse);
const array = MinifiedArray.parse(packages[packageName]);
const releases = ComposerReleasesArray.parse(array);
const releases = ComposerReleases.parse(array);
return releases;
} catch (err) {
logger.debug(
Expand All @@ -106,7 +99,7 @@ export function parsePackagesResponse(
}

export function extractReleaseResult(
...composerReleasesArrays: ComposerReleasesArray[]
...composerReleasesArrays: ComposerReleases[]
): ReleaseResult | null {
const releases: Release[] = [];
let homepage: string | null | undefined;
Expand Down Expand Up @@ -156,6 +149,13 @@ export function extractReleaseResult(
return result;
}

export function extractDepReleases(
composerReleases: unknown
): ReleaseResult | null {
const parsedReleases = ComposerReleases.parse(composerReleases);
return extractReleaseResult(parsedReleases);
}

export function parsePackagesResponses(
packageName: string,
packagesResponses: unknown[]
Expand Down

0 comments on commit 84e50dc

Please sign in to comment.