Skip to content

Commit 457104e

Browse files
author
Gavin Williams
committed
Add zod schema to Gitlab events
1 parent 6c006a0 commit 457104e

2 files changed

Lines changed: 74 additions & 70 deletions

File tree

  • packages/web/src

packages/web/src/app/api/(server)/webhook/route.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { env } from "@sourcebot/shared";
88
import { processGitHubPullRequest, processGitLabMergeRequest } from "@/features/agents/review-agent/app";
99
import { throttling, type ThrottlingOptions } from "@octokit/plugin-throttling";
1010
import fs from "fs";
11-
import { GitHubPullRequest, GitLabMergeRequestPayload, GitLabNotePayload } from "@/features/agents/review-agent/types";
11+
import { GitHubPullRequest, gitLabMergeRequestPayloadSchema, gitLabNotePayloadSchema } from "@/features/agents/review-agent/types";
1212
import { createLogger } from "@sourcebot/shared";
1313

1414
const logger = createLogger('webhook');
@@ -96,24 +96,24 @@ function isIssueCommentEvent(eventHeader: string, payload: unknown): payload is
9696
return eventHeader === "issue_comment" && typeof payload === "object" && payload !== null && "action" in payload && typeof payload.action === "string" && payload.action === "created";
9797
}
9898

99-
function isGitLabMergeRequestEvent(eventHeader: string, payload: unknown): payload is GitLabMergeRequestPayload {
99+
function isGitLabMergeRequestEvent(eventHeader: string, payload: unknown): boolean {
100100
return (
101101
eventHeader === "Merge Request Hook" &&
102102
typeof payload === "object" &&
103103
payload !== null &&
104104
"object_attributes" in payload &&
105-
typeof (payload as GitLabMergeRequestPayload).object_attributes?.action === "string" &&
106-
["open", "update", "reopen"].includes((payload as GitLabMergeRequestPayload).object_attributes.action)
105+
typeof (payload as Record<string, unknown> & { object_attributes: { action?: unknown } }).object_attributes?.action === "string" &&
106+
["open", "update", "reopen"].includes((payload as Record<string, unknown> & { object_attributes: { action: string } }).object_attributes.action)
107107
);
108108
}
109109

110-
function isGitLabNoteEvent(eventHeader: string, payload: unknown): payload is GitLabNotePayload {
110+
function isGitLabNoteEvent(eventHeader: string, payload: unknown): boolean {
111111
return (
112112
eventHeader === "Note Hook" &&
113113
typeof payload === "object" &&
114114
payload !== null &&
115115
"object_attributes" in payload &&
116-
(payload as GitLabNotePayload).object_attributes?.noteable_type === "MergeRequest"
116+
(payload as Record<string, unknown> & { object_attributes: { noteable_type?: unknown } }).object_attributes?.noteable_type === "MergeRequest"
117117
);
118118
}
119119

@@ -217,45 +217,57 @@ export const POST = async (request: NextRequest) => {
217217
return Response.json({ status: 'ok' });
218218
}
219219

220+
const parsed = gitLabMergeRequestPayloadSchema.safeParse(body);
221+
if (!parsed.success) {
222+
logger.warn(`GitLab MR webhook payload failed validation: ${parsed.error.message}`);
223+
return Response.json({ status: 'ok' });
224+
}
225+
220226
try {
221227
await processGitLabMergeRequest(
222228
gitlabClient,
223-
body.project.id,
224-
body,
229+
parsed.data.project.id,
230+
parsed.data,
225231
env.GITLAB_REVIEW_AGENT_HOST,
226232
);
227233
} catch (error) {
228-
logger.error(`Error in processGitLabMergeRequest for project ${body.project.id} (${gitlabEvent}):`, error);
234+
logger.error(`Error in processGitLabMergeRequest for project ${parsed.data.project.id} (${gitlabEvent}):`, error);
229235
}
230236
}
231237

232238
if (isGitLabNoteEvent(gitlabEvent, body)) {
233-
const noteBody = body.object_attributes?.note;
239+
const parsed = gitLabNotePayloadSchema.safeParse(body);
240+
if (!parsed.success) {
241+
logger.warn(`GitLab Note webhook payload failed validation: ${parsed.error.message}`);
242+
return Response.json({ status: 'ok' });
243+
}
244+
245+
const noteBody = parsed.data.object_attributes.note;
234246
if (noteBody === `/${env.REVIEW_AGENT_REVIEW_COMMAND}`) {
235247
logger.info('Review agent review command received on GitLab MR, processing');
236248

237-
const mrPayload: GitLabMergeRequestPayload = {
249+
const mrPayload = {
238250
object_kind: "merge_request",
239251
object_attributes: {
240-
iid: body.merge_request.iid,
241-
title: body.merge_request.title,
242-
description: body.merge_request.description,
252+
iid: parsed.data.merge_request.iid,
253+
title: parsed.data.merge_request.title,
254+
description: parsed.data.merge_request.description,
243255
action: "update",
244-
last_commit: body.merge_request.last_commit,
245-
diff_refs: body.merge_request.diff_refs,
256+
last_commit: parsed.data.merge_request.last_commit,
257+
diff_refs: parsed.data.merge_request.diff_refs,
246258
},
247-
project: body.project,
259+
project: parsed.data.project,
248260
};
249261

250262
try {
251263
await processGitLabMergeRequest(
252264
gitlabClient,
253-
body.project.id,
265+
parsed.data.project.id,
254266
mrPayload,
255267
env.GITLAB_REVIEW_AGENT_HOST,
256268
);
257269
} catch (error) {
258-
logger.error(`Error in processGitLabMergeRequest for project ${body.project.id} (${gitlabEvent}):`, error);
270+
logger.error(`Error in processGitLabMergeRequest for project ${parsed.data.project.id} (${gitlabEvent}):`, error);
259271
}
260272
}
261273
}

packages/web/src/features/agents/review-agent/types.ts

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -56,56 +56,48 @@ export const sourcebot_file_diff_review_schema = z.object({
5656
});
5757
export type sourcebot_file_diff_review = z.infer<typeof sourcebot_file_diff_review_schema>;
5858

59-
export interface GitLabMergeRequestPayload {
60-
object_kind: string;
61-
object_attributes: {
62-
iid: number;
63-
title: string;
64-
description: string | null;
65-
action: string;
66-
last_commit: {
67-
id: string;
68-
};
69-
diff_refs: {
70-
base_sha: string;
71-
head_sha: string;
72-
start_sha: string;
73-
};
74-
};
75-
project: {
76-
id: number;
77-
name: string;
78-
path_with_namespace: string;
79-
web_url: string;
80-
namespace: string;
81-
};
82-
}
59+
const gitLabProjectSchema = z.object({
60+
id: z.number(),
61+
name: z.string(),
62+
path_with_namespace: z.string(),
63+
web_url: z.string(),
64+
namespace: z.string(),
65+
});
66+
67+
const gitLabDiffRefsSchema = z.object({
68+
base_sha: z.string(),
69+
head_sha: z.string(),
70+
start_sha: z.string(),
71+
}).nullable().optional();
72+
73+
export const gitLabMergeRequestPayloadSchema = z.object({
74+
object_kind: z.string(),
75+
object_attributes: z.object({
76+
iid: z.number(),
77+
title: z.string(),
78+
description: z.string().nullable(),
79+
action: z.string(),
80+
last_commit: z.object({ id: z.string() }),
81+
diff_refs: gitLabDiffRefsSchema,
82+
}),
83+
project: gitLabProjectSchema,
84+
});
85+
export type GitLabMergeRequestPayload = z.infer<typeof gitLabMergeRequestPayloadSchema>;
8386

84-
export interface GitLabNotePayload {
85-
object_kind: string;
86-
object_attributes: {
87-
note: string;
88-
noteable_type: string;
89-
};
90-
merge_request: {
91-
iid: number;
92-
title: string;
93-
description: string | null;
94-
last_commit: {
95-
id: string;
96-
};
97-
diff_refs: {
98-
base_sha: string;
99-
head_sha: string;
100-
start_sha: string;
101-
};
102-
};
103-
project: {
104-
id: number;
105-
name: string;
106-
path_with_namespace: string;
107-
web_url: string;
108-
namespace: string;
109-
};
110-
}
87+
export const gitLabNotePayloadSchema = z.object({
88+
object_kind: z.string(),
89+
object_attributes: z.object({
90+
note: z.string(),
91+
noteable_type: z.string(),
92+
}),
93+
merge_request: z.object({
94+
iid: z.number(),
95+
title: z.string(),
96+
description: z.string().nullable(),
97+
last_commit: z.object({ id: z.string() }),
98+
diff_refs: gitLabDiffRefsSchema,
99+
}),
100+
project: gitLabProjectSchema,
101+
});
102+
export type GitLabNotePayload = z.infer<typeof gitLabNotePayloadSchema>;
111103

0 commit comments

Comments
 (0)