All files / lib/attachment-archive attachment-errors.ts

100% Statements 31/31
94.87% Branches 37/39
100% Functions 7/7
100% Lines 31/31

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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115    6x 6x   6x                               61x 4x     57x       51x   51x           51x                 51x                       32x                 18x 18x     18x     54x     18x 9x     18x       30x 30x     30x         30x 20x     30x       6x 2x     4x 4x 3x 3x     4x    
import { isLegacyUploadBucket } from "./bucket-routing";
 
const NOT_FOUND_ERROR_NAMES = new Set(["NoSuchBucket", "NoSuchKey", "NotFound"]);
const ACCESS_DENIED_ERROR_NAMES = new Set(["AccessDenied", "Forbidden", "InvalidAccessKeyId"]);
 
const NOT_FOUND_MESSAGE_FRAGMENTS = [
  "does not exist",
  "NoSuchBucket",
  "NoSuchKey",
  "not found",
  "The specified bucket does not exist",
  "The specified key does not exist",
];
 
type AttachmentErrorInfo = {
  errorName: string;
  message: string;
  statusCode?: number;
};
 
export function getAttachmentErrorMessage(error: unknown) {
  if (error instanceof Error) {
    return error.message;
  }
 
  return String(error);
}
 
function getAttachmentErrorInfo(error: unknown): AttachmentErrorInfo {
  const message = getAttachmentErrorMessage(error);
  const errorName =
    (typeof error === "object" &&
      error &&
      ("name" in error || "Code" in error || "code" in error) &&
      String((error as any).name || (error as any).Code || (error as any).code)) ||
    "";
  const statusCode =
    typeof error === "object" &&
    error &&
    "$metadata" in error &&
    (error as any).$metadata &&
    typeof (error as any).$metadata === "object" &&
    "httpStatusCode" in (error as any).$metadata
      ? (error as any).$metadata.httpStatusCode
      : undefined;
 
  return {
    errorName,
    message,
    statusCode,
  };
}
 
function logAttachmentErrorClassification(
  label: string,
  info: AttachmentErrorInfo,
  bucket?: string,
) {
  console.warn(`[${label}]`, {
    bucket,
    errorName: info.errorName || undefined,
    statusCode: info.statusCode,
    message: info.message,
  });
}
 
export function isAttachmentNotFoundError(error: unknown) {
  const info = getAttachmentErrorInfo(error);
  const normalizedMessage = info.message.toLowerCase();
 
  const notFound =
    info.statusCode === 404 ||
    NOT_FOUND_ERROR_NAMES.has(info.errorName) ||
    NOT_FOUND_MESSAGE_FRAGMENTS.some((fragment) =>
      normalizedMessage.includes(fragment.toLowerCase()),
    );
 
  if (notFound) {
    logAttachmentErrorClassification("MISSING_ATTACHMENT_ERROR", info);
  }
 
  return notFound;
}
 
export function isAttachmentAccessDeniedError(error: unknown) {
  const info = getAttachmentErrorInfo(error);
  const normalizedMessage = info.message.toLowerCase();
 
  const accessDenied =
    info.statusCode === 403 ||
    ACCESS_DENIED_ERROR_NAMES.has(info.errorName) ||
    normalizedMessage.includes("access denied") ||
    normalizedMessage.includes("forbidden");
 
  if (accessDenied) {
    logAttachmentErrorClassification("ACCESS_DENIED_ATTACHMENT_ERROR", info);
  }
 
  return accessDenied;
}
 
export function isLegacyAttachmentUnavailableError(bucket: string, error: unknown) {
  if (!isLegacyUploadBucket(bucket)) {
    return false;
  }
 
  const unavailable = isAttachmentNotFoundError(error) || isAttachmentAccessDeniedError(error);
  if (unavailable) {
    const info = getAttachmentErrorInfo(error);
    logAttachmentErrorClassification("LEGACY_ATTACHMENT_UNAVAILABLE", info, bucket);
  }
 
  return unavailable;
}