EdgeAIG's picture
download
raw
10.8 kB
/**
* Attaches HTTP API metadata to Effect Schema values.
*
* This module is the schema-side bridge for HttpApi endpoint builders,
* generated clients, and OpenAPI support. It does not define routes or perform
* IO. Instead, the helpers annotate schemas so the surrounding HTTP API tooling
* can choose response status codes, content types, body codecs, multipart
* handling, and no-body response behavior.
*
* @since 4.0.0
*/
import { constVoid } from "../../Function.js";
import * as Predicate from "../../Predicate.js";
import * as Schema from "../../Schema.js";
import * as SchemaAST from "../../SchemaAST.js";
import * as SchemaTransformation from "../../SchemaTransformation.js";
import * as Stream from "../../Stream.js";
import { hasBody } from "../http/HttpMethod.js";
const statusCodeByLiteral = {
Continue: 100,
SwitchingProtocols: 101,
Processing: 102,
EarlyHints: 103,
OK: 200,
Ok: 200,
Created: 201,
Accepted: 202,
NonAuthoritativeInformation: 203,
NoContent: 204,
ResetContent: 205,
PartialContent: 206,
MultiStatus: 207,
AlreadyReported: 208,
ImUsed: 226,
MultipleChoices: 300,
MovedPermanently: 301,
Found: 302,
SeeOther: 303,
NotModified: 304,
TemporaryRedirect: 307,
PermanentRedirect: 308,
BadRequest: 400,
Unauthorized: 401,
PaymentRequired: 402,
Forbidden: 403,
NotFound: 404,
MethodNotAllowed: 405,
NotAcceptable: 406,
ProxyAuthenticationRequired: 407,
RequestTimeout: 408,
Conflict: 409,
Gone: 410,
LengthRequired: 411,
PreconditionFailed: 412,
PayloadTooLarge: 413,
UriTooLong: 414,
UnsupportedMediaType: 415,
RangeNotSatisfiable: 416,
ExpectationFailed: 417,
ImATeapot: 418,
MisdirectedRequest: 421,
UnprocessableEntity: 422,
Locked: 423,
FailedDependency: 424,
TooEarly: 425,
UpgradeRequired: 426,
PreconditionRequired: 428,
TooManyRequests: 429,
RequestHeaderFieldsTooLarge: 431,
UnavailableForLegalReasons: 451,
InternalServerError: 500,
NotImplemented: 501,
BadGateway: 502,
ServiceUnavailable: 503,
GatewayTimeout: 504,
HttpVersionNotSupported: 505,
VariantAlsoNegotiates: 506,
InsufficientStorage: 507,
LoopDetected: 508,
NotExtended: 510,
NetworkAuthenticationRequired: 511
};
const StreamSchemaTypeId = "~effect/httpapi/HttpApiSchema/Stream";
export function status(code) {
const statusCode = typeof code === "string" ? statusCodeByLiteral[code] : code;
return self => self.annotate({
httpApiStatus: statusCode
});
}
/**
* Creates a void schema with the given HTTP status code.
* This is used to represent empty responses with a specific status code.
*
* @see {@link NoContent} for the predefined 204 no content schema.
*
* @category Empty
* @since 4.0.0
*/
export const Empty = code => Schema.Void.pipe(status(code));
/**
* Schema for empty HTTP responses with status code 204.
*
* @category Empty
* @since 4.0.0
*/
export const NoContent = /*#__PURE__*/Empty(204);
/**
* Schema for empty HTTP responses with status code 201.
*
* @category Empty
* @since 4.0.0
*/
export const Created = /*#__PURE__*/Empty(201);
/**
* Schema for empty HTTP responses with status code 202.
*
* @category Empty
* @since 4.0.0
*/
export const Accepted = /*#__PURE__*/Empty(202);
/**
* Marks a schema as a no-content response while preserving a decoded client value.
*
* **Details**
*
* The server encodes the response as `void`; generated clients call `decode` to
* produce the schema's decoded value when the response has no body.
*
* @see {@link NoContent} for a void schema with the status code 204.
* @see {@link Empty} for creating a void schema with a specific status code.
*
* @category encoding
* @since 4.0.0
*/
export function asNoContent(options) {
return self => {
return Schema.Void.pipe(Schema.decodeTo(Schema.toType(self), SchemaTransformation.transform({
decode: options.decode,
encode: constVoid
})));
};
}
const streamSchema = /*#__PURE__*/Schema.declare(Stream.isStream);
/**
* Creates a Server-Sent Events streaming success response schema.
*
* @category constructors
* @since 4.0.0
*/
export const StreamSse = options => {
const events = options.events ?? (options.data === undefined ? undefined : Schema.Struct({
id: Schema.UndefinedOr(Schema.String),
event: Schema.String,
data: Schema.fromJsonString(options.data)
}));
if (events === undefined) {
throw new Error("StreamSse requires either an events schema or a data schema");
}
return Schema.make(streamSchema.ast, {
[StreamSchemaTypeId]: StreamSchemaTypeId,
_tag: "StreamSse",
mode: "sse",
sseMode: options.events === undefined ? "data" : "events",
contentType: options.contentType ?? defaultStreamContentType("sse"),
events,
error: options.error ?? Schema.Never
});
};
/**
* Creates a streaming `Uint8Array` success response schema.
*
* @category constructors
* @since 4.0.0
*/
export const StreamUint8Array = options => Schema.make(streamSchema.ast, {
[StreamSchemaTypeId]: StreamSchemaTypeId,
_tag: "StreamUint8Array",
mode: "uint8array",
contentType: options?.contentType ?? defaultStreamContentType("uint8array")
});
/** @internal */
export const isStreamSchema = u => Schema.isSchema(u) && Predicate.hasProperty(u, StreamSchemaTypeId);
/** @internal */
export const isStreamSse = u => isStreamSchema(u) && u._tag === "StreamSse";
/** @internal */
export const isStreamUint8Array = u => isStreamSchema(u) && u._tag === "StreamUint8Array";
/** @internal */
export function getStreamMetadata(self) {
return self._tag === "StreamSse" ? {
mode: self.mode,
sseMode: self.sseMode,
contentType: self.contentType,
events: self.events,
error: self.error
} : {
mode: self.mode,
contentType: self.contentType
};
}
function defaultStreamContentType(mode) {
switch (mode) {
case "sse":
return "text/event-stream";
case "uint8array":
return "application/octet-stream";
}
}
/**
* Runtime brand key used to mark schemas as buffered multipart payloads.
*
* @category type IDs
* @since 4.0.0
*/
export const MultipartTypeId = "~effect/httpapi/HttpApiSchema/Multipart";
/**
* Marks a schema as a multipart payload.
*
* @see {@link asMultipartStream} for a multipart stream payload.
*
* @category encoding
* @since 4.0.0
*/
export function asMultipart(options) {
return self => self.pipe(Schema.brand(MultipartTypeId)).annotate({
"~httpApiEncoding": {
_tag: "Multipart",
mode: "buffered",
contentType: defaultContentType("Multipart"),
limits: options
}
});
}
/**
* Runtime brand key used to mark schemas as streaming multipart payloads.
*
* @category type IDs
* @since 4.0.0
*/
export const MultipartStreamTypeId = "~effect/httpapi/HttpApiSchema/MultipartStream";
/**
* Marks a schema as a multipart stream payload.
*
* @see {@link asMultipart} for a buffered multipart payload.
*
* @category encoding
* @since 4.0.0
*/
export function asMultipartStream(options) {
return self => self.pipe(Schema.brand(MultipartStreamTypeId)).annotate({
"~httpApiEncoding": {
_tag: "Multipart",
mode: "stream",
contentType: defaultContentType("Multipart"),
limits: options
}
});
}
function asNonMultipartEncoding(self, options) {
return self.annotate({
"~httpApiEncoding": {
_tag: options._tag,
contentType: options.contentType ?? defaultContentType(options._tag)
}
});
}
function defaultContentType(_tag) {
switch (_tag) {
case "Multipart":
return "multipart/form-data";
case "Json":
return "application/json";
case "FormUrlEncoded":
return "application/x-www-form-urlencoded";
case "Uint8Array":
return "application/octet-stream";
case "Text":
return "text/plain";
}
}
/**
* Marks a schema as a JSON payload / response.
*
* @category encoding
* @since 4.0.0
*/
export function asJson(options) {
return self => asNonMultipartEncoding(self, {
_tag: "Json",
...options
});
}
/**
* Marks a schema as an `application/x-www-form-urlencoded` payload or response.
*
* **Details**
*
* The schema's encoded side must be a record of strings.
*
* @category encoding
* @since 4.0.0
*/
export function asFormUrlEncoded(options) {
return self => asNonMultipartEncoding(self, {
_tag: "FormUrlEncoded",
...options
});
}
/**
* Marks a schema as a text payload / response.
*
* **Details**
*
* The schema encoded side must be a string.
*
* @category encoding
* @since 4.0.0
*/
export function asText(options) {
return self => asNonMultipartEncoding(self, {
_tag: "Text",
...options
});
}
/**
* Marks a schema as a binary payload / response.
*
* **Details**
*
* The schema encoded side must be a `Uint8Array`.
*
* @category encoding
* @since 4.0.0
*/
export function asUint8Array(options) {
return self => asNonMultipartEncoding(self, {
_tag: "Uint8Array",
...options
});
}
/**
* Returns `true` when a schema AST represents a no-content response.
*
* **Details**
*
* The check succeeds for direct `void` schemas and schemas whose encoded or
* transformation target is `void`.
*
* @category predicates
* @since 4.0.0
*/
export const isNoContent = ast => {
if (SchemaAST.isVoid(ast)) return true;
const encoded = SchemaAST.toEncoded(ast);
if (SchemaAST.isVoid(encoded)) return true;
const target = ast.encoding?.[0].to;
if (target === undefined) return false;
return SchemaAST.isVoid(target);
};
const resolveHttpApiEncoding = /*#__PURE__*/SchemaAST.resolveAt("~httpApiEncoding");
const resolveHttpApiStatus = /*#__PURE__*/SchemaAST.resolveAt("httpApiStatus");
const defaultJsonEncoding = {
_tag: "Json",
contentType: "application/json"
};
const defaultUrlEncodedEncoding = {
_tag: "FormUrlEncoded",
contentType: "application/x-www-form-urlencoded"
};
function getEncoding(ast) {
return resolveHttpApiEncoding(ast) ?? defaultJsonEncoding;
}
/** @internal */
export function getPayloadEncoding(ast, method) {
const encoding = resolveHttpApiEncoding(ast);
if (encoding) return encoding;
return hasBody(method) ? defaultJsonEncoding : defaultUrlEncodedEncoding;
}
/** @internal */
export function getResponseEncoding(ast) {
const out = getEncoding(ast);
if (out._tag === "Multipart") {
throw new Error("Multipart is not supported in response");
}
return out;
}
/** @internal */
export function getStatusSuccess(self) {
return resolveHttpApiStatus(self) ?? 200;
}
/** @internal */
export function getStatusStream(self) {
return getStatusSuccess(self.ast);
}
/** @internal */
export function getStatusError(self) {
return resolveHttpApiStatus(self) ?? 500;
}
//# sourceMappingURL=HttpApiSchema.js.map

Xet Storage Details

Size:
10.8 kB
·
Xet hash:
90772346eb54bed1d2541e39647bee22775c79951860fd8eadf9f72dadb716b7

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