All files / packages/sds-customization-compliance/src/client fetch-json.ts

100% Statements 9/9
100% Branches 6/6
100% Functions 1/1
100% Lines 8/8

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                                                  1x                                   7x 7x 5x 4x 1x   3x   3x            
/**
 * Status of a fetched JSON resource. The four states cover every
 * decision a host-side panel needs to make:
 *
 *   - `loading`  — initial / in-flight; render a spinner placeholder.
 *   - `ready`    — request resolved with parsed JSON in `data`.
 *   - `missing`  — server returned 404; the artifact does not exist
 *                  yet (e.g. compliance reports for a component the
 *                  CLI has not seen). Distinct from `error` so hosts
 *                  can render a remediation hint instead of a banner.
 *   - `error`    — non-OK response, network failure, or JSON parse
 *                  error. `error` carries a human-readable message.
 */
export type FetchStatus = 'loading' | 'ready' | 'missing' | 'error';
 
/** Discriminated union return shape; narrow on `status` at the call site. */
export type FetchResult<T> =
  | { status: 'ready'; data: T }
  | { status: 'missing' }
  | { status: 'error'; error: string };
 
/**
 * Initial state to seed `useState` calls. Exported so consumers don't
 * have to magic-string the literal each time.
 */
export const FETCH_LOADING = { status: 'loading' as const };
 
/**
 * Fetch a JSON resource and resolve to a `FetchResult`. Never rejects —
 * exceptions and non-OK responses fold into `{ status: 'error', … }`
 * so React render trees can switch off `status` without try/catch at
 * the call site.
 *
 * Generic over the JSON payload type. Pass a type argument when the
 * caller has a stable schema for the resource:
 *
 *   const r = await fetchJson<ComponentComplianceReport>(url);
 *
 * `cache: 'no-cache'` is hard-coded because every known consumer is a
 * dev-loop panel that re-fetches on file change. If a future caller
 * needs caching, parameterize the second arg.
 */
export async function fetchJson<T>(url: string): Promise<FetchResult<T>> {
  try {
    const res = await fetch(url, { cache: 'no-cache' });
    if (res.status === 404) return { status: 'missing' };
    if (!res.ok) {
      return { status: 'error', error: `HTTP ${res.status} while loading ${url}.` };
    }
    return { status: 'ready', data: (await res.json()) as T };
  } catch (e) {
    return {
      status: 'error',
      error: e instanceof Error ? e.message : String(e),
    };
  }
}