gemini-cost-tracker
Version:
CLI tool to display token usage and costs for Gemini and Vertex AI
102 lines • 4.29 kB
JavaScript
import { ErrorHandler } from './errorHandler.js';
import { ERROR_MESSAGES, SUPPORTED_MODELS } from './constants.js';
export class ValidationUtils {
static isValidPeriod(period) {
return ['today', 'week', 'month', 'custom'].includes(period);
}
static isValidFormat(format) {
return ['table', 'json', 'csv', 'chart'].includes(format);
}
static isValidCurrency(currency) {
return ['USD', 'JPY'].includes(currency);
}
static isValidModel(model) {
const allSupportedModels = [
...SUPPORTED_MODELS.GEMINI,
...SUPPORTED_MODELS.VERTEX_AI,
];
return allSupportedModels.includes(model);
}
static validateDateRange(startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
throw ErrorHandler.createValidationError('Invalid date format. Use YYYY-MM-DD format.');
}
if (start >= end) {
throw ErrorHandler.createValidationError(ERROR_MESSAGES.INVALID_DATE_RANGE);
}
const now = new Date();
if (end > now) {
throw ErrorHandler.createValidationError('End date cannot be in the future');
}
}
static validateGcpProjectId(projectId) {
// GCP project IDs must be 6-30 characters, lowercase letters, digits, and hyphens
const projectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
if (!projectIdRegex.test(projectId)) {
throw ErrorHandler.createValidationError('Invalid GCP project ID format. Must be 6-30 characters, lowercase letters, digits, and hyphens only.');
}
}
static validateApiKey(apiKey) {
if (!apiKey || apiKey.trim().length === 0) {
throw ErrorHandler.createValidationError('API key cannot be empty');
}
// Basic format check for Gemini API keys (typically start with specific prefixes)
if (!apiKey.startsWith('AIza') && !apiKey.startsWith('AI39')) {
throw ErrorHandler.createValidationError('Invalid API key format. Gemini API keys typically start with "AIza" or "AI39".');
}
}
static validateFilePath(filePath) {
if (!filePath || filePath.trim().length === 0) {
throw ErrorHandler.createValidationError('File path cannot be empty');
}
// Check for potentially dangerous paths
if (filePath.includes('..') || filePath.includes('~')) {
throw ErrorHandler.createValidationError('Invalid file path: relative paths not allowed');
}
}
static validateCliOptions(options) {
if (options.period && !this.isValidPeriod(options.period)) {
throw ErrorHandler.createValidationError(`Invalid period: ${options.period}`);
}
if (options.format && !this.isValidFormat(options.format)) {
throw ErrorHandler.createValidationError(`Invalid format: ${options.format}`);
}
if (options.currency && !this.isValidCurrency(options.currency)) {
throw ErrorHandler.createValidationError(`Invalid currency: ${options.currency}`);
}
if (options.model && !this.isValidModel(options.model)) {
throw ErrorHandler.createValidationError(`Unsupported model: ${options.model}`);
}
if (options.period === 'custom') {
if (!options.startDate || !options.endDate) {
throw ErrorHandler.createValidationError('Custom period requires both startDate and endDate');
}
this.validateDateRange(options.startDate, options.endDate);
}
if (options.output) {
this.validateFilePath(options.output);
}
if (options.project) {
this.validateGcpProjectId(options.project);
}
}
static sanitizeInput(input) {
return input.trim().replace(/[<>"'&]/g, '');
}
static isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
static isValidUrl(url) {
try {
new globalThis.URL(url);
return true;
}
catch {
return false;
}
}
}
//# sourceMappingURL=validation.js.map