EdgeAIG's picture
download
raw
9.24 kB
/**
* Describes failures raised while handling HTTP server requests.
*
* `HttpServerError` covers failures that happen while accepting a request,
* matching a route, running a handler, or building and sending a response.
* Request-scoped failures keep the request that caused them, and response
* failures keep the response being produced. This module also includes helpers
* for turning failed causes or exits into HTTP responses and an annotation for
* interrupts caused by client aborts.
*
* @since 4.0.0
*/
import * as Cause from "../../Cause.js";
import * as Context from "../../Context.js";
import * as Data from "../../Data.js";
import * as Effect from "../../Effect.js";
import * as ErrorReporter from "../../ErrorReporter.js";
import { constUndefined } from "../../Function.js";
import * as Option from "../../Option.js";
import { hasProperty } from "../../Predicate.js";
import * as Respondable from "./HttpServerRespondable.js";
import * as Response from "./HttpServerResponse.js";
const TypeId = "~effect/http/HttpServerError";
/**
* Tagged error for failures that occur while handling an HTTP server request.
*
* **Details**
*
* It wraps a `HttpServerErrorReason`, exposes the associated request and optional
* response, and can be converted to an HTTP response through the `Respondable`
* protocol.
*
* @category errors
* @since 4.0.0
*/
export class HttpServerError extends /*#__PURE__*/Data.TaggedError("HttpServerError") {
constructor(props) {
if ("cause" in props.reason) {
super({
...props,
cause: props.reason.cause
});
} else {
super(props);
}
}
[TypeId] = TypeId;
stack = `${this.name}: ${this.message}`;
get request() {
return this.reason.request;
}
get response() {
return "response" in this.reason ? this.reason.response : undefined;
}
[Respondable.symbol]() {
return this.reason[Respondable.symbol]();
}
get [ErrorReporter.ignore]() {
return this.reason[ErrorReporter.ignore] ?? false;
}
get message() {
return this.reason.message;
}
}
/**
* Error describing a failure to parse or read an incoming request.
*
* **Details**
*
* When converted to a response it produces an empty `400` response.
*
* @category errors
* @since 4.0.0
*/
export class RequestParseError extends /*#__PURE__*/Data.TaggedError("RequestParseError") {
/**
* Converts the request error into a `400 Bad Request` response.
*
* @since 4.0.0
*/
[Respondable.symbol]() {
return Effect.succeed(Response.empty({
status: 400
}));
}
get methodAndUrl() {
return `${this.request.method} ${this.request.url}`;
}
get message() {
return formatRequestMessage(this._tag, this.description, this.methodAndUrl);
}
}
/**
* Error indicating that no route matched the incoming request.
*
* **Details**
*
* When converted to a response it produces an empty `404` response, and it is
* ignored by the error reporter.
*
* @category errors
* @since 4.0.0
*/
export class RouteNotFound extends /*#__PURE__*/Data.TaggedError("RouteNotFound") {
[Respondable.symbol]() {
return Effect.succeed(Response.empty({
status: 404
}));
}
[ErrorReporter.ignore] = true;
get methodAndUrl() {
return `${this.request.method} ${this.request.url}`;
}
get message() {
return formatRequestMessage(this._tag, this.description, this.methodAndUrl);
}
}
/**
* Error describing an unexpected server-side failure while handling a request.
*
* **Details**
*
* When converted to a response it produces an empty `500` response.
*
* @category errors
* @since 4.0.0
*/
export class InternalError extends /*#__PURE__*/Data.TaggedError("InternalError") {
/**
* Converts the server error into a `500 Internal Server Error` response.
*
* @since 4.0.0
*/
[Respondable.symbol]() {
return Effect.succeed(Response.empty({
status: 500
}));
}
get methodAndUrl() {
return `${this.request.method} ${this.request.url}`;
}
get message() {
return formatRequestMessage(this._tag, this.description, this.methodAndUrl);
}
}
/**
* Returns `true` when the supplied value is an `HttpServerError`.
*
* @category predicates
* @since 4.0.0
*/
export const isHttpServerError = u => hasProperty(u, TypeId);
/**
* Error describing a failure related to an HTTP response.
*
* **Details**
*
* It carries the request and response involved in the failure. When converted to
* a response it produces an empty `500` response.
*
* @category errors
* @since 4.0.0
*/
export class ResponseError extends /*#__PURE__*/Data.TaggedError("ResponseError") {
[Respondable.symbol]() {
return Effect.succeed(Response.empty({
status: 500
}));
}
get methodAndUrl() {
return `${this.request.method} ${this.request.url}`;
}
get message() {
const info = `${this._tag} (${this.response.status} ${this.methodAndUrl})`;
return this.description ? `${info}: ${this.description}` : info;
}
}
/**
* Error wrapping a low-level failure from the HTTP server implementation.
*
* @category errors
* @since 4.0.0
*/
export class ServeError extends /*#__PURE__*/Data.TaggedError("ServeError") {}
/**
* Context annotation used to mark an interrupt as caused by the client aborting
* the request.
*
* **Details**
*
* `causeResponse` uses this annotation to map a pure client abort to a `499`
* response instead of a server abort response.
*
* @category annotations
* @since 4.0.0
*/
export class ClientAbort extends /*#__PURE__*/Context.Service()("effect/http/HttpServerError/ClientAbort") {
static annotation = /*#__PURE__*/this.context(true).pipe(/*#__PURE__*/Context.add(Cause.StackTrace, {
name: "ClientAbort",
stack: constUndefined,
parent: undefined
}));
}
const formatRequestMessage = (reason, description, info) => {
const prefix = `${reason} (${info})`;
return description ? `${prefix}: ${description}` : prefix;
};
/**
* Converts a failed handler cause into the HTTP response that should be sent and
* the cause that should be reported.
*
* **Details**
*
* Respondable failures and defects can choose their own response, defects that
* are already `HttpServerResponse` values are used directly, and pure interrupts
* produce either `499` for client aborts or `503` for server aborts.
*
* @category error handling
* @since 4.0.0
*/
export const causeResponse = cause => {
let response;
let effect = succeedInternalServerError;
const failures = [];
let interrupts = [];
let isClientInterrupt = false;
for (let i = 0; i < cause.reasons.length; i++) {
const reason = cause.reasons[i];
switch (reason._tag) {
case "Fail":
{
effect = Respondable.toResponseOrElse(reason.error, internalServerError);
failures.push(reason);
break;
}
case "Die":
{
if (Response.isHttpServerResponse(reason.defect)) {
response = reason.defect;
} else {
effect = Respondable.toResponseOrElseDefect(reason.defect, internalServerError);
failures.push(reason);
}
break;
}
case "Interrupt":
{
isClientInterrupt = reason.annotations.has(ClientAbort.key);
if (failures.length > 0) break;
interrupts.push(reason);
break;
}
}
}
if (response) {
return Effect.succeed([response, Cause.fromReasons(failures)]);
} else if (interrupts.length > 0 && failures.length === 0) {
failures.push(...interrupts);
effect = isClientInterrupt ? clientAbortError : serverAbortError;
}
return Effect.mapEager(effect, response => {
failures.push(Cause.makeDieReason(response));
return [response, Cause.fromReasons(failures)];
});
};
/**
* Derives an HTTP response from a failed handler cause synchronously.
*
* **Details**
*
* If the cause contains a defect that is already an `HttpServerResponse`, that
* response is used and removed from the remaining cause. Otherwise the response
* defaults to `500`.
*
* @category error handling
* @since 4.0.0
*/
export const causeResponseStripped = cause => {
let response;
const failures = cause.reasons.filter(f => {
if (f._tag === "Die" && Response.isHttpServerResponse(f.defect)) {
response = f.defect;
return false;
}
return true;
});
return [response ?? internalServerError, failures.length > 0 ? Option.some(Cause.fromReasons(failures)) : Option.none()];
};
const internalServerError = /*#__PURE__*/Response.empty({
status: 500
});
const succeedInternalServerError = /*#__PURE__*/Effect.succeed(internalServerError);
const clientAbortError = /*#__PURE__*/Effect.succeed(/*#__PURE__*/Response.empty({
status: 499
}));
const serverAbortError = /*#__PURE__*/Effect.succeed(/*#__PURE__*/Response.empty({
status: 503
}));
/**
* Extracts the response from a successful handler exit, or derives a response
* from the failure cause.
*
* @category error handling
* @since 4.0.0
*/
export const exitResponse = exit => {
if (exit._tag === "Success") {
return exit.value;
}
return causeResponseStripped(exit.cause)[0];
};
//# sourceMappingURL=HttpServerError.js.map

Xet Storage Details

Size:
9.24 kB
·
Xet hash:
cd2b26926e43d714d2cbde06a5dc561b483b35aa7f0b8ec42cba2daaa370776b

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.