Skip to content

Commit d4187dc

Browse files
committed
Simplify OpenAPI
1 parent b79617c commit d4187dc

File tree

12 files changed

+61
-104
lines changed

12 files changed

+61
-104
lines changed

examples/misc/express/valibot/openapi/index.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import express from "express";
22
import * as v from "valibot";
33
import cors from "cors";
44
import { OpenAPIV3_1 } from "openapi-types";
5-
import { ValibotOpenApiEndpoints } from "@notainc/typed-api-spec/valibot/openapi";
6-
import { toOpenApiDoc } from "@notainc/typed-api-spec/valibot";
5+
import { SSOpenApiEndpoints, toOpenApiDoc } from "@notainc/typed-api-spec/core";
76

87
const openapiBaseDoc: Omit<OpenAPIV3_1.Document, "paths"> = {
98
openapi: "3.1.0",
@@ -45,7 +44,7 @@ const apiEndpoints = {
4544
},
4645
},
4746
},
48-
} satisfies ValibotOpenApiEndpoints;
47+
} satisfies SSOpenApiEndpoints;
4948

5049
const newApp = () => {
5150
const app = express();

examples/misc/express/zod/openapi/index.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import cors from "cors";
33
import { OpenAPIV3_1 } from "openapi-types";
44
import "zod-openapi/extend";
55
import z from "zod";
6-
import { toOpenApiDoc } from "@notainc/typed-api-spec/zod/openapi";
7-
import { ZodOpenApiEndpoints } from "@notainc/typed-api-spec/zod/openapi";
6+
import { SSOpenApiEndpoints, toOpenApiDoc } from "@notainc/typed-api-spec/core";
87

98
const openapiBaseDoc: Omit<OpenAPIV3_1.Document, "paths"> = {
109
openapi: "3.1.0",
@@ -48,15 +47,15 @@ const apiEndpoints = {
4847
},
4948
},
5049
},
51-
} satisfies ZodOpenApiEndpoints;
50+
} satisfies SSOpenApiEndpoints;
5251

5352
const newApp = () => {
5453
const app = express();
5554
app.use(express.json());
5655
app.use(cors());
5756
// const wApp = asAsync(typed(apiEndpoints, app));
58-
app.get("/openapi", (req, res) => {
59-
const openapi = toOpenApiDoc(openapiBaseDoc, apiEndpoints);
57+
app.get("/openapi", async (req, res) => {
58+
const openapi = await toOpenApiDoc(openapiBaseDoc, apiEndpoints);
6059
res.status(200).json(openapi);
6160
});
6261
return app;

examples/misc/simple/withValidation.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { withValidation } from "@notainc/typed-api-spec/fetch";
22
import { z } from "zod";
33
import { SpecValidatorError } from "@notainc/typed-api-spec/fetch";
4-
import { newSSValidator, SSApiEndpoints } from "@notainc/typed-api-spec/ss";
4+
import { SSApiEndpoints } from "@notainc/typed-api-spec/ss";
55

66
const GITHUB_API_ORIGIN = "https://api.github.com";
77

@@ -25,11 +25,10 @@ const spec2 = {
2525
const main = async () => {
2626
{
2727
// const fetchT = fetch as FetchT<typeof GITHUB_API_ORIGIN, Spec>;
28-
const { req: reqValidator, res: resValidator } = newSSValidator(spec);
29-
const fetchWithV = withValidation(fetch, spec, reqValidator, resValidator);
28+
const fetchWithV = withValidation(fetch, spec);
3029
const response = await fetchWithV(
3130
`${GITHUB_API_ORIGIN}/repos/nota/typed-api-spec/topics?page=1`,
32-
{ headers: { Accept: "application/vnd.github+json" } },
31+
{ headers: { Accept: "application/vnd.github+json" } }
3332
);
3433
if (!response.ok) {
3534
const { message } = await response.json();
@@ -41,12 +40,11 @@ const main = async () => {
4140

4241
{
4342
// const fetchT = fetch as FetchT<typeof GITHUB_API_ORIGIN, Spec>;
44-
const { req: reqValidator, res: resValidator } = newSSValidator(spec2);
45-
const fetchWithV = withValidation(fetch, spec2, reqValidator, resValidator);
43+
const fetchWithV = withValidation(fetch, spec2);
4644
try {
4745
await fetchWithV(
4846
`${GITHUB_API_ORIGIN}/repos/nota/typed-api-spec/topics?page=1`,
49-
{ headers: { Accept: "application/vnd.github+json" } },
47+
{ headers: { Accept: "application/vnd.github+json" } }
5048
);
5149
} catch (e) {
5250
if (e instanceof SpecValidatorError) {

pkgs/typed-api-spec/package.json

-10
Original file line numberDiff line numberDiff line change
@@ -113,21 +113,11 @@
113113
"require": "./dist/zod/index.js",
114114
"import": "./dist/zod/index.mjs"
115115
},
116-
"./zod/openapi": {
117-
"types": "./dist/zod/openapi.d.ts",
118-
"require": "./dist/zod/openapi.js",
119-
"import": "./dist/zod/openapi.mjs"
120-
},
121116
"./valibot": {
122117
"types": "./dist/valibot/index.d.ts",
123118
"require": "./dist/valibot/index.js",
124119
"import": "./dist/valibot/index.mjs"
125120
},
126-
"./valibot/openapi": {
127-
"types": "./dist/valibot/openapi.d.ts",
128-
"require": "./dist/valibot/openapi.js",
129-
"import": "./dist/valibot/openapi.mjs"
130-
},
131121
"./ss": {
132122
"types": "./dist/ss/index.d.ts",
133123
"require": "./dist/ss/index.js",

pkgs/typed-api-spec/src/core/jsonschema.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -13,56 +13,57 @@ import {
1313
} from "./spec";
1414
import { StatusCode } from "./hono-types";
1515
import { JSONSchema7 } from "json-schema";
16+
import { StandardSchemaV1 } from "@standard-schema/spec";
1617

1718
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18-
type ToSchema = (schema: any) => JSONSchema7;
19+
type ToSchema = (schema: StandardSchemaV1<any>) => Promise<JSONSchema7>;
1920

20-
export const toJsonSchemaApiEndpoints = <E extends AnyApiEndpoints>(
21+
export const toJsonSchemaApiEndpoints = async <E extends AnyApiEndpoints>(
2122
toSchema: ToSchema,
2223
endpoints: E,
23-
): JsonSchemaApiEndpoints => {
24+
): Promise<JsonSchemaApiEndpoints> => {
2425
const ret: JsonSchemaApiEndpoints = {};
2526
for (const path of Object.keys(endpoints)) {
26-
ret[path] = toJsonSchemaEndpoint(toSchema, endpoints[path]);
27+
ret[path] = await toJsonSchemaEndpoint(toSchema, endpoints[path]);
2728
}
2829
return ret;
2930
};
3031

31-
export const toJsonSchemaEndpoint = <Endpoint extends AnyApiEndpoint>(
32+
export const toJsonSchemaEndpoint = async <Endpoint extends AnyApiEndpoint>(
3233
toSchema: ToSchema,
3334
endpoint: Endpoint,
34-
) => {
35+
): Promise<Partial<Record<Method, JsonSchemaApiSpec>>> => {
3536
const ret: Partial<Record<Method, JsonSchemaApiSpec>> = {};
3637
for (const method of Method) {
3738
const spec = endpoint[method];
3839
if (spec) {
39-
ret[method] = toJsonSchemaApiSpec(toSchema, spec);
40+
ret[method] = await toJsonSchemaApiSpec(toSchema, spec);
4041
}
4142
}
4243
return ret;
4344
};
4445

45-
export const toJsonSchemaApiSpec = <Spec extends AnyApiSpec>(
46+
export const toJsonSchemaApiSpec = async <Spec extends AnyApiSpec>(
4647
toSchema: ToSchema,
4748
spec: Spec,
48-
): JsonSchemaApiSpec => {
49+
): Promise<JsonSchemaApiSpec> => {
4950
const extraProps = extractExtraApiSpecProps(spec);
5051
const ret: JsonSchemaApiSpec = {
51-
responses: toJsonSchemaResponses(toSchema, spec.responses),
52+
responses: await toJsonSchemaResponses(toSchema, spec.responses),
5253
};
5354
for (const key of apiSpecRequestKeys) {
5455
if (spec[key]) {
5556
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56-
ret[key] = toSchema(spec[key]);
57+
ret[key] = await toSchema(spec[key]);
5758
}
5859
}
5960
return { ...extraProps, ...ret };
6061
};
6162

62-
const toJsonSchemaResponses = (
63+
const toJsonSchemaResponses = async (
6364
toSchema: ToSchema,
6465
responses: AnyApiResponses,
65-
): JsonSchemaApiResponses => {
66+
): Promise<JsonSchemaApiResponses> => {
6667
const statusCodes = Object.keys(responses).map(Number) as StatusCode[];
6768
const ret: JsonSchemaApiResponses = {};
6869
for (const statusCode of statusCodes) {
@@ -72,8 +73,8 @@ const toJsonSchemaResponses = (
7273
}
7374
ret[statusCode] = {
7475
...extractExtraResponseProps(r),
75-
body: toSchema(r.body),
76-
headers: r.headers ? toSchema(r.headers) : undefined,
76+
body: await toSchema(r.body),
77+
headers: r.headers ? await toSchema(r.headers) : undefined,
7778
};
7879
}
7980
return ret;

pkgs/typed-api-spec/src/core/openapi/openapi.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from "./spec";
1919
import { StandardSchemaV1 } from "@standard-schema/spec";
2020
import { SSAnyApiResponse } from "../../ss";
21+
import { toJsonSchemaApiEndpoints } from "../jsonschema";
2122

2223
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2324
type AnyStandardSchemaV1 = StandardSchemaV1<any>;
@@ -147,7 +148,7 @@ const toResponses = (
147148
return ret;
148149
};
149150

150-
export const toOpenApiDoc = (
151+
export const jsonSchemaToOpenApiDoc = (
151152
doc: Omit<OpenAPIV3_1.Document, "paths">,
152153
endpoints: JsonSchemaOpenApiEndpoints,
153154
): OpenAPIV3_1.Document => {
@@ -157,3 +158,29 @@ export const toOpenApiDoc = (
157158
}
158159
return { ...doc, paths };
159160
};
161+
162+
export const toOpenApiDoc = async <E extends SSOpenApiEndpoints>(
163+
doc: Omit<OpenAPIV3_1.Document, "paths">,
164+
endpoints: E,
165+
): Promise<OpenAPIV3_1.Document> => {
166+
const e = await toJsonSchemaApiEndpoints(toSchema, endpoints);
167+
return jsonSchemaToOpenApiDoc(doc, e as JsonSchemaOpenApiEndpoints);
168+
};
169+
170+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
171+
const toSchema = async (s: StandardSchemaV1<any>) => {
172+
switch (s["~standard"].vendor) {
173+
case "zod": {
174+
const { createSchema } = await import("zod-openapi");
175+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
176+
return createSchema(s as any).schema as JSONSchema7;
177+
}
178+
case "valibot": {
179+
const { toJsonSchema } = await import("@valibot/to-json-schema");
180+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181+
return toJsonSchema(s as any);
182+
}
183+
default:
184+
throw new Error(`Unsupported vendor: ${s["~standard"].vendor}`);
185+
}
186+
};

pkgs/typed-api-spec/src/index.ts

-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,3 @@ import JSONT, { JSON$stringifyT } from "./json";
1515
export { JSONT, JSON$stringifyT };
1616

1717
export * from "./zod";
18-
export {
19-
toOpenApiDoc as toZodOpenApiDoc,
20-
toJsonSchemaApiEndpoints as toZodJsonSchemaApiEndpoints,
21-
} from "./zod/openapi";
-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from "./util";
2-
export * from "./openapi";

pkgs/typed-api-spec/src/valibot/openapi.test.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { describe, it, expect } from "vitest";
22
import * as v from "valibot";
33
import { OpenAPIV3_1 } from "openapi-types";
4-
import { toOpenApiDoc } from "./openapi";
5-
import { SSOpenApiEndpoints } from "../core";
4+
import { SSOpenApiEndpoints, toOpenApiDoc } from "../core";
65

76
describe("openapi", () => {
87
const endpoints = {
@@ -106,15 +105,15 @@ describe("openapi", () => {
106105
},
107106
};
108107

109-
it("toOpenApiDoc", () => {
108+
it("toOpenApiDoc", async () => {
110109
const baseDoc: Omit<OpenAPIV3_1.Document, "paths"> = {
111110
openapi: "3.1.0",
112111
info: { title: "title", version: "1" },
113112
security: [],
114113
servers: [],
115114
components: {},
116115
};
117-
const doc = toOpenApiDoc(baseDoc, endpoints);
116+
const doc = await toOpenApiDoc(baseDoc, endpoints);
118117
expect(doc).toEqual({
119118
...baseDoc,
120119
paths: {

pkgs/typed-api-spec/src/valibot/openapi.ts

-22
This file was deleted.

pkgs/typed-api-spec/src/zod/openapi.test.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { describe, it, expect } from "vitest";
22
import { OpenAPIV3_1 } from "openapi-types";
3-
import { toOpenApiDoc } from "./openapi";
43
import "zod-openapi/extend";
54
import z from "zod";
6-
import { SSOpenApiEndpoints } from "../core";
7-
5+
import { SSOpenApiEndpoints, toOpenApiDoc } from "../core";
86
describe("openapi", () => {
97
const endpoints = {
108
"/pets": {
@@ -104,15 +102,15 @@ describe("openapi", () => {
104102
},
105103
};
106104

107-
it("toOpenApiDoc", () => {
105+
it("toOpenApiDoc", async () => {
108106
const baseDoc: Omit<OpenAPIV3_1.Document, "paths"> = {
109107
openapi: "3.1.0",
110108
info: { title: "title", version: "1" },
111109
security: [],
112110
servers: [],
113111
components: {},
114112
};
115-
const doc = toOpenApiDoc(baseDoc, endpoints);
113+
const doc = await toOpenApiDoc(baseDoc, endpoints);
116114
expect(doc).toEqual({
117115
...baseDoc,
118116
paths: {

pkgs/typed-api-spec/src/zod/openapi.ts

-27
This file was deleted.

0 commit comments

Comments
 (0)