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    2x 2x   2x                               23x 3x     20x       18x   18x           18x                 18x                       11x                 11x 11x     11x     30x     11x 6x     11x       6x 6x     6x         6x 4x     6x       4x 2x     2x 2x 1x 1x     2x    
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;
}