Skip to content

Commit

Permalink
Update HttpApi to remove wildcard support for better OpenAPI compat… (
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored Jan 14, 2025
1 parent ca07bd6 commit c110032
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 51 deletions.
21 changes: 21 additions & 0 deletions .changeset/fifty-pears-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"@effect/platform": minor
---

Update `HttpApi` to remove wildcard support for better OpenAPI compatibility.

The `HttpApi*` modules previously reused the following type from `HttpRouter`:

```ts
type PathInput = `/${string}` | "*"
```
However, the `"*"` wildcard value was not handled correctly, as OpenAPI does not support wildcards.
This has been updated to use a more specific type:
```ts
type PathSegment = `/${string}`
```
This change ensures better alignment with OpenAPI specifications and eliminates potential issues related to unsupported wildcard paths.
5 changes: 2 additions & 3 deletions packages/platform/src/HttpApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type * as HttpApiGroup from "./HttpApiGroup.js"
import type * as HttpApiMiddleware from "./HttpApiMiddleware.js"
import * as HttpApiSchema from "./HttpApiSchema.js"
import type { HttpMethod } from "./HttpMethod.js"
import type { PathInput } from "./HttpRouter.js"

/**
* @since 1.0.0
Expand Down Expand Up @@ -85,7 +84,7 @@ export interface HttpApi<
/**
* Prefix all endpoints in the `HttpApi`.
*/
prefix(prefix: PathInput): HttpApi<Id, Groups, E, R>
prefix(prefix: HttpApiEndpoint.PathSegment): HttpApi<Id, Groups, E, R>
/**
* Add a middleware to a `HttpApi`. It will be applied to all endpoints in the
* `HttpApi`.
Expand Down Expand Up @@ -194,7 +193,7 @@ const Proto = {
middlewares: this.middlewares
})
},
prefix(this: HttpApi.AnyWithProps, prefix: PathInput) {
prefix(this: HttpApi.AnyWithProps, prefix: HttpApiEndpoint.PathSegment) {
return makeProto({
identifier: this.identifier,
groups: Record.map(this.groups, (group) => group.prefix(prefix)),
Expand Down
2 changes: 1 addition & 1 deletion packages/platform/src/HttpApiBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ export const middlewareCors = (
*/
export const middlewareOpenApi = (
options?: {
readonly path?: HttpRouter.PathInput | undefined
readonly path?: HttpApiEndpoint.PathSegment | undefined
} | undefined
): Layer.Layer<never, never, HttpApi.Api> =>
Router.use((router) =>
Expand Down
35 changes: 22 additions & 13 deletions packages/platform/src/HttpApiEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export type TypeId = typeof TypeId
*/
export const isHttpApiEndpoint = (u: unknown): u is HttpApiEndpoint<any, any, any> => Predicate.hasProperty(u, TypeId)

/**
* Represents a path segment. A path segment is a string that represents a
* segment of a URL path.
*
* @since 1.0.0
* @category models
*/
export type PathSegment = `/${string}`

/**
* Represents an API endpoint. An API endpoint is mapped to a single route on
* the underlying `HttpRouter`.
Expand All @@ -54,7 +63,7 @@ export interface HttpApiEndpoint<
> extends Pipeable {
readonly [TypeId]: TypeId
readonly name: Name
readonly path: HttpRouter.PathInput
readonly path: PathSegment
readonly method: Method
readonly pathSchema: Option.Option<Schema.Schema<Path, unknown, R>>
readonly urlParamsSchema: Option.Option<Schema.Schema<UrlParams, unknown, R>>
Expand Down Expand Up @@ -194,7 +203,7 @@ export interface HttpApiEndpoint<
* Add a prefix to the path of the endpoint.
*/
prefix(
prefix: HttpRouter.PathInput
prefix: PathSegment
): HttpApiEndpoint<Name, Method, Path, UrlParams, Payload, Headers, Success, Error, R, RE>

/**
Expand Down Expand Up @@ -743,10 +752,10 @@ const Proto = {
headersSchema: Option.some(schema)
})
},
prefix(this: HttpApiEndpoint.AnyWithProps, prefix: HttpRouter.PathInput) {
prefix(this: HttpApiEndpoint.AnyWithProps, prefix: PathSegment) {
return makeProto({
...this,
path: HttpRouter.prefixPath(this.path, prefix) as HttpRouter.PathInput
path: HttpRouter.prefixPath(this.path, prefix) as PathSegment
})
},
middleware(this: HttpApiEndpoint.AnyWithProps, middleware: HttpApiMiddleware.TagClassAny) {
Expand Down Expand Up @@ -783,7 +792,7 @@ const makeProto = <
RE
>(options: {
readonly name: Name
readonly path: HttpRouter.PathInput
readonly path: PathSegment
readonly method: Method
readonly pathSchema: Option.Option<Schema.Schema<Path, unknown, R>>
readonly urlParamsSchema: Option.Option<Schema.Schema<UrlParams, unknown, R>>
Expand All @@ -802,9 +811,9 @@ const makeProto = <
*/
export const make = <Method extends HttpMethod>(method: Method): {
<const Name extends string>(name: Name): HttpApiEndpoint.Constructor<Name, Method>
<const Name extends string>(name: Name, path: HttpRouter.PathInput): HttpApiEndpoint<Name, Method>
<const Name extends string>(name: Name, path: PathSegment): HttpApiEndpoint<Name, Method>
} =>
((name: string, ...args: [HttpRouter.PathInput]) => {
((name: string, ...args: [PathSegment]) => {
if (args.length === 1) {
return makeProto({
name,
Expand All @@ -821,7 +830,7 @@ export const make = <Method extends HttpMethod>(method: Method): {
})
}
return (segments: TemplateStringsArray, ...schemas: ReadonlyArray<HttpApiSchema.AnyString>) => {
let path = segments[0] as HttpRouter.PathInput
let path = segments[0] as PathSegment
let pathSchema = Option.none<Schema.Schema.Any>()
if (schemas.length > 0) {
const obj: Record<string, Schema.Schema.Any> = {}
Expand Down Expand Up @@ -857,7 +866,7 @@ export const get: {
<const Name extends string>(name: Name): HttpApiEndpoint.Constructor<Name, "GET">
<const Name extends string>(
name: Name,
path: HttpRouter.PathInput
path: PathSegment
): HttpApiEndpoint<Name, "GET">
} = make("GET")

Expand All @@ -869,7 +878,7 @@ export const post: {
<const Name extends string>(name: Name): HttpApiEndpoint.Constructor<Name, "POST">
<const Name extends string>(
name: Name,
path: HttpRouter.PathInput
path: PathSegment
): HttpApiEndpoint<Name, "POST">
} = make("POST")

Expand All @@ -881,7 +890,7 @@ export const put: {
<const Name extends string>(name: Name): HttpApiEndpoint.Constructor<Name, "PUT">
<const Name extends string>(
name: Name,
path: HttpRouter.PathInput
path: PathSegment
): HttpApiEndpoint<Name, "PUT">
} = make("PUT")

Expand All @@ -893,7 +902,7 @@ export const patch: {
<const Name extends string>(name: Name): HttpApiEndpoint.Constructor<Name, "PATCH">
<const Name extends string>(
name: Name,
path: HttpRouter.PathInput
path: PathSegment
): HttpApiEndpoint<Name, "PATCH">
} = make(
"PATCH"
Expand All @@ -907,7 +916,7 @@ export const del: {
<const Name extends string>(name: Name): HttpApiEndpoint.Constructor<Name, "DELETE">
<const Name extends string>(
name: Name,
path: HttpRouter.PathInput
path: PathSegment
): HttpApiEndpoint<Name, "DELETE">
} = make(
"DELETE"
Expand Down
5 changes: 2 additions & 3 deletions packages/platform/src/HttpApiGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type * as HttpApiEndpoint from "./HttpApiEndpoint.js"
import type { HttpApiDecodeError } from "./HttpApiError.js"
import type * as HttpApiMiddleware from "./HttpApiMiddleware.js"
import * as HttpApiSchema from "./HttpApiSchema.js"
import type { PathInput } from "./HttpRouter.js"

/**
* @since 1.0.0
Expand Down Expand Up @@ -77,7 +76,7 @@ export interface HttpApiGroup<
* Add a path prefix to all endpoints in an `HttpApiGroup`. Note that this will only
* add the prefix to the endpoints before this api is called.
*/
prefix(prefix: PathInput): HttpApiGroup<Id, Endpoints, Error, R, TopLevel>
prefix(prefix: HttpApiEndpoint.PathSegment): HttpApiGroup<Id, Endpoints, Error, R, TopLevel>

/**
* Add an `HttpApiMiddleware` to the `HttpApiGroup`.
Expand Down Expand Up @@ -315,7 +314,7 @@ const Proto = {
middlewares: this.middlewares
})
},
prefix(this: HttpApiGroup.AnyWithProps, prefix: PathInput) {
prefix(this: HttpApiGroup.AnyWithProps, prefix: HttpApiEndpoint.PathSegment) {
return makeProto({
identifier: this.identifier,
topLevel: this.topLevel,
Expand Down
31 changes: 0 additions & 31 deletions packages/platform/test/OpenApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,37 +734,6 @@ describe("OpenApi", () => {
)
expectPaths(api, ["/prefix1", "/prefix2/a"])
})

it("wildcard: *", () => {
const api = HttpApi.make("api").add(
HttpApiGroup.make("group").add(
HttpApiEndpoint.get("get", "*")
.addSuccess(Schema.String)
)
)
// TODO: better handle wildcard paths
expectSpecPaths(api, {
"*": {
"get": {
"tags": ["group"],
"operationId": "group.get",
"parameters": [],
"security": [],
"responses": {
"200": {
"description": "a string",
"content": {
"application/json": {
"schema": { "type": "string" }
}
}
},
"400": HttpApiDecodeError
}
}
}
})
})
})

describe("HttpApiEndpoint.get", () => {
Expand Down

0 comments on commit c110032

Please sign in to comment.