Skip to content
Draft
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/calm-methods-webdav.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@effect/platform": patch
"@effect/ai": patch
---

Support WebDAV and other uncommon HTTP methods.
3 changes: 2 additions & 1 deletion packages/ai/ai/src/AiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
* @since 1.0.0
*/
import type * as HttpClientError from "@effect/platform/HttpClientError"
import * as HttpMethod from "@effect/platform/HttpMethod"
import * as Effect from "effect/Effect"
import * as Inspectable from "effect/Inspectable"
import type { ParseError } from "effect/ParseResult"
Expand Down Expand Up @@ -145,7 +146,7 @@ export const isAiError = (u: unknown): u is AiError => Predicate.hasProperty(u,
* @category Schemas
*/
export const HttpRequestDetails = Schema.Struct({
method: Schema.Literal("GET", "POST", "PATCH", "PUT", "DELETE", "HEAD", "OPTIONS"),
method: Schema.Literal(...HttpMethod.all),
url: Schema.String,
urlParams: Schema.Array(Schema.Tuple(Schema.String, Schema.String)),
hash: Schema.Option(Schema.String),
Expand Down
18 changes: 18 additions & 0 deletions packages/ai/ai/test/AiError.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as AiError from "@effect/ai/AiError"
import { describe, it } from "@effect/vitest"
import { strictEqual } from "@effect/vitest/utils"
import { Schema } from "effect"

describe("AiError", () => {
it("accepts uncommon HTTP methods in request details", () => {
const details = Schema.decodeUnknownSync(AiError.HttpRequestDetails)({
method: "PROPFIND",
url: "https://example.com",
urlParams: [],
hash: { _tag: "None" },
headers: {}
})

strictEqual(details.method, "PROPFIND")
})
})
6 changes: 3 additions & 3 deletions packages/platform/src/HttpLayerRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface HttpRouter {
readonly prefixed: (prefix: string) => HttpRouter

readonly add: <E, R>(
method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS",
method: HttpMethod.HttpMethod | "*",
path: PathInput,
handler:
| Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
Expand Down Expand Up @@ -299,7 +299,7 @@ export const use = <A, E, R>(
* @category HttpRouter
*/
export const add = <E, R>(
method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS",
method: HttpMethod.HttpMethod | "*",
path: PathInput,
handler:
| Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
Expand Down Expand Up @@ -442,7 +442,7 @@ const makeRoute = <E, R>(options: {
* @category Route
*/
export const route = <E, R>(
method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS",
method: HttpMethod.HttpMethod | "*",
path: PathInput,
handler:
| Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
Expand Down
37 changes: 34 additions & 3 deletions packages/platform/src/HttpMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ export type HttpMethod =
| "PATCH"
| "HEAD"
| "OPTIONS"
| "PROPFIND"
| "PROPPATCH"
| "MKCOL"
| "COPY"
| "MOVE"
| "LOCK"
| "UNLOCK"
| "TRACE"
| "SEARCH"
| "REPORT"
| "MKCALENDAR"

/**
* @since 1.0.0
Expand All @@ -20,7 +31,7 @@ export declare namespace HttpMethod {
* @since 1.0.0
* @category models
*/
export type NoBody = "GET" | "HEAD" | "OPTIONS"
export type NoBody = "GET" | "HEAD" | "OPTIONS" | "TRACE"

/**
* @since 1.0.0
Expand All @@ -32,12 +43,32 @@ export declare namespace HttpMethod {
/**
* @since 1.0.0
*/
export const hasBody = (method: HttpMethod): boolean => method !== "GET" && method !== "HEAD" && method !== "OPTIONS"
export const hasBody = (method: HttpMethod): boolean =>
method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE"

/**
* @since 1.0.0
*/
export const all: ReadonlySet<HttpMethod> = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
export const all: ReadonlySet<HttpMethod> = new Set([
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"PROPFIND",
"PROPPATCH",
"MKCOL",
"COPY",
"MOVE",
"LOCK",
"UNLOCK",
"TRACE",
"SEARCH",
"REPORT",
"MKCALENDAR"
])

/**
* Tests if a value is a `HttpMethod`.
Expand Down
39 changes: 39 additions & 0 deletions packages/platform/test/HttpMethod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
import * as HttpMethod from "@effect/platform/HttpMethod"
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
import { describe, it } from "@effect/vitest"
import { assertFalse, assertTrue, strictEqual } from "@effect/vitest/utils"

const uncommonMethods = [
"PROPFIND",
"PROPPATCH",
"MKCOL",
"COPY",
"MOVE",
"LOCK",
"UNLOCK",
"TRACE",
"SEARCH",
"REPORT",
"MKCALENDAR"
] as const satisfies ReadonlyArray<HttpMethod.HttpMethod>

describe("HttpMethod", () => {
it("recognizes uncommon HTTP methods", () => {
for (const method of uncommonMethods) {
assertTrue(HttpMethod.isHttpMethod(method))
assertTrue(HttpMethod.all.has(method))
}
})

it("treats TRACE as a no-body method", () => {
assertFalse(HttpMethod.hasBody("TRACE"))
})

it("supports uncommon methods in HttpLayerRouter", () => {
for (const method of uncommonMethods) {
const route = HttpLayerRouter.route(method, "/", HttpServerResponse.empty())
strictEqual(route.method, method)
}
})
})