fixing jsonSchema validation by using zod

This commit is contained in:
2026-04-11 22:23:25 -05:00
parent 0bae26ae0b
commit eb0a4e8308
56 changed files with 12275 additions and 287 deletions

270
src/tools/cars.ts Normal file
View File

@@ -0,0 +1,270 @@
/**
* Car Rental Tools
* Implements searchCars and bookCar MCP tools
*/
import { generateCarOptions, getCarById } from '../data/cars.js';
import { generatePNR } from '../data/pnr.js';
import { storage } from '../session/storage.js';
import { logger } from '../utils/logger.js';
import { ValidationError, BookingError } from '../utils/errors.js';
import {
validateRequired,
validateString,
validateFutureDate
} from '../validation/validators.js';
/**
* Search for available car rentals
* @param {Object} params - Search parameters
* @param {string} sessionId - Session identifier
* @returns {Promise<Object>} Search results
*/
export async function searchCars(params: any, sessionId: any) {
try {
// Validate required fields
validateRequired(params.pickupLocation, 'pickupLocation');
validateRequired(params.dropoffLocation, 'dropoffLocation');
validateRequired(params.pickupDate, 'pickupDate');
validateRequired(params.dropoffDate, 'dropoffDate');
validateString(params.pickupLocation, 'pickupLocation', { minLength: 3, maxLength: 3 });
validateString(params.dropoffLocation, 'dropoffLocation', { minLength: 3, maxLength: 3 });
validateFutureDate(params.pickupDate, 'pickupDate');
validateFutureDate(params.dropoffDate, 'dropoffDate');
const pickupLocation = params.pickupLocation.toUpperCase();
const dropoffLocation = params.dropoffLocation.toUpperCase();
const pickupDate = new Date(params.pickupDate);
const dropoffDate = new Date(params.dropoffDate);
// Validate date range
if (dropoffDate <= pickupDate) {
throw new ValidationError('dropoffDate must be after pickupDate');
}
const dropoffDateObj = new Date(dropoffDate);
const days = Math.ceil((dropoffDateObj.getTime() - pickupDate.getTime()) / (1000 * 60 * 60 * 24));
if (days < 1) {
throw new ValidationError('Minimum rental period is 1 day');
}
// Generate car options
const options = generateCarOptions(
pickupLocation,
dropoffLocation,
params.pickupDate,
params.dropoffDate
);
logger.info({ sessionId, pickupLocation, dropoffLocation, days, resultCount: options.length }, 'Car search completed');
return {
pickupLocation,
dropoffLocation,
pickupDate: params.pickupDate,
dropoffDate: params.dropoffDate,
days,
results: options
};
} catch (error) {
logger.error({ error, sessionId }, 'Car search failed');
throw error;
}
}
/**
* Book a car rental
* @param {Object} params - Booking parameters
* @param {string} sessionId - Session identifier
* @returns {Promise<Object>} Booking confirmation
*/
export async function bookCar(params: any, sessionId: any) {
try {
// Validate required fields
validateRequired(params.carId, 'carId');
validateRequired(params.pickupLocation, 'pickupLocation');
validateRequired(params.dropoffLocation, 'dropoffLocation');
validateRequired(params.pickupDate, 'pickupDate');
validateRequired(params.dropoffDate, 'dropoffDate');
validateRequired(params.driverName, 'driverName');
validateRequired(params.driverEmail, 'driverEmail');
const { carId, pickupLocation, dropoffLocation, pickupDate, dropoffDate, driverName, driverEmail, driverPhone, driverLicense, pnr: existingPnr } = params;
// Validate car exists
const car = getCarById(carId);
if (!car) {
throw new ValidationError(`Car not found: ${carId}`);
}
// Validate dates
validateFutureDate(pickupDate, 'pickupDate');
validateFutureDate(dropoffDate, 'dropoffDate');
const pickup = new Date(pickupDate);
const dropoff = new Date(dropoffDate);
if (dropoff <= pickup) {
throw new ValidationError('dropoffDate must be after pickupDate');
}
const pickupDateObj = new Date(params.pickupDate);
const dropoffDateObj = new Date(params.dropoffDate);
const days = Math.ceil((dropoffDateObj.getTime() - pickupDateObj.getTime()) / (1000 * 60 * 60 * 24));
// Calculate pricing (recalculate with same logic as search)
const oneWayFee = pickupLocation !== dropoffLocation ? 75 : 0;
const dailyRate = car.basePrice; // Simplified - real calculation in generateCarOptions
const totalPrice = (dailyRate * days) + oneWayFee;
// Generate or use existing PNR
const pnr = existingPnr || generatePNR();
const client = storage.client;
// Check if this is adding to existing booking
let booking;
if (existingPnr) {
const existingKey = `gds:session:${sessionId}:booking:${existingPnr}`;
const existingData = await client.get(existingKey);
if (!existingData) {
throw new BookingError(`Booking not found: ${existingPnr}`);
}
booking = JSON.parse(existingData);
// Add car segment
if (!booking.cars) {
booking.cars = [];
}
booking.cars.push({
carId: car.carId,
company: car.company,
category: car.category,
example: car.example,
passengers: car.passengers,
bags: car.bags,
features: car.features,
pickupLocation,
dropoffLocation,
pickupDate,
dropoffDate,
days,
driverName,
driverEmail,
driverPhone,
driverLicense,
pricing: {
dailyRate,
days,
oneWayFee,
totalPrice,
currency: 'USD'
},
status: 'confirmed',
bookedAt: new Date().toISOString()
});
// Recalculate total price
let totalBookingPrice = 0;
if (booking.flights) {
totalBookingPrice += booking.flights.reduce((sum, f) => sum + (f.pricing?.totalPrice || 0), 0);
}
if (booking.hotels) {
totalBookingPrice += booking.hotels.reduce((sum, h) => sum + (h.pricing?.totalPrice || 0), 0);
}
if (booking.cars) {
totalBookingPrice += booking.cars.reduce((sum, c) => sum + (c.pricing?.totalPrice || 0), 0);
}
booking.totalPrice = totalBookingPrice;
booking.updatedAt = new Date().toISOString();
} else {
// Create new car-only booking
booking = {
pnr,
type: 'car',
status: 'confirmed',
cars: [{
carId: car.carId,
company: car.company,
category: car.category,
example: car.example,
passengers: car.passengers,
bags: car.bags,
features: car.features,
pickupLocation,
dropoffLocation,
pickupDate,
dropoffDate,
days,
driverName,
driverEmail,
driverPhone,
driverLicense,
pricing: {
dailyRate,
days,
oneWayFee,
totalPrice,
currency: 'USD'
},
status: 'confirmed',
bookedAt: new Date().toISOString()
}],
totalPrice,
currency: 'USD',
createdAt: new Date().toISOString(),
sessionId
};
}
// Store booking
const bookingKey = `gds:session:${sessionId}:booking:${pnr}`;
await client.set(bookingKey, JSON.stringify(booking), 'EX', 3600);
// Update session bookings set
await client.sadd(`gds:session:${sessionId}:bookings`, pnr);
logger.info({ sessionId, pnr, carId, days }, 'Car rental booking created');
return {
pnr,
status: 'confirmed',
car: {
carId: car.carId,
company: car.company,
category: car.category,
example: car.example,
pickupLocation,
dropoffLocation,
pickupDate,
dropoffDate,
days
},
pricing: {
dailyRate,
days,
oneWayFee,
totalPrice,
currency: 'USD'
},
driverName,
driverEmail,
message: existingPnr
? `Car rental added to existing booking ${pnr}`
: `Car rental confirmed with PNR ${pnr}`
};
} catch (error) {
logger.error({ error, sessionId }, 'Car booking failed');
throw error;
}
}
export default { searchCars, bookCar };