1/**
2 * Normalized errors for platform APIs.
3 *
4 * Platform services such as file systems, terminals, and sockets use
5 * `PlatformError` to report host-level failures in a consistent shape. The
6 * wrapper records whether the problem came from an invalid argument or from the
7 * operating system, while preserving useful details such as the module, method,
8 * path, descriptor, description, and original cause when available.
9 *
10 * @since 4.0.0
11 */
12import * as Data from "./Data.ts"
13import * as Predicate from "./Predicate.ts"
14
15const TypeId = "~effect/PlatformError"
16
17/**
18 * Error data for an invalid argument passed to a platform API.
19 *
20 * **When to use**
21 *
22 * Use when you need to model caller input rejected before a platform operation
23 * runs, including invalid-argument reason data.
24 *
25 * **Details**
26 *
27 * The error records the module and method that rejected the argument, with an
28 * optional description and cause. It is usually wrapped in `PlatformError`.
29 *
30 * @see {@link badArgument} for creating a wrapped `PlatformError` whose reason is `BadArgument`
31 * @see {@link SystemError} for failures reported by the host platform or operating system
32 * @see {@link PlatformError} for the wrapper used by most platform APIs
33 *
34 * @category errors
35 * @since 4.0.0
36 */
37export class BadArgument extends Data.TaggedError("BadArgument")<{
38 module: string
39 method: string
40 description?: string | undefined
41 cause?: unknown
42}> {
43 /**
44 * Formats the module, method, and optional description that rejected the argument.
45 *
46 * **When to use**
47 *
48 * Use to read the formatted error message for a rejected platform argument.
49 *
50 * @since 4.0.0
51 */
52 override get message(): string {
53 return `${this.module}.${this.method}${this.description ? `: ${this.description}` : ""}`
54 }
55}
56
57/**
58 * Normalized category for failures reported by platform or system operations.
59 *
60 * **When to use**
61 *
62 * Use to type or match the normalized `_tag` on `SystemError` values reported
63 * by platform operations.
64 *
65 * **Details**
66 *
67 * The tags group lower-level platform errors into a stable set such as
68 * `NotFound`, `PermissionDenied`, `TimedOut`, and `Unknown`.
69 *
70 * @see {@link SystemError} for the error data that carries this tag on its `_tag` field
71 * @see {@link systemError} for creating a `PlatformError` from a system failure with one of these tags
72 *
73 * @category errors
74 * @since 4.0.0
75 */
76export type SystemErrorTag =
77 | "AlreadyExists"
78 | "BadResource"
79 | "Busy"
80 | "InvalidData"
81 | "NotFound"
82 | "PermissionDenied"
83 | "TimedOut"
84 | "UnexpectedEof"
85 | "Unknown"
86 | "WouldBlock"
87 | "WriteZero"
88
89/**
90 * Error data for a platform or system operation failure.
91 *
92 * **When to use**
93 *
94 * Use when you need normalized reason data for a platform or system operation
95 * failure, including the operation details.
96 *
97 * **Details**
98 *
99 * The error records a normalized `_tag`, the module and method that failed,
100 * and optional details such as the syscall, path or descriptor, description,
101 * and original cause. It is usually wrapped in `PlatformError`.
102 *
103 * @see {@link systemError} for creating the usual `PlatformError` wrapper from this reason data
104 * @see {@link BadArgument} for platform API failures caused by rejected caller input before an operation runs
105 * @see {@link SystemErrorTag} for the normalized tag values stored in `_tag`
106 *
107 * @category errors
108 * @since 4.0.0
109 */
110export class SystemError extends Data.Error<{
111 _tag: SystemErrorTag
112 module: string
113 method: string
114 description?: string | undefined
115 syscall?: string | undefined
116 pathOrDescriptor?: string | number | undefined
117 cause?: unknown
118}> {
119 /**
120 * Formats the normalized system error tag with operation and path details.
121 *
122 * **When to use**
123 *
124 * Use to read the formatted error message for a normalized system failure.
125 *
126 * @since 4.0.0
127 */
128 override get message(): string {
129 return `${this._tag}: ${this.module}.${this.method}${
130 this.pathOrDescriptor !== undefined ? ` (${this.pathOrDescriptor})` : ""
131 }${this.description ? `: ${this.description}` : ""}`
132 }
133}
134
135/**
136 * Tagged error used by platform APIs to report either invalid arguments or
137 * system-level failures.
138 *
139 * **When to use**
140 *
141 * Use as the shared error type for platform APIs that expose invalid arguments
142 * and host or operating-system failures through a single `Effect` error
143 * channel.
144 *
145 * **Details**
146 *
147 * The `reason` field contains the underlying `BadArgument` or `SystemError`.
148 * When that reason has a cause, the cause is preserved on the wrapper.
149 *
150 * @see {@link BadArgument} for invalid inputs rejected before an operation runs
151 * @see {@link SystemError} for failures reported by the host platform or operating system
152 * @see {@link badArgument} for creating this wrapper from rejected caller input
153 * @see {@link systemError} for creating this wrapper from a host or operating-system failure
154 *
155 * @category errors
156 * @since 4.0.0
157 */
158export class PlatformError extends Data.TaggedError("PlatformError")<{
159 reason: BadArgument | SystemError
160}> {
161 constructor(reason: BadArgument | SystemError) {
162 if ("cause" in reason) {
163 super({ reason, cause: reason.cause } as any)
164 } else {
165 super({ reason })
166 }
167 }
168
169 /**
170 * Marks this value as a platform error wrapper for runtime guards.
171 *
172 * **When to use**
173 *
174 * Use to identify `PlatformError` values through their runtime type marker.
175 *
176 * @since 4.0.0
177 */
178 readonly [TypeId]: typeof TypeId = TypeId
179
180 override get message(): string {
181 return this.reason.message
182 }
183}
184
185/**
186 * Returns `true` if a value is a `PlatformError`.
187 *
188 * **When to use**
189 *
190 * Use to narrow a caught defect, or an error channel typed more loosely than
191 * the platform APIs, to `PlatformError`.
192 *
193 * **Example** (Checking a caught value)
194 *
195 * ```ts import.meta.vitest
196 * import { PlatformError } from "effect"
197 *
198 * const error = PlatformError.badArgument({
199 * module: "FileSystem",
200 * method: "readFile"
201 * })
202 *
203 * PlatformError.isPlatformError(error) // => true
204 * PlatformError.isPlatformError(new Error("boom")) // => false
205 * ```
206 *
207 * @category guards
208 * @since 4.0.0
209 */
210export const isPlatformError = (u: unknown): u is PlatformError => Predicate.hasProperty(u, TypeId)
211
212/**
213 * Creates a `PlatformError` whose reason is a `SystemError`.
214 *
215 * **When to use**
216 *
217 * Use to adapt an operating-system or platform failure into the normalized
218 * platform error model.
219 *
220 * @category constructors
221 * @since 4.0.0
222 */
223export const systemError = (options: {
224 readonly _tag: SystemErrorTag
225 readonly module: string
226 readonly method: string
227 readonly description?: string | undefined
228 readonly syscall?: string | undefined
229 readonly pathOrDescriptor?: string | number | undefined
230 readonly cause?: unknown
231}): PlatformError => new PlatformError(new SystemError(options))
232
233/**
234 * Creates a `PlatformError` whose reason is a `BadArgument`.
235 *
236 * **When to use**
237 *
238 * Use to report a platform API rejecting caller input before performing the
239 * underlying operation.
240 *
241 * @category constructors
242 * @since 4.0.0
243 */
244export const badArgument = (options: {
245 readonly module: string
246 readonly method: string
247 readonly description?: string | undefined
248 readonly cause?: unknown
249}): PlatformError => new PlatformError(new BadArgument(options))