import { z } from 'zod';
// Common schemas
export const StringIdSchema = z.string().min(1, 'ID cannot be empty');
export const EmailSchema = z.string().email('Invalid email format');
export const PositiveIntSchema = z.number().int().positive();
export const NonNegativeIntSchema = z.number().int().nonnegative();
// Pagination
export const MaxResultsSchema = z.number().int().min(1).max(500).default(10);
export const PageTokenSchema = z.string().optional();
// Date/Time
export const ISODateTimeSchema = z.string().refine(
(val) => {
const date = new Date(val);
return !isNaN(date.getTime());
},
{ message: 'Invalid ISO date-time format' }
);
// Gmail schemas
export const GmailSearchSchema = z.object({
query: z.string().min(1, 'Search query cannot be empty'),
maxResults: MaxResultsSchema,
pageToken: PageTokenSchema,
});
export const GmailGetMessageSchema = z.object({
messageId: StringIdSchema,
format: z.enum(['metadata', 'full']).default('metadata'),
});
export const GmailModifyLabelsSchema = z.object({
messageId: StringIdSchema,
addLabelIds: z.array(StringIdSchema).optional(),
removeLabelIds: z.array(StringIdSchema).optional(),
}).refine(
(data) => data.addLabelIds?.length || data.removeLabelIds?.length,
{ message: 'At least one of addLabelIds or removeLabelIds must be provided' }
);
export const GmailSendMessageSchema = z.object({
to: z.array(EmailSchema).min(1, 'At least one recipient is required'),
subject: z.string().min(1, 'Subject cannot be empty'),
body: z.string().min(1, 'Body cannot be empty'),
cc: z.array(EmailSchema).optional(),
bcc: z.array(EmailSchema).optional(),
});
export const GmailReplyMessageSchema = z.object({
messageId: StringIdSchema,
body: z.string().min(1, 'Reply body cannot be empty'),
replyAll: z.boolean().default(false),
});
export const GmailDeleteMessageSchema = z.object({
messageId: StringIdSchema,
permanent: z.boolean().default(false), // false = move to trash, true = permanent delete
});
export const GmailCreateDraftSchema = z.object({
to: z.array(EmailSchema).min(1, 'At least one recipient is required'),
subject: z.string().min(1, 'Subject cannot be empty'),
body: z.string().min(1, 'Body cannot be empty'),
cc: z.array(EmailSchema).optional(),
bcc: z.array(EmailSchema).optional(),
});
export const GmailGetAttachmentSchema = z.object({
messageId: StringIdSchema,
attachmentId: StringIdSchema,
});
// Drive schemas
export const DriveSearchSchema = z.object({
q: z.string().min(1, 'Search query cannot be empty'),
pageSize: z.number().int().min(1).max(1000).default(10),
pageToken: PageTokenSchema,
});
export const DriveCreateFolderSchema = z.object({
name: z.string().min(1, 'Folder name cannot be empty'),
parentFolderId: StringIdSchema.optional(),
});
export const DrivePermissionRoleSchema = z.enum(['reader', 'writer', 'commenter', 'owner']);
export const DriveSetPermissionSchema = z.object({
fileId: StringIdSchema,
email: EmailSchema,
role: DrivePermissionRoleSchema.refine(
(val) => val !== 'owner',
{ message: 'Cannot set owner permission via this API' }
),
});
export const DriveCopyFileSchema = z.object({
fileId: StringIdSchema,
newName: z.string().optional(),
parentFolderId: StringIdSchema.optional(),
});
export const DriveUploadFileSchema = z.object({
name: z.string().min(1, 'File name cannot be empty'),
content: z.string().min(1, 'Content cannot be empty'), // base64 encoded content
mimeType: z.string().min(1, 'MIME type cannot be empty'),
parentFolderId: StringIdSchema.optional(),
});
export const DriveDownloadFileSchema = z.object({
fileId: StringIdSchema,
});
export const DriveDeleteFileSchema = z.object({
fileId: StringIdSchema,
permanent: z.boolean().default(false), // false = move to trash, true = permanent delete
});
export const DriveMoveFileSchema = z.object({
fileId: StringIdSchema,
newParentFolderId: StringIdSchema,
removeFromCurrentParent: z.boolean().default(true),
});
export const DriveGetFileSchema = z.object({
fileId: StringIdSchema,
});
// Sheets schemas
export const SheetsCreateSchema = z.object({
title: z.string().min(1, 'Spreadsheet title cannot be empty'),
parentFolderId: StringIdSchema.optional(),
});
export const SheetsRangeSchema = z.string().regex(
/^[A-Za-z0-9_]+(!([A-Z]+[0-9]+(:[A-Z]+[0-9]+)?|[A-Z]+:[A-Z]+))?$/,
'Invalid range format (e.g., "Sheet1!A1:B10" or "Sheet1!A:B")'
);
export const SheetsGetValuesSchema = z.object({
spreadsheetId: StringIdSchema,
range: z.string().min(1, 'Range cannot be empty'),
});
export const ValueInputOptionSchema = z.enum(['RAW', 'USER_ENTERED']).default('USER_ENTERED');
export const SheetsUpdateValuesSchema = z.object({
spreadsheetId: StringIdSchema,
range: z.string().min(1, 'Range cannot be empty'),
values: z.array(z.array(z.unknown())).min(1, 'Values cannot be empty'),
valueInputOption: ValueInputOptionSchema,
});
export const SheetsAppendValuesSchema = z.object({
spreadsheetId: StringIdSchema,
range: z.string().min(1, 'Range cannot be empty'),
values: z.array(z.array(z.unknown())).min(1, 'Values cannot be empty'),
valueInputOption: ValueInputOptionSchema,
});
export const SheetsBatchUpdateSchema = z.object({
spreadsheetId: StringIdSchema,
requests: z.array(z.record(z.unknown())).min(1, 'Requests cannot be empty'),
});
export const SheetsAddSheetSchema = z.object({
spreadsheetId: StringIdSchema,
title: z.string().min(1, 'Sheet title cannot be empty'),
index: z.number().int().nonnegative().optional(),
});
export const SheetsDeleteSheetSchema = z.object({
spreadsheetId: StringIdSchema,
sheetId: z.number().int(),
});
export const SheetsFormatCellsSchema = z.object({
spreadsheetId: StringIdSchema,
range: z.string().min(1, 'Range cannot be empty'),
format: z.object({
backgroundColor: z.object({
red: z.number().min(0).max(1).optional(),
green: z.number().min(0).max(1).optional(),
blue: z.number().min(0).max(1).optional(),
}).optional(),
textFormat: z.object({
bold: z.boolean().optional(),
italic: z.boolean().optional(),
fontSize: z.number().positive().optional(),
foregroundColor: z.object({
red: z.number().min(0).max(1).optional(),
green: z.number().min(0).max(1).optional(),
blue: z.number().min(0).max(1).optional(),
}).optional(),
}).optional(),
horizontalAlignment: z.enum(['LEFT', 'CENTER', 'RIGHT']).optional(),
verticalAlignment: z.enum(['TOP', 'MIDDLE', 'BOTTOM']).optional(),
}),
});
// Docs schemas
export const DocsCreateSchema = z.object({
title: z.string().min(1, 'Document title cannot be empty'),
parentFolderId: StringIdSchema.optional(),
});
export const DocsGetSchema = z.object({
documentId: StringIdSchema,
});
export const DocsInsertTextSchema = z.object({
documentId: StringIdSchema,
index: NonNegativeIntSchema,
text: z.string().min(1, 'Text cannot be empty'),
});
export const DocsReplaceTextSchema = z.object({
documentId: StringIdSchema,
containsText: z.string().min(1, 'Search text cannot be empty'),
replaceText: z.string(),
matchCase: z.boolean().default(false),
});
export const DocsFormatTextSchema = z.object({
documentId: StringIdSchema,
startIndex: NonNegativeIntSchema,
endIndex: PositiveIntSchema,
format: z.object({
bold: z.boolean().optional(),
italic: z.boolean().optional(),
underline: z.boolean().optional(),
strikethrough: z.boolean().optional(),
fontSize: z.number().positive().optional(),
foregroundColor: z.object({
red: z.number().min(0).max(1).optional(),
green: z.number().min(0).max(1).optional(),
blue: z.number().min(0).max(1).optional(),
}).optional(),
}),
});
export const DocsInsertImageSchema = z.object({
documentId: StringIdSchema,
index: NonNegativeIntSchema,
uri: z.string().url('Invalid image URL'),
width: z.number().positive().optional(),
height: z.number().positive().optional(),
});
export const DocsBatchUpdateSchema = z.object({
documentId: StringIdSchema,
requests: z.array(z.record(z.unknown())).min(1, 'Requests cannot be empty'),
});
// Calendar schemas
export const CalendarListEventsSchema = z.object({
calendarId: z.string().default('primary'),
timeMinISO: ISODateTimeSchema.optional(),
timeMaxISO: ISODateTimeSchema.optional(),
q: z.string().optional(),
maxResults: MaxResultsSchema,
});
export const CalendarEventSchema = z.object({
summary: z.string().min(1, 'Event summary cannot be empty'),
description: z.string().optional(),
location: z.string().optional(),
startISO: ISODateTimeSchema,
endISO: ISODateTimeSchema,
attendeesEmails: z.array(EmailSchema).optional(),
}).refine(
(data) => new Date(data.startISO) < new Date(data.endISO),
{ message: 'Start time must be before end time' }
);
export const CalendarCreateEventSchema = z.object({
calendarId: z.string().default('primary'),
event: CalendarEventSchema,
dryRun: z.boolean().default(true),
});
export const CalendarUpdateEventSchema = z.object({
calendarId: z.string().default('primary'),
eventId: StringIdSchema,
patch: z.object({
summary: z.string().optional(),
description: z.string().optional(),
location: z.string().optional(),
startISO: ISODateTimeSchema.optional(),
endISO: ISODateTimeSchema.optional(),
attendeesEmails: z.array(EmailSchema).optional(),
}),
dryRun: z.boolean().default(true),
});
export const CalendarDeleteEventSchema = z.object({
calendarId: z.string().default('primary'),
eventId: StringIdSchema,
dryRun: z.boolean().default(true),
});
export const CalendarFreeBusySchema = z.object({
timeMinISO: ISODateTimeSchema,
timeMaxISO: ISODateTimeSchema,
calendarIds: z.array(z.string()).default(['primary']),
});
// Validation helper
export function validateInput<T>(
schema: z.ZodSchema<T>,
input: unknown
): { success: true; data: T } | { success: false; errors: string[] } {
const result = schema.safeParse(input);
if (result.success) {
return { success: true, data: result.data };
}
const errors = result.error.issues.map(
(issue) => `${issue.path.join('.')}: ${issue.message}`
);
return { success: false, errors };
}
// Export all schemas
// Gmail
export type GmailSearch = z.infer<typeof GmailSearchSchema>;
export type GmailGetMessage = z.infer<typeof GmailGetMessageSchema>;
export type GmailModifyLabels = z.infer<typeof GmailModifyLabelsSchema>;
export type GmailSendMessage = z.infer<typeof GmailSendMessageSchema>;
export type GmailReplyMessage = z.infer<typeof GmailReplyMessageSchema>;
export type GmailDeleteMessage = z.infer<typeof GmailDeleteMessageSchema>;
export type GmailCreateDraft = z.infer<typeof GmailCreateDraftSchema>;
export type GmailGetAttachment = z.infer<typeof GmailGetAttachmentSchema>;
// Drive
export type DriveSearch = z.infer<typeof DriveSearchSchema>;
export type DriveCreateFolder = z.infer<typeof DriveCreateFolderSchema>;
export type DriveSetPermission = z.infer<typeof DriveSetPermissionSchema>;
export type DriveCopyFile = z.infer<typeof DriveCopyFileSchema>;
export type DriveUploadFile = z.infer<typeof DriveUploadFileSchema>;
export type DriveDownloadFile = z.infer<typeof DriveDownloadFileSchema>;
export type DriveDeleteFile = z.infer<typeof DriveDeleteFileSchema>;
export type DriveMoveFile = z.infer<typeof DriveMoveFileSchema>;
export type DriveGetFile = z.infer<typeof DriveGetFileSchema>;
// Sheets
export type SheetsCreate = z.infer<typeof SheetsCreateSchema>;
export type SheetsGetValues = z.infer<typeof SheetsGetValuesSchema>;
export type SheetsUpdateValues = z.infer<typeof SheetsUpdateValuesSchema>;
export type SheetsAppendValues = z.infer<typeof SheetsAppendValuesSchema>;
export type SheetsBatchUpdate = z.infer<typeof SheetsBatchUpdateSchema>;
export type SheetsAddSheet = z.infer<typeof SheetsAddSheetSchema>;
export type SheetsDeleteSheet = z.infer<typeof SheetsDeleteSheetSchema>;
export type SheetsFormatCells = z.infer<typeof SheetsFormatCellsSchema>;
// Docs
export type DocsCreate = z.infer<typeof DocsCreateSchema>;
export type DocsGet = z.infer<typeof DocsGetSchema>;
export type DocsInsertText = z.infer<typeof DocsInsertTextSchema>;
export type DocsReplaceText = z.infer<typeof DocsReplaceTextSchema>;
export type DocsFormatText = z.infer<typeof DocsFormatTextSchema>;
export type DocsInsertImage = z.infer<typeof DocsInsertImageSchema>;
export type DocsBatchUpdate = z.infer<typeof DocsBatchUpdateSchema>;
// Calendar
export type CalendarListEvents = z.infer<typeof CalendarListEventsSchema>;
export type CalendarEvent = z.infer<typeof CalendarEventSchema>;
export type CalendarCreateEvent = z.infer<typeof CalendarCreateEventSchema>;
export type CalendarUpdateEvent = z.infer<typeof CalendarUpdateEventSchema>;
export type CalendarDeleteEvent = z.infer<typeof CalendarDeleteEventSchema>;
export type CalendarFreeBusy = z.infer<typeof CalendarFreeBusySchema>;