fix(server): serve HttpApi OpenAPI document (#26438)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
|
||||||
import { FetchHttpClient, HttpClient, HttpMiddleware, HttpRouter, HttpServer } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
|
||||||
import * as Socket from "effect/unstable/socket/Socket"
|
import * as Socket from "effect/unstable/socket/Socket"
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { Account } from "@/account/account"
|
import { Account } from "@/account/account"
|
||||||
@@ -49,6 +49,7 @@ import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@/server/cors
|
|||||||
import { serveUIEffect } from "@/server/shared/ui"
|
import { serveUIEffect } from "@/server/shared/ui"
|
||||||
import { ServerAuth } from "@/server/auth"
|
import { ServerAuth } from "@/server/auth"
|
||||||
import { InstanceHttpApi, RootHttpApi } from "./api"
|
import { InstanceHttpApi, RootHttpApi } from "./api"
|
||||||
|
import { PublicApi } from "./public"
|
||||||
import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
|
import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
|
||||||
import { EventApi, eventHandlers } from "./event"
|
import { EventApi, eventHandlers } from "./event"
|
||||||
import { configHandlers } from "./handlers/config"
|
import { configHandlers } from "./handlers/config"
|
||||||
@@ -144,6 +145,17 @@ const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const openApiDocument = OpenApi.fromApi(PublicApi)
|
||||||
|
const openApiDocumentJson = JSON.stringify(openApiDocument)
|
||||||
|
|
||||||
|
const docRoute = HttpRouter.use((router) =>
|
||||||
|
router.add(
|
||||||
|
"GET",
|
||||||
|
"/doc",
|
||||||
|
() => Effect.succeed(HttpServerResponse.text(openApiDocumentJson, { headers: { "content-type": "application/json" } })),
|
||||||
|
),
|
||||||
|
).pipe(Layer.provide(authOnlyRouterLayer))
|
||||||
|
|
||||||
const uiRoute = HttpRouter.use((router) =>
|
const uiRoute = HttpRouter.use((router) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
@@ -153,7 +165,7 @@ const uiRoute = HttpRouter.use((router) =>
|
|||||||
).pipe(Layer.provide(authOnlyRouterLayer))
|
).pipe(Layer.provide(authOnlyRouterLayer))
|
||||||
|
|
||||||
export function createRoutes(corsOptions?: CorsOptions) {
|
export function createRoutes(corsOptions?: CorsOptions) {
|
||||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
|
||||||
Layer.provide([
|
Layer.provide([
|
||||||
errorLayer,
|
errorLayer,
|
||||||
cors(corsOptions),
|
cors(corsOptions),
|
||||||
|
|||||||
@@ -48,6 +48,23 @@ const it = testEffect(Layer.mergeAll(testStateLayer, httpApiServerLayer))
|
|||||||
const directoryHeader = (dir: string) => HttpClientRequest.setHeader("x-opencode-directory", dir)
|
const directoryHeader = (dir: string) => HttpClientRequest.setHeader("x-opencode-directory", dir)
|
||||||
|
|
||||||
describe("instance HttpApi", () => {
|
describe("instance HttpApi", () => {
|
||||||
|
it.live("serves the OpenAPI document", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* HttpClient.get("/doc")
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.headers["content-type"]).toContain("application/json")
|
||||||
|
expect(yield* response.json).toMatchObject({
|
||||||
|
openapi: expect.any(String),
|
||||||
|
info: expect.any(Object),
|
||||||
|
paths: expect.objectContaining({
|
||||||
|
"/global/health": expect.any(Object),
|
||||||
|
"/session": expect.any(Object),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("serves path and VCS read endpoints", () =>
|
it.live("serves path and VCS read endpoints", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const dir = yield* tmpdirScoped({ git: true })
|
const dir = yield* tmpdirScoped({ git: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user