Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/src/api/public/v1/akrites-external/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,10 @@ components:
enum: [failed]
reason:
type: string
description: >
Why the job failed. Inferred-repo jobs whose result has left
Temporal retention answer "Job result expired, resubmit the job";
a new submission for the same purl is then accepted.
example: Repository not found on GitHub

MemberContributorsJobUnresolvable:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
collectionPathForPurlType,
isWellFormedPurl,
purlType,
canonicalizePurlType,
} from './memberContributorCollectionPaths'

describe('purlType', () => {
Expand All @@ -21,6 +22,16 @@ describe('purlType', () => {
})
})

describe('canonicalizePurlType', () => {
it.each([
['pkg:Hackage/aeson', 'pkg:hackage/aeson'],
['pkg:hackage/Aeson', 'pkg:hackage/Aeson'],
['pkg:npm/%40Babel/core', 'pkg:npm/%40Babel/core'],
])('canonicalizes %s to %s', (purl, expected) => {
expect(canonicalizePurlType(purl)).toBe(expected)
})
})

describe('collectionPathForPurlType', () => {
it('maps every collected purl type to the github path', () => {
for (const type of ['npm', 'maven', 'pypi', 'cargo', 'golang', 'nuget', 'composer', 'gem']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ export function purlType(purl: string): string {
return type.toLowerCase()
}

export function canonicalizePurlType(purl: string): string {
const schemeLength = 'pkg:'.length
const slash = purl.indexOf('/', schemeLength)
const typeEnd = slash === -1 ? purl.length : slash
return (
purl.slice(0, schemeLength) +
purl.slice(schemeLength, typeEnd).toLowerCase() +
purl.slice(typeEnd)
)
}

const WELL_FORMED_PURL = /^pkg:[a-z][a-z0-9.+-]*\/[^/?#]+/i

function isDecodable(value: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,15 @@ describe('submitMemberContributorsJob, unknown ecosystem', () => {

it.each(['pkg:hackage/aeson', 'pkg:Hackage/aeson'])(
'creates an llm repo resolution job for %s without a package lookup and starts the inferred-repo workflow',
async (purl) => {
const { req, res, status, json } = mockReqRes({ purl })
async (requestedPurl) => {
const purl = 'pkg:hackage/aeson'
const { req, res, status, json } = mockReqRes({ purl: requestedPurl })

await submitMemberContributorsJob(req, res)

expect(findActiveMemberContributorJobByPurl.mock.calls).toEqual([[{}, purl]])
expect(findLatestMemberContributorJobByPurl.mock.calls).toEqual([[{}, purl]])

const [, input] = createMemberContributorJob.mock.calls[0]
expect(createMemberContributorJob.mock.calls).toEqual([
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {

import {
LLM_REPO_RESOLUTION_PATH,
canonicalizePurlType,
collectionPathForPurlType,
isWellFormedPurl,
purlType,
Expand Down Expand Up @@ -179,13 +180,14 @@ async function respondWithActiveJob(

async function submitInferredRepoJob(
qx: QueryExecutor,
purl: string,
requestedPurl: string,
ecosystem: string,
res: Response,
): Promise<void> {
if (!isWellFormedPurl(purl)) {
if (!isWellFormedPurl(requestedPurl)) {
throw new BadRequestError('Malformed purl')
}
const purl = canonicalizePurlType(requestedPurl)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reachable with the data that exists. The table holds 4 jobs in prod (2 github, 2 llm_repo_resolution, created today) and none has a mixed-case purl type, so no stored key is orphaned by this change. Had one existed the cost would be a single extra LLM call (~$0.002) after which the lowercased row becomes the one every later lookup hits; a migration for that is not worth the risk. Leaving as is.

const active = await findActiveMemberContributorJobByPurl(qx, purl)
if (active) {
await respondWithActiveJob(qx, active.id, MEMBER_CONTRIBUTORS_INFERRED_REPO_WORKFLOW, res)
Expand Down
Loading