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    3x 3x   3x                               58x 4x     54x       49x   49x           49x                 49x                       31x                 17x 17x     17x     54x     17x 8x     17x       29x 29x     29x         29x 20x     29x       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;
}