/** * @since 1.0.0 */ import * as FiberRef from "effect/FiberRef" import * as FiberRefs from "effect/FiberRefs" import { dual, identity } from "effect/Function" import { globalValue } from "effect/GlobalValue" import { type Redactable, symbolRedactable } from "effect/Inspectable" import type * as Option from "effect/Option" import * as Predicate from "effect/Predicate" import * as Record from "effect/Record" import * as Redacted from "effect/Redacted" import * as Schema from "effect/Schema" import * as String from "effect/String" import type { Mutable } from "effect/Types" /** * @since 1.0.0 * @category type ids */ export const HeadersTypeId: unique symbol = Symbol.for("@effect/platform/Headers") /** * @since 1.0.0 * @category type ids */ export type HeadersTypeId = typeof HeadersTypeId /** * @since 1.0.0 * @category refinements */ export const isHeaders = (u: unknown): u is Headers => Predicate.hasProperty(u, HeadersTypeId) /** * @since 1.0.0 * @category models */ export interface Headers extends Redactable { readonly [HeadersTypeId]: HeadersTypeId readonly [key: string]: string } const Proto = Object.assign(Object.create(null), { [HeadersTypeId]: HeadersTypeId, [symbolRedactable]( this: Headers, fiberRefs: FiberRefs.FiberRefs ): Record> { return redact(this, FiberRefs.getOrDefault(fiberRefs, currentRedactedNames)) } }) const make = (input: Record.ReadonlyRecord): Mutable => Object.assign(Object.create(Proto), input) as Headers /** * @since 1.0.0 * @category schemas */ export const schemaFromSelf: Schema.Schema = Schema.declare(isHeaders, { typeConstructor: { _tag: "effect/platform/Headers" }, identifier: "Headers", equivalence: () => Record.getEquivalence(String.Equivalence) }) /** * @since 1.0.0 * @category schemas */ export const schema: Schema.Schema> = Schema .transform( Schema.Record({ key: Schema.String, value: Schema.String }), schemaFromSelf, { strict: true, decode: (record) => fromInput(record), encode: identity } ) /** * @since 1.0.0 * @category models */ export type Input = | Record.ReadonlyRecord | undefined> | Iterable /** * @since 1.0.0 * @category constructors */ export const empty: Headers = Object.create(Proto) /** * @since 1.0.0 * @category constructors */ export const fromInput: (input?: Input) => Headers = (input) => { if (input === undefined) { return empty } else if (Symbol.iterator in input) { const out: Record = Object.create(Proto) for (const [k, v] of input) { out[k.toLowerCase()] = v } return out as Headers } const out: Record = Object.create(Proto) for (const [k, v] of Object.entries(input)) { if (Array.isArray(v)) { out[k.toLowerCase()] = v.join(", ") } else if (v !== undefined) { out[k.toLowerCase()] = v as string } } return out as Headers } /** * @since 1.0.0 * @category constructors */ export const unsafeFromRecord = (input: Record.ReadonlyRecord): Headers => Object.setPrototypeOf(input, Proto) as Headers /** * @since 1.0.0 * @category combinators */ export const has: { /** * @since 1.0.0 * @category combinators */ (key: string): (self: Headers) => boolean /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string): boolean } = dual< /** * @since 1.0.0 * @category combinators */ (key: string) => (self: Headers) => boolean, /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string) => boolean >(2, (self, key) => key.toLowerCase() in self) /** * @since 1.0.0 * @category combinators */ export const get: { /** * @since 1.0.0 * @category combinators */ (key: string): (self: Headers) => Option.Option /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string): Option.Option } = dual< /** * @since 1.0.0 * @category combinators */ (key: string) => (self: Headers) => Option.Option, /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string) => Option.Option >(2, (self, key) => Record.get(self as Record, key.toLowerCase())) /** * @since 1.0.0 * @category combinators */ export const set: { /** * @since 1.0.0 * @category combinators */ (key: string, value: string): (self: Headers) => Headers /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string, value: string): Headers } = dual< /** * @since 1.0.0 * @category combinators */ (key: string, value: string) => (self: Headers) => Headers, /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string, value: string) => Headers >(3, (self, key, value) => { const out = make(self) out[key.toLowerCase()] = value return out }) /** * @since 1.0.0 * @category combinators */ export const setAll: { /** * @since 1.0.0 * @category combinators */ (headers: Input): (self: Headers) => Headers /** * @since 1.0.0 * @category combinators */ (self: Headers, headers: Input): Headers } = dual< /** * @since 1.0.0 * @category combinators */ (headers: Input) => (self: Headers) => Headers, /** * @since 1.0.0 * @category combinators */ (self: Headers, headers: Input) => Headers >(2, (self, headers) => make({ ...self, ...fromInput(headers) })) /** * @since 1.0.0 * @category combinators */ export const merge: { /** * @since 1.0.0 * @category combinators */ (headers: Headers): (self: Headers) => Headers /** * @since 1.0.0 * @category combinators */ (self: Headers, headers: Headers): Headers } = dual< /** * @since 1.0.0 * @category combinators */ (headers: Headers) => (self: Headers) => Headers, /** * @since 1.0.0 * @category combinators */ (self: Headers, headers: Headers) => Headers >(2, (self, headers) => { const out = make(self) Object.assign(out, headers) return out }) /** * @since 1.0.0 * @category combinators */ export const remove: { /** * @since 1.0.0 * @category combinators */ (key: string | RegExp | ReadonlyArray): (self: Headers) => Headers /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string | RegExp | ReadonlyArray): Headers } = dual< /** * @since 1.0.0 * @category combinators */ (key: string | RegExp | ReadonlyArray) => (self: Headers) => Headers, /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string | RegExp | ReadonlyArray) => Headers >(2, (self, key) => { const out = make(self) const modify = (key: string | RegExp) => { if (typeof key === "string") { const k = key.toLowerCase() if (k in self) { delete out[k] } } else { for (const name in self) { if (key.test(name)) { delete out[name] } } } } if (Array.isArray(key)) { for (let i = 0; i < key.length; i++) { modify(key[i]) } } else { modify(key as string | RegExp) } return out }) /** * @since 1.0.0 * @category combinators */ export const redact: { /** * @since 1.0.0 * @category combinators */ (key: string | RegExp | ReadonlyArray): (self: Headers) => Record /** * @since 1.0.0 * @category combinators */ (self: Headers, key: string | RegExp | ReadonlyArray): Record } = dual( 2, ( self: Headers, key: string | RegExp | ReadonlyArray ): Record => { const out: Record = { ...self } const modify = (key: string | RegExp) => { if (typeof key === "string") { const k = key.toLowerCase() if (k in self) { out[k] = Redacted.make(self[k]) } } else { for (const name in self) { if (key.test(name)) { out[name] = Redacted.make(self[name]) } } } } if (Array.isArray(key)) { for (let i = 0; i < key.length; i++) { modify(key[i]) } } else { modify(key as string | RegExp) } return out } ) /** * @since 1.0.0 * @category fiber refs */ export const currentRedactedNames: FiberRef.FiberRef> = globalValue( "@effect/platform/Headers/currentRedactedNames", () => FiberRef.unsafeMake>([ "authorization", "cookie", "set-cookie", "x-api-key" ]) )