Skip to content

Commit 85ebc36

Browse files
committed
Add typed error handling for PR creation
Replaces undefined return with OpenPrResult discriminated union type: - Distinguishes failure reasons: already_exists, validation_error, permission_denied, network_error, unknown - Enables caller to handle specific error cases appropriately - Provides detailed error information including validation details This foundational change enables better error handling in subsequent commits.
1 parent 62b3aea commit 85ebc36

1 file changed

Lines changed: 55 additions & 16 deletions

File tree

src/commands/fix/pull-request.mts

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,26 @@ export type OpenSocketFixPrOptions = {
3535
ghsaDetails?: Map<string, GhsaDetails> | undefined
3636
}
3737

38+
export type OpenPrResult =
39+
| { ok: true; pr: OctokitResponse<Pr> }
40+
| { ok: false; reason: 'already_exists'; error: RequestError }
41+
| {
42+
ok: false
43+
reason: 'validation_error'
44+
error: RequestError
45+
details: string
46+
}
47+
| { ok: false; reason: 'permission_denied'; error: RequestError }
48+
| { ok: false; reason: 'network_error'; error: RequestError }
49+
| { ok: false; reason: 'unknown'; error: Error }
50+
3851
export async function openSocketFixPr(
3952
owner: string,
4053
repo: string,
4154
branch: string,
4255
ghsaIds: string[],
4356
options?: OpenSocketFixPrOptions | undefined,
44-
): Promise<OctokitResponse<Pr> | undefined> {
57+
): Promise<OpenPrResult> {
4558
const { baseBranch = 'main', ghsaDetails } = {
4659
__proto__: null,
4760
...options,
@@ -59,25 +72,51 @@ export async function openSocketFixPr(
5972
body: getSocketFixPullRequestBody(ghsaIds, ghsaDetails),
6073
}
6174
debugDir('inspect', { octokitPullsCreateParams })
62-
return await octokit.pulls.create(octokitPullsCreateParams)
75+
const pr = await octokit.pulls.create(octokitPullsCreateParams)
76+
return { ok: true, pr }
6377
} catch (e) {
64-
let message = `Failed to open pull request`
65-
const errors =
66-
e instanceof RequestError
67-
? (e.response?.data as any)?.['errors']
68-
: undefined
69-
if (Array.isArray(errors) && errors.length) {
70-
const details = errors
71-
.map(
72-
d =>
73-
`- ${d.message?.trim() ?? `${d.resource}.${d.field} (${d.code})`}`,
78+
// Handle RequestError from Octokit.
79+
if (e instanceof RequestError) {
80+
const errors = (e.response?.data as any)?.['errors']
81+
const errorMessages = Array.isArray(errors)
82+
? errors.map(
83+
d => d.message?.trim() ?? `${d.resource}.${d.field} (${d.code})`,
84+
)
85+
: []
86+
87+
// Check for "PR already exists" error.
88+
if (
89+
errorMessages.some(msg =>
90+
msg.toLowerCase().includes('pull request already exists'),
7491
)
75-
.join('\n')
76-
message += `:\n${details}`
92+
) {
93+
debugFn('error', 'Failed to open pull request: already exists')
94+
return { ok: false, reason: 'already_exists', error: e }
95+
}
96+
97+
// Check for validation errors (e.g., no commits between branches).
98+
if (errors && errors.length > 0) {
99+
const details = errorMessages.map(d => `- ${d}`).join('\n')
100+
debugFn('error', `Failed to open pull request:\n${details}`)
101+
return { ok: false, reason: 'validation_error', error: e, details }
102+
}
103+
104+
// Check HTTP status codes.
105+
if (e.status === 403 || e.status === 401) {
106+
debugFn('error', 'Failed to open pull request: permission denied')
107+
return { ok: false, reason: 'permission_denied', error: e }
108+
}
109+
110+
if (e.status && e.status >= 500) {
111+
debugFn('error', 'Failed to open pull request: network error')
112+
return { ok: false, reason: 'network_error', error: e }
113+
}
77114
}
78-
debugFn('error', message)
115+
116+
// Unknown error.
117+
debugFn('error', `Failed to open pull request: ${e}`)
118+
return { ok: false, reason: 'unknown', error: e as Error }
79119
}
80-
return undefined
81120
}
82121

83122
export type GQL_MERGE_STATE_STATUS =

0 commit comments

Comments
 (0)