< Summary

Information
Class: problem.ts
Assembly: app.api
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/api/problem.ts
Tag: 58_25416222083
Line coverage
96%
Covered lines: 30
Uncovered lines: 1
Coverable lines: 31
Total lines: 80
Line coverage: 96.7%
Branch coverage
93%
Covered branches: 28
Total branches: 30
Branch coverage: 93.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/api/problem.ts

#LineLine coverage
 1import type { components } from "~/api/types";
 2
 3type ProblemDetails = components["schemas"]["Microsoft.AspNetCore.Mvc.ProblemDetails"];
 4type ValidationProblemDetails =
 5  components["schemas"]["Microsoft.AspNetCore.Http.HttpValidationProblemDetails"];
 6
 7export type ProblemBody = ProblemDetails | ValidationProblemDetails;
 8
 9export class ApiProblemError extends Error {
 10  readonly status: number;
 11  readonly title: string;
 12  readonly detail?: string;
 13  readonly instance?: string;
 14  readonly type?: string;
 15  readonly errors?: Record<string, string[]>;
 16  readonly requestId?: string;
 17  readonly traceId?: string;
 18  readonly raw: ProblemBody;
 19
 1820  constructor(status: number, body: ProblemBody) {
 1821    const title = body.title ?? `HTTP ${status}`;
 1822    super(body.detail ?? title);
 1823    this.name = "ApiProblemError";
 1824    this.status = status;
 1825    this.title = title;
 1826    this.detail = body.detail ?? undefined;
 1827    this.instance = body.instance ?? undefined;
 1828    this.type = body.type ?? undefined;
 1829    this.errors = (body as ValidationProblemDetails).errors ?? undefined;
 1830    const ext = body as { requestId?: unknown; traceId?: unknown };
 1831    this.requestId = typeof ext.requestId === "string" ? ext.requestId : undefined;
 1832    this.traceId = typeof ext.traceId === "string" ? ext.traceId : undefined;
 1833    this.raw = body;
 34  }
 35}
 36
 1837export function isApiProblem(value: unknown): value is ApiProblemError {
 1838  return value instanceof ApiProblemError;
 39}
 40
 41interface RouteErrorResponseLike {
 42  status: number;
 43  statusText?: string;
 44  data?: unknown;
 45}
 46
 647function isRouteErrorResponseLike(value: unknown): value is RouteErrorResponseLike {
 648  return (
 49    value != null &&
 50    typeof value === "object" &&
 51    typeof (value as { status?: unknown }).status === "number" &&
 52    "data" in value
 53  );
 54}
 55
 56/**
 57 * Coerce any thrown value into an ApiProblemError when possible, regardless of
 58 * how it survived (or did not survive) RR7's SSR serialization. Use this in
 59 * route ErrorBoundaries — the typed wrapper throws a `Response` for HTTP
 60 * failures, which RR7 hands back as a route ErrorResponse with `data` holding
 61 * the parsed ProblemBody.
 62 */
 763export function asApiProblem(error: unknown): ApiProblemError | null {
 764  if (isApiProblem(error)) return error;
 665  if (isRouteErrorResponseLike(error)) {
 366    const data = error.data;
 367    let body: ProblemBody | null = null;
 368    if (data && typeof data === "object") {
 269      body = data as ProblemBody;
 170    } else if (typeof data === "string" && data.length > 0) {
 171      try {
 172        body = JSON.parse(data) as ProblemBody;
 73      } catch {
 074        body = null;
 75      }
 76    }
 377    if (body) return new ApiProblemError(error.status, body);
 78  }
 379  return null;
 80}