@prodbirdy/mockup-generator
Version:
Serverless-optimized TypeScript SDK for generating high-quality product mockups from PSD templates
247 lines (207 loc) • 6.54 kB
text/typescript
/**
* Input validation for MockupSDK
*/
import type {
MockupGenerationConfig,
BatchMockupConfig,
UploadOptions,
SmartObjectReplacement,
ExportOptions,
} from "./types";
import { ValidationError } from "./types";
export function validateGenerationConfig(config: MockupGenerationConfig): void {
if (!config || typeof config !== "object") {
throw new ValidationError(
"Configuration is required and must be an object"
);
}
// Validate PSD input
validatePSDInput(config.psd);
// Validate replacements
validateSmartObjects(config.replacements);
// Validate exports if provided
if (config.exports) {
validateExportOptions(config.exports);
}
}
export function validateBatchConfig(config: BatchMockupConfig): void {
if (!config || typeof config !== "object") {
throw new ValidationError(
"Batch configuration is required and must be an object"
);
}
// Validate template PSD
validatePSDInput(config.template);
// Validate variations
if (!Array.isArray(config.variations)) {
throw new ValidationError("Variations must be an array");
}
if (config.variations.length === 0) {
throw new ValidationError("At least one variation is required");
}
const seenIds = new Set<string>();
for (const [index, variation] of config.variations.entries()) {
if (!variation.id || typeof variation.id !== "string") {
throw new ValidationError(
`Variation ${index}: id is required and must be a string`
);
}
if (seenIds.has(variation.id)) {
throw new ValidationError(
`Variation ${index}: duplicate id '${variation.id}'`
);
}
seenIds.add(variation.id);
validateSmartObjects(variation.replacements);
if (variation.exports) {
validateExportOptions(variation.exports);
}
}
}
export function validateUploadOptions(options: UploadOptions): void {
if (!options || typeof options !== "object") {
return; // Options are optional
}
if (options.prefix && typeof options.prefix !== "string") {
throw new ValidationError("Upload prefix must be a string");
}
if (
options.uploadPSD !== undefined &&
typeof options.uploadPSD !== "boolean"
) {
throw new ValidationError("uploadPSD must be a boolean");
}
if (
options.uploadExports !== undefined &&
typeof options.uploadExports !== "boolean"
) {
throw new ValidationError("uploadExports must be a boolean");
}
}
function validatePSDInput(psd: string | Buffer): void {
if (!psd) {
throw new ValidationError("PSD input is required");
}
if (typeof psd === "string") {
if (!isValidUrl(psd)) {
throw new ValidationError("PSD URL must be a valid URL");
}
} else if (!(psd instanceof Buffer)) {
throw new ValidationError("PSD must be a URL string or Buffer");
} else if (psd.length === 0) {
throw new ValidationError("PSD Buffer cannot be empty");
}
}
export function validateSmartObjects(
replacements: SmartObjectReplacement[]
): void {
if (!Array.isArray(replacements)) {
throw new ValidationError("Replacements must be an array");
}
if (replacements.length === 0) {
throw new ValidationError(
"At least one smart object replacement is required"
);
}
for (const [index, replacement] of replacements.entries()) {
if (!replacement.name || typeof replacement.name !== "string") {
throw new ValidationError(
`Replacement ${index}: name is required and must be a string`
);
}
if (!replacement.image) {
throw new ValidationError(`Replacement ${index}: image is required`);
}
if (
typeof replacement.image === "string" &&
!isValidUrl(replacement.image)
) {
throw new ValidationError(`Replacement ${index}: image URL is invalid`);
}
if (replacement.image instanceof Buffer && replacement.image.length === 0) {
throw new ValidationError(`Replacement ${index}: image Buffer is empty`);
}
if (
replacement.fillMode &&
!["fit", "fill", "stretch"].includes(replacement.fillMode)
) {
throw new ValidationError(
`Replacement ${index}: fillMode must be 'fit', 'fill', or 'stretch'`
);
}
if (replacement.printArea) {
validatePrintArea(replacement.printArea, index);
}
}
}
export function validateExportOptions(exports: ExportOptions[]): void {
if (!Array.isArray(exports)) {
throw new ValidationError("Exports must be an array");
}
const validFormats = ["png", "jpg", "jpeg", "webp", "pdf"];
for (const [index, exportOption] of exports.entries()) {
if (!exportOption.format || !validFormats.includes(exportOption.format)) {
throw new ValidationError(
`Export ${index}: format must be one of ${validFormats.join(", ")}`
);
}
if (exportOption.quality !== undefined) {
if (
typeof exportOption.quality !== "number" ||
exportOption.quality < 0 ||
exportOption.quality > 100
) {
throw new ValidationError(
`Export ${index}: quality must be a number between 0 and 100`
);
}
}
if (exportOption.width !== undefined) {
if (typeof exportOption.width !== "number" || exportOption.width <= 0) {
throw new ValidationError(
`Export ${index}: width must be a positive number`
);
}
}
if (exportOption.height !== undefined) {
if (typeof exportOption.height !== "number" || exportOption.height <= 0) {
throw new ValidationError(
`Export ${index}: height must be a positive number`
);
}
}
}
}
function validatePrintArea(printArea: any, index: number): void {
const requiredFields = ["x", "y", "width", "height"];
for (const field of requiredFields) {
if (typeof printArea[field] !== "number") {
throw new ValidationError(
`Replacement ${index}: printArea.${field} must be a number`
);
}
if (printArea[field] < 0) {
throw new ValidationError(
`Replacement ${index}: printArea.${field} must be non-negative`
);
}
}
if (printArea.width === 0) {
throw new ValidationError(
`Replacement ${index}: printArea.width must be greater than 0`
);
}
if (printArea.height === 0) {
throw new ValidationError(
`Replacement ${index}: printArea.height must be greater than 0`
);
}
}
function isValidUrl(url: string): boolean {
try {
new URL(url);
return true;
} catch {
return false;
}
}