All files / src/lib/monitoring schemas.ts

100% Statements 86/86
100% Branches 11/11
100% Functions 2/2
100% Lines 86/86

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 871x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 66x 66x 4x 13x 13x 5x 5x 3x 5x 1x 1x 5x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/**
 * Monitoring Schemas
 *
 * Zod validation schemas for monitoring-related payloads.
 * Provides strict validation with size limits to prevent abuse.
 */
 
import { z } from 'zod';
 
/**
 * Maximum allowed sizes for error payload fields
 */
export const ERROR_PAYLOAD_LIMITS = {
  message: 2000,
  stack: 10000,
  componentStack: 5000,
  url: 2000,
  userAgent: 500,
  metadataKeys: 20,
  metadataValueLength: 1000,
} as const;
 
/**
 * Validate metadata object
 * Checks key count and string value lengths
 */
function validateMetadata(val: Record<string, unknown> | undefined): boolean {
  if (!val) return true;
  const keys = Object.keys(val);
  if (keys.length > ERROR_PAYLOAD_LIMITS.metadataKeys) return false;
  for (const value of Object.values(val)) {
    if (
      typeof value === 'string' &&
      value.length > ERROR_PAYLOAD_LIMITS.metadataValueLength
    ) {
      return false;
    }
  }
  return true;
}
 
/**
 * Schema for client-side error reports
 * Validates and constrains all fields to prevent abuse
 */
export const clientErrorSchema = z
  .object({
    message: z.string().max(ERROR_PAYLOAD_LIMITS.message).optional(),
    stack: z.string().max(ERROR_PAYLOAD_LIMITS.stack).optional(),
    componentStack: z.string().max(ERROR_PAYLOAD_LIMITS.componentStack).optional(),
    url: z.string().max(ERROR_PAYLOAD_LIMITS.url).optional(),
    userAgent: z.string().max(ERROR_PAYLOAD_LIMITS.userAgent).optional(),
    errorId: z.string().uuid().optional(),
    metadata: z.record(z.string(), z.unknown()).optional(),
  })
  .strict()
  .refine(
    (data) => validateMetadata(data.metadata),
    {
      message: `Metadata exceeds limits: max ${ERROR_PAYLOAD_LIMITS.metadataKeys} keys, max ${ERROR_PAYLOAD_LIMITS.metadataValueLength} chars per string value`,
      path: ['metadata'],
    }
  );
 
export type ClientErrorPayload = z.infer<typeof clientErrorSchema>;
 
/**
 * Schema for error resolution requests
 */
export const errorResolveSchema = z.object({
  fingerprint: z.string().min(1).max(64),
  status: z.enum(['resolved', 'ignored', 'unresolved']),
  resolution: z.string().max(1000).optional(),
});
 
export type ErrorResolvePayload = z.infer<typeof errorResolveSchema>;
 
/**
 * Schema for bulk error resolution
 */
export const bulkErrorResolveSchema = z.object({
  fingerprints: z.array(z.string().min(1).max(64)).min(1).max(100),
  status: z.enum(['resolved', 'ignored']),
});
 
export type BulkErrorResolvePayload = z.infer<typeof bulkErrorResolveSchema>;