Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add set-cookie headers in Http.response.toWeb #2403

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cuddly-dodos-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@effect/platform-node": patch
"@effect/platform": patch
---

use ReadonlyRecord for storing cookies
7 changes: 7 additions & 0 deletions .changeset/eight-seals-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@effect/platform-node": patch
"@effect/platform-bun": patch
"@effect/platform": patch
---

add set-cookie headers in Http.response.toWeb
3 changes: 1 addition & 2 deletions packages/platform-bun/src/internal/http/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ const makeResponse = (request: ServerRequest.ServerRequest, response: ServerResp
}

if (!Cookies.isEmpty(response.cookies)) {
const toSet = Cookies.toSetCookieHeaders(response.cookies)
for (const header of toSet) {
for (const header of Cookies.toSetCookieHeaders(response.cookies)) {
fields.headers.append("set-cookie", header)
}
}
Expand Down
27 changes: 13 additions & 14 deletions packages/platform-node/test/HttpServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,20 +604,19 @@ describe("HttpServer", () => {
const client = yield* _(makeClient)
const res = yield* _(HttpC.request.get("/home"), client, Effect.scoped)
assert.deepStrictEqual(
res.cookies.cookies[0].toJSON(),
Http.cookies.unsafeMakeCookie("test", "value").toJSON()
)
assert.deepStrictEqual(
res.cookies.cookies[1].toJSON(),
Http.cookies.unsafeMakeCookie("test2", "value2", {
httpOnly: true,
secure: true,
sameSite: "lax",
partitioned: true,
path: "/",
domain: "example.com",
expires: new Date(2022, 1, 1, 0, 0, 0, 0),
maxAge: Duration.minutes(5)
res.cookies.toJSON(),
Http.cookies.fromReadonlyRecord({
test: Http.cookies.unsafeMakeCookie("test", "value"),
test2: Http.cookies.unsafeMakeCookie("test2", "value2", {
httpOnly: true,
secure: true,
sameSite: "lax",
partitioned: true,
path: "/",
domain: "example.com",
expires: new Date(2022, 1, 1, 0, 0, 0, 0),
maxAge: Duration.minutes(5)
})
}).toJSON()
)
}).pipe(Effect.scoped, runPromise))
Expand Down
97 changes: 60 additions & 37 deletions packages/platform/src/Http/Cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as Inspectable from "effect/Inspectable"
import * as Option from "effect/Option"
import { type Pipeable, pipeArguments } from "effect/Pipeable"
import * as Predicate from "effect/Predicate"
import * as ReadonlyArray from "effect/ReadonlyArray"
import * as ReadonlyRecord from "effect/ReadonlyRecord"
import type * as Types from "effect/Types"
import { TypeIdError } from "../Error.js"

Expand Down Expand Up @@ -36,7 +36,7 @@ export const isCookies = (u: unknown): u is Cookies => Predicate.hasProperty(u,
*/
export interface Cookies extends Pipeable, Inspectable.Inspectable {
readonly [TypeId]: TypeId
readonly cookies: ReadonlyArray<Cookie>
readonly cookies: ReadonlyRecord.ReadonlyRecord<string, Cookie>
}

/**
Expand Down Expand Up @@ -103,7 +103,7 @@ const Proto: Omit<Cookies, "cookies"> = {
toJSON(this: Cookies) {
return {
_id: "@effect/platform/Http/Cookies",
cookies: this.cookies.map((cookie) => cookie.toJSON())
cookies: ReadonlyRecord.map(this.cookies, (cookie) => cookie.toJSON())
}
},
pipe() {
Expand All @@ -117,12 +117,26 @@ const Proto: Omit<Cookies, "cookies"> = {
* @since 1.0.0
* @category constructors
*/
export const fromIterable = (cookies: Iterable<Cookie>): Cookies => {
export const fromReadonlyRecord = (cookies: ReadonlyRecord.ReadonlyRecord<string, Cookie>): Cookies => {
const self = Object.create(Proto)
self.cookies = ReadonlyArray.fromIterable(cookies)
self.cookies = cookies
return self
}

/**
* Create a Cookies object from an Iterable
*
* @since 1.0.0
* @category constructors
*/
export const fromIterable = (cookies: Iterable<Cookie>): Cookies => {
const record: Record<string, Cookie> = {}
for (const cookie of cookies) {
record[cookie.name] = cookie
}
return fromReadonlyRecord(record)
}

/**
* Create a Cookies object from a set of Set-Cookie headers
*
Expand Down Expand Up @@ -285,7 +299,7 @@ export const empty: Cookies = fromIterable([])
* @since 1.0.0
* @category refinements
*/
export const isEmpty = (self: Cookies): boolean => self.cookies.length === 0
export const isEmpty = (self: Cookies): boolean => ReadonlyRecord.isEmptyRecord(self.cookies)

// eslint-disable-next-line no-control-regex
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/
Expand Down Expand Up @@ -362,15 +376,20 @@ export const unsafeMakeCookie = (
* @since 1.0.0
* @category combinators
*/
export const append: {
export const setCookie: {
(cookie: Cookie): (self: Cookies) => Cookies
(
self: Cookies,
cookie: Cookie
): Cookies
} = dual(
2,
(self: Cookies, cookie: Cookie) => fromIterable([...self.cookies, cookie])
(self: Cookies, cookie: Cookie) =>
fromReadonlyRecord(ReadonlyRecord.set(
self.cookies,
cookie.name,
cookie
))
)

/**
Expand All @@ -379,16 +398,19 @@ export const append: {
* @since 1.0.0
* @category combinators
*/
export const appendAll: {
export const setAllCookie: {
(cookies: Iterable<Cookie>): (self: Cookies) => Cookies
(
self: Cookies,
cookies: Iterable<Cookie>
): Cookies
} = dual(2, (self: Cookies, cookies: Iterable<Cookie>) =>
fromIterable(
ReadonlyArray.appendAll(self.cookies, cookies)
))
} = dual(2, (self: Cookies, cookies: Iterable<Cookie>) => {
const record = { ...self.cookies }
for (const cookie of cookies) {
record[cookie.name] = cookie
}
return fromReadonlyRecord(record)
})

/**
* Combine two Cookies objects, removing duplicates from the first
Expand All @@ -402,12 +424,11 @@ export const merge: {
self: Cookies,
that: Cookies
): Cookies
} = dual(2, (self: Cookies, that: Cookies) => {
const cookies = self.cookies.filter((c) => !that.cookies.some((c2) => c2.name === c.name))
// eslint-disable-next-line no-restricted-syntax
cookies.push(...that.cookies)
return fromIterable(cookies)
})
} = dual(2, (self: Cookies, that: Cookies) =>
fromReadonlyRecord({
...self.cookies,
...that.cookies
}))

/**
* Remove a cookie by name
Expand All @@ -421,18 +442,15 @@ export const remove: {
self: Cookies,
name: string
): Cookies
} = dual(2, (self: Cookies, cookie: Cookie) =>
fromIterable(
self.cookies.filter((c) => c.name !== cookie.name)
))
} = dual(2, (self: Cookies, name: string) => fromReadonlyRecord(ReadonlyRecord.remove(self.cookies, name)))

/**
* Add a cookie to a Cookies object
*
* @since 1.0.0
* @category combinators
*/
export const add: {
export const set: {
(
name: string,
value: string,
Expand All @@ -449,7 +467,7 @@ export const add: {
(self: Cookies, name: string, value: string, options?: Cookie["options"]) =>
Either.map(
makeCookie(name, value, options),
(cookie) => fromIterable([...self.cookies, cookie])
(cookie) => fromReadonlyRecord(ReadonlyRecord.set(self.cookies, name, cookie))
)
)

Expand All @@ -459,7 +477,7 @@ export const add: {
* @since 1.0.0
* @category combinators
*/
export const unsafeAdd: {
export const unsafeSet: {
(
name: string,
value: string,
Expand All @@ -474,7 +492,11 @@ export const unsafeAdd: {
} = dual(
(args) => isCookies(args[0]),
(self: Cookies, name: string, value: string, options?: Cookie["options"]) =>
append(self, unsafeMakeCookie(name, value, options))
fromReadonlyRecord(ReadonlyRecord.set(
self.cookies,
name,
unsafeMakeCookie(name, value, options)
))
)

/**
Expand All @@ -483,7 +505,7 @@ export const unsafeAdd: {
* @since 1.0.0
* @category combinators
*/
export const addAll: {
export const setAll: {
(
cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
): (self: Cookies) => Either.Either<Cookies, CookiesError>
Expand All @@ -497,15 +519,15 @@ export const addAll: {
self: Cookies,
cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
): Either.Either<Cookies, CookiesError> => {
const toAdd: Array<Cookie> = []
const record: Record<string, Cookie> = { ...self.cookies }
for (const [name, value, options] of cookies) {
const either = makeCookie(name, value, options)
if (Either.isLeft(either)) {
return either as Either.Left<CookiesError, never>
}
toAdd.push(either.right)
record[name] = either.right
}
return Either.right(appendAll(self, toAdd))
return Either.right(fromReadonlyRecord(record))
}
)

Expand All @@ -515,7 +537,7 @@ export const addAll: {
* @since 1.0.0
* @category combinators
*/
export const unsafeAddAll: {
export const unsafeSetAll: {
(
cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
): (self: Cookies) => Cookies
Expand All @@ -528,7 +550,7 @@ export const unsafeAddAll: {
(
self: Cookies,
cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
): Cookies => Either.getOrThrow(addAll(self, cookies))
): Cookies => Either.getOrThrow(setAll(self, cookies))
)

/**
Expand Down Expand Up @@ -616,7 +638,7 @@ export function serializeCookie(self: Cookie): string {
* @category encoding
*/
export const toCookieHeader = (self: Cookies): string =>
self.cookies.map((cookie) => `${cookie.name}=${cookie.valueEncoded}`).join("; ")
Object.values(self.cookies).map((cookie) => `${cookie.name}=${cookie.valueEncoded}`).join("; ")

/**
* To record
Expand All @@ -626,8 +648,9 @@ export const toCookieHeader = (self: Cookies): string =>
*/
export const toRecord = (self: Cookies): Record<string, string> => {
const record: Record<string, string> = {}
for (let index = 0; index < self.cookies.length; index++) {
const cookie = self.cookies[index]
const cookies = Object.values(self.cookies)
for (let index = 0; index < cookies.length; index++) {
const cookie = cookies[index]
record[cookie.name] = cookie.value
}
return record
Expand All @@ -639,7 +662,7 @@ export const toRecord = (self: Cookies): Record<string, string> => {
* @since 1.0.0
* @category encoding
*/
export const toSetCookieHeaders = (self: Cookies): Array<string> => self.cookies.map(serializeCookie)
export const toSetCookieHeaders = (self: Cookies): Array<string> => Object.values(self.cookies).map(serializeCookie)

/**
* Parse a cookie header into a record of key-value pairs
Expand Down
Loading