UNPKG

@effect-ts/system

Version:

Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.

332 lines (295 loc) 7.84 kB
// ets_tracing: off import * as A from "../../Collections/Immutable/Array/index.js" import type { FiberID } from "../../Fiber/id.js" import type { Trace } from "../../Fiber/tracing.js" import { prettyTrace } from "../../Fiber/tracing.js" import { pipe } from "../../Function/index.js" import * as S from "../../IO/index.js" import * as O from "../../Option/index.js" import type { Cause } from "../cause.js" // // @category PrettyPrint // export type Segment = Sequential | Parallel | Failure export type Step = Parallel | Failure export interface Failure { _tag: "Failure" lines: string[] } export interface Parallel { _tag: "Parallel" all: Sequential[] } export interface Sequential { _tag: "Sequential" all: Step[] } export function Failure(lines: string[]): Failure { return { _tag: "Failure", lines } } export function Sequential(all: Step[]): Sequential { return { _tag: "Sequential", all } } export function Parallel(all: Sequential[]): Parallel { return { _tag: "Parallel", all } } export type TraceRenderer = (_: Trace) => string export interface Renderer<E = unknown> { renderError: (error: E) => string[] renderTrace: TraceRenderer renderUnknown: (error: unknown) => string[] } export function headTail<A>(a: readonly A[] & { 0: A }): [A, A[]] { const x = [...a] // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const head = x.shift()! return [head, x] } export function prefixBlock( values: readonly string[], p1: string, p2: string ): string[] { return A.isNonEmpty(values) ? pipe(headTail(values), ([head, tail]) => [ `${p1}${head}`, ...tail.map((_) => `${p2}${_}`) ]) : [] } export function renderInterrupt( fiberId: FiberID, trace: O.Option<Trace>, traceRenderer: TraceRenderer ): Sequential { return Sequential([ Failure([ `An interrupt was produced by #${fiberId.seqNumber}.`, "", ...renderTrace(trace, traceRenderer) ]) ]) } export function renderError(error: Error): string[] { return lines(error.stack ? error.stack : String(error)) } export function renderDie( error: string[], trace: O.Option<Trace>, traceRenderer: TraceRenderer ): Sequential { return Sequential([ Failure([ "An unchecked error was produced.", "", ...error, ...renderTrace(trace, traceRenderer) ]) ]) } export function renderFail( error: string[], trace: O.Option<Trace>, traceRenderer: TraceRenderer ): Sequential { return Sequential([ Failure([ "A checked error was not handled.", "", ...error, ...renderTrace(trace, traceRenderer) ]) ]) } export function lines(s: string) { return s.split("\n").map((s) => s.replace("\r", "")) as string[] } export function linearSegments<E>( cause: Cause<E>, renderer: Renderer<E> ): S.IO<Step[]> { return S.gen(function* (_) { switch (cause._tag) { case "Then": { return [ ...(yield* _(linearSegments(cause.left, renderer))), ...(yield* _(linearSegments(cause.right, renderer))) ] } default: { return (yield* _(causeToSequential(cause, renderer))).all } } }) } export function parallelSegments<E>( cause: Cause<E>, renderer: Renderer<E> ): S.IO<Sequential[]> { return S.gen(function* (_) { switch (cause._tag) { case "Both": { return [ ...(yield* _(parallelSegments(cause.left, renderer))), ...(yield* _(parallelSegments(cause.right, renderer))) ] } default: { return [yield* _(causeToSequential(cause, renderer))] } } }) } export function renderToString(u: unknown): string { if ( typeof u === "object" && u != null && "toString" in u && typeof u["toString"] === "function" && u["toString"] !== Object.prototype.toString ) { return u["toString"]() } return JSON.stringify(u, null, 2) } export function causeToSequential<E>( cause: Cause<E>, renderer: Renderer<E> ): S.IO<Sequential> { return S.gen(function* (_) { switch (cause._tag) { case "Empty": { return Sequential([]) } case "Fail": { return renderFail( renderer.renderError(cause.value), O.none, renderer.renderTrace ) } case "Die": { return renderDie( renderer.renderUnknown(cause.value), O.none, renderer.renderTrace ) } case "Interrupt": { return renderInterrupt(cause.fiberId, O.none, renderer.renderTrace) } case "Then": { return Sequential(yield* _(linearSegments(cause, renderer))) } case "Both": { return Sequential([Parallel(yield* _(parallelSegments(cause, renderer)))]) } case "Traced": { switch (cause.cause._tag) { case "Fail": { return renderFail( renderer.renderError(cause.cause.value), O.some(cause.trace), renderer.renderTrace ) } case "Die": { return renderDie( renderer.renderUnknown(cause.cause.value), O.some(cause.trace), renderer.renderTrace ) } case "Interrupt": { return renderInterrupt( cause.cause.fiberId, O.some(cause.trace), renderer.renderTrace ) } default: { return Sequential([ Failure([ "An error was rethrown with a new trace.", ...renderTrace(O.some(cause.trace), renderer.renderTrace) ]), ...(yield* _(causeToSequential(cause.cause, renderer))).all ]) } } } } }) } export function renderTrace(o: O.Option<Trace>, renderTrace: TraceRenderer) { return o._tag === "None" ? [] : lines(renderTrace(o.value)) } export function times(s: string, n: number) { let h = "" for (let i = 0; i < n; i += 1) { h += s } return h } export function format(segment: Segment): readonly string[] { switch (segment._tag) { case "Failure": { return prefixBlock(segment.lines, "─", " ") } case "Parallel": { return [ times("══╦", segment.all.length - 1) + "══╗", ...A.reduceRight_<Sequential, string[]>(segment.all, [], (current, acc) => [ ...prefixBlock(acc, " ║", " ║"), ...prefixBlock(format(current), " ", " ") ]) ] } case "Sequential": { return A.chain_(segment.all, (seg) => [ "║", ...prefixBlock(format(seg), "╠", "║"), "▼" ]) } } } export function prettyLines<E>(cause: Cause<E>, renderer: Renderer<E>) { return S.gen(function* (_) { const s = yield* _(causeToSequential(cause, renderer)) if (s.all.length === 1 && s.all[0] && s.all[0]._tag === "Failure") { return s.all[0].lines } return O.getOrElse_(A.updateAt_(format(s), 0, "╥"), (): string[] => []) }) } export function prettyM<E1>(cause: Cause<E1>, renderer: Renderer<E1>) { return S.gen(function* (_) { const lines = yield* _(prettyLines(cause, renderer)) return `\n${lines.join("\n")}` }) } export function defaultErrorToLines(error: unknown) { return error instanceof Error ? renderError(error) : lines(renderToString(error)) } export const defaultRenderer: Renderer = { renderError: defaultErrorToLines, renderTrace: prettyTrace, renderUnknown: defaultErrorToLines } /** * Returns a `String` with the cause pretty-printed. */ export const pretty = <E1>( cause: Cause<E1>, renderer: Renderer<E1> = defaultRenderer ) => S.run(prettyM(cause, renderer))