Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
use branded type for Headers (#359)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored Dec 16, 2023
1 parent ceeb639 commit 6dbc587
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .changeset/four-scissors-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@effect/platform-node": patch
"@effect/platform": patch
---

use branded type for Headers
6 changes: 6 additions & 0 deletions .changeset/many-rats-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@effect/platform-node": patch
"@effect/platform": patch
---

change UrlParams to ReadonlyArray
58 changes: 54 additions & 4 deletions docs/platform/Http/Headers.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ Added in v1.0.0
- [combinators](#combinators)
- [get](#get)
- [has](#has)
- [merge](#merge)
- [remove](#remove)
- [set](#set)
- [setAll](#setall)
- [constructors](#constructors)
- [empty](#empty)
- [fromInput](#frominput)
- [unsafeFromRecord](#unsafefromrecord)
- [models](#models)
- [Headers (interface)](#headers-interface)
- [Headers (type alias)](#headers-type-alias)
- [Input (type alias)](#input-type-alias)
- [type ids](#type-ids)
- [HeadersTypeId](#headerstypeid)
- [HeadersTypeId (type alias)](#headerstypeid-type-alias)

---

Expand Down Expand Up @@ -52,6 +57,19 @@ export declare const has: { (key: string): (self: Headers) => boolean; (self: He
Added in v1.0.0
## merge
**Signature**
```ts
export declare const merge: {
(headers: Headers): (self: Headers) => Headers
(self: Headers, headers: Headers): Headers
}
```
Added in v1.0.0
## remove
**Signature**
Expand Down Expand Up @@ -107,14 +125,24 @@ export declare const fromInput: (input?: Input) => Headers
Added in v1.0.0
## unsafeFromRecord
**Signature**
```ts
export declare const unsafeFromRecord: (input: ReadonlyRecord.ReadonlyRecord<string>) => Headers
```
Added in v1.0.0
# models
## Headers (interface)
## Headers (type alias)
**Signature**
```ts
export interface Headers extends ReadonlyRecord.ReadonlyRecord<string> {}
export type Headers = Brand.Branded<ReadonlyRecord.ReadonlyRecord<string>, HeadersTypeId>
```
Added in v1.0.0
Expand All @@ -124,7 +152,29 @@ Added in v1.0.0
**Signature**
```ts
export type Input = Headers | Iterable<readonly [string, string]>
export type Input = ReadonlyRecord.ReadonlyRecord<string> | Iterable<readonly [string, string]>
```
Added in v1.0.0
# type ids
## HeadersTypeId
**Signature**
```ts
export declare const HeadersTypeId: typeof HeadersTypeId
```
Added in v1.0.0
## HeadersTypeId (type alias)
**Signature**
```ts
export type HeadersTypeId = typeof HeadersTypeId
```
Added in v1.0.0
4 changes: 2 additions & 2 deletions docs/platform/Http/Platform.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export declare const make: (impl: {
path: string,
status: number,
statusText: string | undefined,
headers: Record<string, string>,
headers: Headers.Headers,
start: number,
end: number | undefined,
contentLength: number
Expand All @@ -45,7 +45,7 @@ export declare const make: (impl: {
file: Body.Body.FileLike,
status: number,
statusText: string | undefined,
headers: Record<string, string>,
headers: Headers.Headers,
options?: FileSystem.StreamOptions | undefined
) => ServerResponse.ServerResponse
}) => Effect.Effect<FileSystem.FileSystem | Etag.Generator, never, Platform>
Expand Down
4 changes: 2 additions & 2 deletions docs/platform/Http/UrlParams.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Added in v1.0.0
**Signature**
```ts
export type Input = UrlParams | Readonly<Record<string, string>> | Iterable<readonly [string, string]> | URLSearchParams
export type Input = Readonly<Record<string, string>> | Iterable<readonly [string, string]> | URLSearchParams
```
Added in v1.0.0
Expand All @@ -159,7 +159,7 @@ Added in v1.0.0
**Signature**
```ts
export interface UrlParams extends Chunk.Chunk<[string, string]> {}
export interface UrlParams extends ReadonlyArray<readonly [string, string]> {}
```

Added in v1.0.0
13 changes: 8 additions & 5 deletions packages/platform-node/src/internal/http/platform.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as Headers from "@effect/platform/Http/Headers"
import * as Platform from "@effect/platform/Http/Platform"
import * as ServerResponse from "@effect/platform/Http/ServerResponse"
import { pipe } from "effect/Function"
Expand All @@ -24,11 +25,13 @@ export const make = Platform.make({
},
fileWebResponse(file, status, statusText, headers, _options) {
return ServerResponse.raw(Readable.fromWeb(file.stream() as any), {
headers: {
...headers,
"content-type": headers["content-type"] ?? Mime.getType(file.name) ?? "application/octet-stream",
"content-length": file.size.toString()
},
headers: Headers.merge(
headers,
Headers.unsafeFromRecord({
"content-type": headers["content-type"] ?? Mime.getType(file.name) ?? "application/octet-stream",
"content-length": file.size.toString()
})
),
status,
statusText
})
Expand Down
46 changes: 40 additions & 6 deletions packages/platform/src/Http/Headers.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
/**
* @since 1.0.0
*/
import type * as Brand from "effect/Brand"
import { dual } from "effect/Function"
import type * as Option from "effect/Option"
import * as ReadonlyArray from "effect/ReadonlyArray"
import * as ReadonlyRecord from "effect/ReadonlyRecord"

/**
* @since 1.0.0
* @category type ids
*/
export const HeadersTypeId = Symbol.for("@effect/platform/Http/Headers")

/**
* @since 1.0.0
* @category type ids
*/
export type HeadersTypeId = typeof HeadersTypeId

/**
* @since 1.0.0
* @category models
*/
export interface Headers extends ReadonlyRecord.ReadonlyRecord<string> {}
export type Headers = Brand.Branded<ReadonlyRecord.ReadonlyRecord<string>, HeadersTypeId>

/**
* @since 1.0.0
* @category models
*/
export type Input = Headers | Iterable<readonly [string, string]>
export type Input = ReadonlyRecord.ReadonlyRecord<string> | Iterable<readonly [string, string]>

/**
* @since 1.0.0
* @category constructors
*/
export const empty: Headers = ReadonlyRecord.empty()
export const empty: Headers = Object.create(null) as Headers

/**
* @since 1.0.0
Expand All @@ -35,13 +48,19 @@ export const fromInput: (input?: Input) => Headers = (input) => {
return ReadonlyRecord.fromEntries(ReadonlyArray.map(
ReadonlyArray.fromIterable(input),
([k, v]) => [k.toLowerCase(), v] as const
))
)) as Headers
}
return ReadonlyRecord.fromEntries(
Object.entries(input).map(([k, v]) => [k.toLowerCase(), v])
)
) as Headers
}

/**
* @since 1.0.0
* @category constructors
*/
export const unsafeFromRecord = (input: ReadonlyRecord.ReadonlyRecord<string>): Headers => input as Headers

/**
* @since 1.0.0
* @category combinators
Expand Down Expand Up @@ -96,6 +115,21 @@ export const setAll: {
...fromInput(headers)
}))

/**
* @since 1.0.0
* @category combinators
*/
export const merge: {
(headers: Headers): (self: Headers) => Headers
(self: Headers, headers: Headers): Headers
} = dual<
(headers: Headers) => (self: Headers) => Headers,
(self: Headers, headers: Headers) => Headers
>(2, (self, headers) => ({
...self,
...headers
}))

/**
* @since 1.0.0
* @category combinators
Expand All @@ -106,4 +140,4 @@ export const remove: {
} = dual<
(key: string) => (self: Headers) => Headers,
(self: Headers, key: string) => Headers
>(2, (self, key) => ReadonlyRecord.remove(self, key.toLowerCase()))
>(2, (self, key) => ReadonlyRecord.remove(self, key.toLowerCase()) as Headers)
5 changes: 3 additions & 2 deletions packages/platform/src/Http/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type * as FileSystem from "../FileSystem.js"
import * as internal from "../internal/http/platform.js"
import type * as Body from "./Body.js"
import type * as Etag from "./Etag.js"
import type * as Headers from "./Headers.js"
import type * as ServerResponse from "./ServerResponse.js"

/**
Expand Down Expand Up @@ -54,7 +55,7 @@ export const make: (
path: string,
status: number,
statusText: string | undefined,
headers: Record<string, string>,
headers: Headers.Headers,
start: number,
end: number | undefined,
contentLength: number
Expand All @@ -63,7 +64,7 @@ export const make: (
file: Body.Body.FileLike,
status: number,
statusText: string | undefined,
headers: Record<string, string>,
headers: Headers.Headers,
options?: FileSystem.StreamOptions | undefined
) => ServerResponse.ServerResponse
}
Expand Down
Loading

0 comments on commit 6dbc587

Please sign in to comment.