Improve OpenAPI response payload typing for Swagger data schemas (#3102)

* Fix custom parser OpenAPI types and add structured default response schema

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Document all registerPath responses and normalize OpenAPI parser schemas

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Add concrete OpenAPI data schemas for selected routes

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Reformat generated OpenAPI response schemas for readability

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Remove obsolete stoi import from blueprint OpenAPI route

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
This commit is contained in:
Owen Schwartz
2026-05-31 11:10:38 -07:00
committed by GitHub
183 changed files with 2875 additions and 245 deletions

View File

@@ -78,7 +78,22 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}
}
});
export async function createSite(

View File

@@ -16,7 +16,7 @@ import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
const deleteSiteSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
registry.registerPath({
@@ -27,7 +27,22 @@ registry.registerPath({
request: {
params: deleteSiteSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}
}
});
export async function deleteSite(

View File

@@ -61,7 +61,22 @@ registry.registerPath({
niceId: z.string()
})
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}
}
});
registry.registerPath({
@@ -74,7 +89,22 @@ registry.registerPath({
siteId: z.number()
})
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}
}
});
export async function getSite(

View File

@@ -10,7 +10,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
const listSiteRolesSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
export async function listSiteRoles(

View File

@@ -248,7 +248,22 @@ registry.registerPath({
params: listSitesParamsSchema,
query: listSitesSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}
}
});
export async function listSites(

View File

@@ -15,6 +15,7 @@ import config from "@server/lib/config";
import { OpenAPITags, registry } from "@server/openApi";
import { fromError } from "zod-validation-error";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { listExitNodes } from "#dynamic/lib/exitNodes";
export type PickSiteDefaultsResponse = {
@@ -29,6 +30,19 @@ export type PickSiteDefaultsResponse = {
newtSecret: string;
clientAddress?: string;
};
const PickSiteDefaultsResponseDataSchema = z.object({
exitNodeId: z.number(),
address: z.string(),
publicKey: z.string(),
name: z.string(),
listenPort: z.number(),
endpoint: z.string(),
subnet: z.string(),
newtId: z.string(),
newtSecret: z.string(),
clientAddress: z.string().optional()
});
registry.registerPath({
method: "get",
@@ -41,7 +55,16 @@ registry.registerPath({
orgId: z.string()
})
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(PickSiteDefaultsResponseDataSchema)
}
}
}
}
});
const pickSiteDefaultsSchema = z.strictObject({

View File

@@ -44,7 +44,7 @@ export interface Container {
}
const siteIdParamsSchema = z.strictObject({
siteId: z.string().transform(stoi).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
const DockerStatusSchema = z.strictObject({

View File

@@ -12,7 +12,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { isValidCIDR } from "@server/lib/validators";
const updateSiteParamsSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
const updateSiteBodySchema = z
@@ -53,7 +53,22 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}
}
});
export async function updateSite(