/** * Common model for incoming HTTP messages. * * `HttpIncomingMessage` is used by server requests and client responses to * expose headers, an optional remote address, and body accessors. This module * provides decoders for JSON bodies, URL-encoded bodies, and headers, plus the * shared body-size setting and inspection helper used by request and response * implementations. * * @stability unstable * @since 4.0.0 */ import type * as ByteSize from "../ByteSize.ts" import * as Context from "../Context.ts" import * as Effect from "../Effect.ts" import type * as Inspectable from "../Inspectable.ts" import type * as Option from "../Option.ts" import { hasProperty } from "../Predicate.ts" import { redact } from "../Redactable.ts" import * as Schema from "../Schema.ts" import type { ParseOptions } from "../SchemaAST.ts" import type * as Stream from "../Stream.ts" import type * as Headers from "./Headers.ts" import type * as UrlParams from "./UrlParams.ts" /** * Type identifier for `HttpIncomingMessage` values. * * @stability unstable * @category type IDs * @since 4.0.0 */ export const TypeId = "~effect/http/HttpIncomingMessage" /** * Returns `true` when a value is an `HttpIncomingMessage`. * * @stability unstable * @category guards * @since 4.0.0 */ export const isHttpIncomingMessage = (u: unknown): u is HttpIncomingMessage => hasProperty(u, TypeId) /** * Options for parsing an incoming HTTP message body as JSON. * * @stability unstable * @category models * @since 4.0.0 */ export interface JsonOptions { readonly reviver?: Parameters[1] | undefined } /** * Common model for incoming HTTP messages, with headers, remote address, and effectful body accessors. * * @stability unstable * @category models * @since 4.0.0 */ export interface HttpIncomingMessage extends Inspectable.Inspectable { readonly [TypeId]: typeof TypeId readonly headers: Headers.Headers readonly remoteAddress: Option.Option readonly json: Effect.Effect readonly text: Effect.Effect readonly urlParamsBody: Effect.Effect readonly arrayBuffer: Effect.Effect readonly stream: Stream.Stream } /** * Creates a decoder that reads an incoming message's JSON body and decodes it with the supplied schema. * * @stability unstable * @category schemas * @since 4.0.0 */ export const schemaBodyJson = ( schema: S, options?: (ParseOptions & JsonOptions) | undefined ) => { const decode = Schema.decodeEffect(Schema.toCodecJson(schema)) const decodeJson = options?.reviver === undefined ? undefined : Schema.decodeEffect(Schema.fromJsonString(Schema.toCodecJson(schema), options)) return ( self: HttpIncomingMessage ): Effect.Effect => decodeJson === undefined ? Effect.flatMap(self.json, (u) => decode(u, options)) : Effect.flatMap(self.text, (body) => body === "" ? Effect.flatMap(self.json, (u) => decode(u, options)) : decodeJson(body, options)) } /** * Creates a decoder that reads an incoming message's URL-encoded body parameters and decodes them with the supplied schema. * * @stability unstable * @category schemas * @since 4.0.0 */ export const schemaBodyUrlParams = < A, I extends Readonly | undefined>>, RD >( schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const decode = Schema.RecordFromUrlParams.pipe( Schema.decodeTo(schema), Schema.decodeEffect ) return (self: HttpIncomingMessage): Effect.Effect => Effect.flatMap(self.urlParamsBody, (u) => decode(u, options)) } /** * Creates a decoder that validates and decodes an incoming message's headers with the supplied schema. * * @stability unstable * @category schemas * @since 4.0.0 */ export const schemaHeaders = >, RD>( schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const decode = Schema.decodeUnknownEffect(schema) return (self: HttpIncomingMessage): Effect.Effect => decode(self.headers, options) } /** * Context reference for the optional maximum size allowed when reading an incoming message body. * * @stability unstable * @category references * @since 4.0.0 */ export const MaxBodySize = Context.Reference( "effect/http/HttpIncomingMessage/MaxBodySize", { defaultValue: () => undefined } ) /** * Builds an inspectable object for an incoming message and redacts headers. * The body is left unread. * * @stability unstable * @category converting * @since 4.0.0 */ export const inspect = (self: HttpIncomingMessage, that: object): object => { return { ...that, headers: redact(self.headers), remoteAddress: self.remoteAddress } }