antiwork/gumroad · error · ResponseError

typia.assert<{ error: string }>(json).error

Error message

typia.assert<{ error: string }>(json).error

What it means

resendToNonOpeners POSTs to the non_opener_resend path; on non-ok it runs typia.assert<{ error: string }>(json) and throws ResponseError with the server's error text. Two distinct failures live on this line: the expected ResponseError when the server sent { error }, and a typia TypeError thrown from inside the assert when the failure body doesn't match that exact shape — an HTML error page, an empty body, or a body keyed message instead of error.

Source

Thrown at app/javascript/data/installments.ts:176

  });

  if (!response.ok) throw new ResponseError();
  // `count` is null when the audience is too large to count within the request; the
  // resend itself still works (recipients are resolved in a background job).
  return typia.assert<{ count: number | null; recently_resent: boolean; audience_filtered_out: boolean }>(
    await response.json(),
  );
}

export async function resendToNonOpeners(externalId: string) {
  const response = await request({
    method: "POST",
    accept: "json",
    url: Routes.internal_installment_non_opener_resend_path(externalId),
  });

  const json: unknown = await response.json();
  if (!response.ok) throw new ResponseError(typia.assert<{ error: string }>(json).error);
  return typia.assert<{ success: boolean }>(json);
}

export async function previewInstallment(externalId: string) {
  const response = await request({
    method: "POST",
    accept: "json",
    url: Routes.internal_installment_preview_email_path(externalId),
  });

  if (!response.ok) throw new ResponseError(typia.assert<{ message: string }>(await response.json()).message);
}

View on GitHub (pinned to afeacbd394)

Solutions

  1. Inspect the failing response body in the Network tab — does it actually contain { error: string }?
  2. Make the extraction defensive: parse once, type-guard the error key, and fall back to a generic message instead of typia.assert on an error path.
  3. Align the server: on failure render json: { error: ... } with a matching status.
  4. If the failure is 429, note request() already raises RateLimitError before your handler — don't re-parse that body.

Example fix

// before
if (!response.ok) throw new ResponseError(typia.assert<{ error: string }>(json).error);

// after
if (!response.ok) {
  const message =
    typeof json === 'object' && json !== null && 'error' in json && typeof (json as { error: unknown }).error === 'string'
      ? (json as { error: string }).error
      : 'Could not resend to non-openers.';
  throw new ResponseError(message);
}
Defensive patterns

Strategy: try-catch

Type guard

const hasErrorBody = (json: unknown): json is { error: string } =>
  typeof json === 'object' && json !== null && 'error' in json && typeof (json as { error: unknown }).error === 'string';

Try / catch

try {
  const result = await resendToNonOpeners(externalId);
} catch (e) {
  if (!(e instanceof ResponseError)) {
    // typia TypeError: the failure body wasn't { error: string } — log it whole
    console.error('Unexpected error body', e);
    showError('Could not resend to non-openers.');
  } else {
    showError(e.message);
  }
}

Prevention

When it happens

Trigger: A 4xx whose body is JSON { error } → ResponseError with that message. A proxy or Rails exception page (HTML), an empty 4xx body, or a body like { message: '...' } → typia TypeError (invalid type at $input.error) instead of the intended ResponseError.

Common situations: A rate-limited internal endpoint whose 429 body has no error key; a controller rescue block returning head :no_content on failure; an environment behind an HTML error proxy where the JSON contract breaks only on the error path.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/3f40c3b99981369b. Report an issue: GitHub.