Assist_Design/docs/_archive/VALIDATION-FINAL-FIXES.md

11 KiB

Validation Cleanup - Final Fixes

Summary

All remaining validation issues have been fixed! This document details the additional improvements made after the initial cleanup.


Fixes Completed

1. Removed validateSignupData() Wrapper

File: apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts

Before (Lines 476-483):

private validateSignupData(signupData: SignupRequest) {
  const validation = signupRequestSchema.safeParse(signupData);
  if (!validation.success) {
    const message = validation.error.issues.map(issue => issue.message).join(". ") || "Invalid signup data";
    throw new BadRequestException(message);
  }
}

// Called as:
this.validateSignupData(signupData);

After:

// Validate signup data using schema (throws on validation error)
signupRequestSchema.parse(signupData);

Impact:

  • Removed 7 lines of unnecessary wrapper code
  • Direct schema usage is clearer
  • Schema throws with proper error messages automatically

2. Simplified validateRequestFormat() in OrderValidator

File: apps/bff/src/modules/orders/services/order-validator.service.ts

Before (Lines 42-89, ~48 lines):

validateRequestFormat(rawBody: unknown): CreateOrderRequest {
  try {
    const validationResult = createOrderRequestSchema.safeParse(rawBody);
    
    if (!validationResult.success) {
      const errorMessages = validationResult.error.issues.map(issue => {
        const path = issue.path.join(".");
        return path ? `${path}: ${issue.message}` : issue.message;
      });
      this.logger.error({ errors: errorMessages.length }, "Zod validation failed");
      throw new BadRequestException({
        message: "Order validation failed",
        errors: errorMessages,
        statusCode: 400,
      });
    }
    
    const validatedData = validationResult.data;
    const validatedBody: CreateOrderRequest = validatedData;
    
    return validatedBody;
  } catch (error) {
    // ...
  }
}

After (~40 lines):

validateRequestFormat(rawBody: unknown): CreateOrderRequest {
  try {
    // Use direct Zod validation with .parse() - throws ZodError on failure
    const validatedBody = createOrderRequestSchema.parse(rawBody);
    return validatedBody;
  } catch (error) {
    if (error instanceof ZodError) {
      const errorMessages = error.issues.map(issue => {
        const path = issue.path.join(".");
        return path ? `${path}: ${issue.message}` : issue.message;
      });
      this.logger.error({ errors: errorMessages }, "Zod validation failed");
      throw new BadRequestException({
        message: "Order validation failed",
        errors: errorMessages,
        statusCode: 400,
      });
    }
    throw error;
  }
}

Impact:

  • 8 lines shorter, clearer flow
  • Uses .parse() instead of unnecessary safeParse + manual check
  • Better error handling with explicit ZodError catch

3. Added Common Validation Schemas

File: packages/domain/common/validation.ts

Added:

/**
 * Required non-empty string schema (trimmed)
 * Use for any string that must have a value
 */
export const requiredStringSchema = z.string().min(1, "This field is required").trim();

/**
 * Salesforce ID schema (18 characters, alphanumeric)
 * Used for Account IDs, Order IDs, etc.
 */
export const salesforceIdSchema = z
  .string()
  .length(18, "Salesforce ID must be 18 characters")
  .regex(/^[A-Za-z0-9]+$/, "Salesforce ID must be alphanumeric")
  .trim();

/**
 * Customer number / account number schema
 * Generic schema for customer/account identifiers
 */
export const customerNumberSchema = z.string().min(1, "Customer number is required").trim();

Impact:

  • Reusable schemas for common validation patterns
  • Consistent validation across the codebase
  • Single source of truth for ID formats

4. Fixed SalesforceAccountService Manual Validation

File: apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts

Fixed 5 methods:

Method 1: findByCustomerNumber()

Before:

if (!customerNumber?.trim()) throw new Error("Customer number is required");
const result = await this.connection.query(
  `SELECT Id FROM Account WHERE SF_Account_No__c = '${this.safeSoql(customerNumber.trim())}'`
);

After:

const validCustomerNumber = customerNumberSchema.parse(customerNumber);
const result = await this.connection.query(
  `SELECT Id FROM Account WHERE SF_Account_No__c = '${this.safeSoql(validCustomerNumber)}'`
);

Method 2: getAccountDetails()

Before:

if (!accountId?.trim()) throw new Error("Account ID is required");

After:

const validAccountId = salesforceIdSchema.parse(accountId);

Method 3: updateWhAccount()

Before:

if (!accountId?.trim()) throw new Error("Account ID is required");
if (!whAccountValue?.trim()) throw new Error("WH Account value is required");

After:

const validAccountId = salesforceIdSchema.parse(accountId);
const validWhAccount = requiredStringSchema.parse(whAccountValue);

Method 4: upsert()

Before:

if (!accountData.name?.trim()) throw new Error("Account name is required");

After:

const validName = requiredStringSchema.parse(accountData.name);

Method 5: getById()

Before:

if (!accountId?.trim()) throw new Error("Account ID is required");

After:

const validAccountId = salesforceIdSchema.parse(accountId);

Impact:

  • Replaced 5 manual validation checks with schemas
  • Consistent validation pattern across all methods
  • Better type safety and error messages

5. Fixed SOQL Utility Manual Validation

File: apps/bff/src/integrations/salesforce/utils/soql.util.ts

Function 1: assertSalesforceId()

Before:

const SALESFORCE_ID_REGEX = /^[a-zA-Z0-9]{15,18}$/u;

export function assertSalesforceId(value: unknown, fieldName: string): string {
  if (typeof value !== "string" || !SALESFORCE_ID_REGEX.test(value)) {
    throw new Error(`Invalid Salesforce id for ${fieldName}`);
  }
  return value;
}

After:

import { z } from "zod";

const salesforceIdSchema = z
  .string()
  .regex(/^[a-zA-Z0-9]{15,18}$/, "Invalid Salesforce ID format")
  .trim();

export function assertSalesforceId(value: unknown, fieldName: string): string {
  try {
    return salesforceIdSchema.parse(value);
  } catch {
    throw new Error(`Invalid Salesforce id for ${fieldName}`);
  }
}

Function 2: buildInClause()

Before:

const sanitized = values.map(value => {
  if (typeof value !== "string" || value.trim() === "") {
    throw new Error(`Invalid value provided for ${contextLabel} IN clause`);
  }
  return `'${sanitizeSoqlLiteral(value)}'`;
});

After:

const nonEmptyStringSchema = z.string().min(1, "Value cannot be empty").trim();

const sanitized = values.map(value => {
  try {
    const validValue = nonEmptyStringSchema.parse(value);
    return `'${sanitizeSoqlLiteral(validValue)}'`;
  } catch {
    throw new Error(`Invalid value provided for ${contextLabel} IN clause`);
  }
});

Impact:

  • SQL injection prevention now uses schema validation
  • More robust validation for security-critical functions
  • Consistent with rest of codebase

📊 Summary Statistics

Code Reduction

Item Lines Removed
validateSignupData() wrapper 7
Simplified validateRequestFormat() 8
Manual validation checks replaced 10
Total 25 lines

Schemas Added

Schema Purpose
requiredStringSchema Non-empty strings
salesforceIdSchema Salesforce IDs (18 chars)
customerNumberSchema Customer/account numbers

Files Modified

  1. apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts
  2. apps/bff/src/modules/orders/services/order-validator.service.ts
  3. packages/domain/common/validation.ts
  4. apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts
  5. apps/bff/src/integrations/salesforce/utils/soql.util.ts

Methods Fixed

  • 5 methods in SalesforceAccountService
  • 2 utility functions in soql.util.ts
  • 1 method in OrderValidator
  • 1 method in SignupWorkflowService

Total: 9 methods improved


🎯 Validation Patterns Now Established

DO: Use Schema Directly

// Good - direct schema usage
const validId = salesforceIdSchema.parse(accountId);

DO: Use .parse() for Throwing Validation

// Good - throws ZodError with detailed info
const validated = createOrderRequestSchema.parse(rawBody);

DON'T: Use safeParse Then Manual Check

// Bad - unnecessary complexity
const result = schema.safeParse(data);
if (!result.success) {
  throw new Error(...);
}
return result.data;

DON'T: Create Wrapper Methods

// Bad - unnecessary wrapper
private validateX(data: X) {
  schema.parse(data);
}

DON'T: Manual Type/Format Checks

// Bad - should use schema
if (!value?.trim()) throw new Error("Required");
if (typeof value !== "string") throw new Error("Invalid");

🏆 Benefits Achieved

1. Consistency

  • All validation now uses Zod schemas
  • No more mixed patterns (manual checks + schemas)
  • Clear, predictable validation across codebase

2. Maintainability

  • Validation rules defined once in schemas
  • Easy to update validation rules
  • Less code to maintain

3. Type Safety

  • Schema validation ensures runtime type safety
  • TypeScript types inferred from schemas
  • Catch type issues early

4. Better Errors

  • Zod provides detailed, helpful error messages
  • Path information for nested validation failures
  • Consistent error format

5. Security

  • SQL injection prevention uses schemas
  • Consistent validation for security-critical inputs
  • Less room for validation bypass

Remaining Items (Acceptable)

Files with Manual Checks (Not Issues):

  1. sso.util.ts - Sanitization logic (not validation)

    • Security-related path sanitization
    • Returns safe fallback, doesn't throw
    • Acceptable as-is
  2. Length checks on arrays - Business logic

    • if (array.length === 0) for empty checks
    • Not validation, just conditional logic
    • Acceptable as-is
  3. Type guards - TypeScript patterns

    • typeof x === "object" for type narrowing
    • Part of TypeScript type system
    • Acceptable as-is

🎉 Complete!

All validation wrapper functions have been removed or simplified. The codebase now follows a consistent, schema-first validation approach.

Key Achievements:

  • Zero unnecessary wrapper functions
  • Direct schema usage throughout
  • Reusable validation schemas in domain layer
  • Consistent patterns across all services
  • Better error messages and type safety
  • ~25 additional lines of code removed
  • 9 methods improved
  • 5 files cleaned up

The validation cleanup is now complete! 🎊