Skip to content

Commit 9b50329

Browse files
authored
Merge pull request #13 from maxholman/feat/header-params
feat: generate typed header parameters with valibot validation
2 parents ab01c11 + 9206060 commit 9b50329

16 files changed

Lines changed: 707 additions & 42 deletions
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,99 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`const values 1`] = `
4+
"export type StringConst = "hello";
5+
export type NumberConst = 42;
6+
export type BooleanConst = true;
7+
export type NullConst = null;
8+
"
9+
`;
10+
11+
exports[`const values 2`] = `
12+
"import * as v from "valibot";
13+
14+
export const stringConstSchema = v.literal("hello");
15+
export const numberConstSchema = v.literal(42);
16+
export const booleanConstSchema = v.literal(true);
17+
export const nullConstSchema = v.null();
18+
"
19+
`;
20+
21+
exports[`header parameters 1`] = `
22+
"export type UploadStatus = "pending" | "complete";
23+
export type UploadDataCommandHeader = {
24+
"content-type": "application/json" | "text/csv" | "application/xml";
25+
"content-length": number;
26+
"x-idempotency-key"?: string | undefined;
27+
};
28+
export type UploadDataCommandParams = {
29+
uploadId: string;
30+
};
31+
export type UploadDataCommandInput = UploadDataCommandParams;
32+
"
33+
`;
34+
35+
exports[`header parameters 2`] = `
36+
"import * as v from "valibot";
37+
38+
export const uploadStatusSchema = v.picklist(["pending", "complete"]);
39+
export const uploadDataCommandParamsSchema = v.strictObject({
40+
"uploadId": v.string()
41+
});
42+
export const uploadDataCommandHeaderSchema = v.object({
43+
"content-type": v.picklist(["application/json", "text/csv", "application/xml"]),
44+
"content-length": v.pipe(v.number(), v.integer()),
45+
"x-idempotency-key": v.exactOptional(v.pipe(v.string(), v.uuid()))
46+
});
47+
"
48+
`;
49+
50+
exports[`header parameters 3`] = `
51+
"import { validator } from "hono/validator";
52+
import * as v from "valibot";
53+
import { PublicValibotHonoError } from "@block65/rest-client";
54+
import { uploadDataCommandParamsSchema } from "./valibot.js";
55+
56+
function toPublicValibotHonoError(err: unknown): never {
57+
58+
if (err instanceof v.ValiError) {
59+
throw PublicValibotHonoError.from(err);
60+
}
61+
throw err;
62+
63+
}
64+
65+
export const uploadData = [
66+
validator("param", (value) => {
67+
return v.parseAsync(uploadDataCommandParamsSchema, value).catch(toPublicValibotHonoError);
68+
}),
69+
] as const;
70+
"
71+
`;
72+
373
exports[`nullables 1`] = `
474
"export type MySchemaLolOrNullable = "lol" | "kek" | null;
575
"
676
`;
77+
78+
exports[`top-level type array with null 1`] = `
79+
"export type NullableString = string | null;
80+
export type NullableStringEnum = "active" | "inactive" | null;
81+
export type NullableInteger = number | null;
82+
export type MultiType = string | number;
83+
"
84+
`;
85+
86+
exports[`top-level type array with null 2`] = `
87+
"import * as v from "valibot";
88+
89+
export const nullableStringSchema = v.nullable(v.string());
90+
export const nullableStringEnumSchema = v.nullable(v.picklist(["active", "inactive"]));
91+
export const nullableIntegerSchema = v.nullable(v.pipe(v.number(), v.integer()));
92+
export const multiTypeSchema = v.union([v.string(), v.number()]);
93+
"
94+
`;
95+
96+
exports[`top-level type array with null 3`] = `
97+
"export const nullableStringEnum = ["active", "inactive"] as const;
98+
"
99+
`;

__tests__/fixtures/test1.json

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"openapi": "3.0.3",
2+
"openapi": "3.1.0",
33
"info": {
44
"title": "Billing Service REST API",
55
"version": "1.0.0"
@@ -58,6 +58,33 @@
5858
"maxLength": 256,
5959
"pattern": "/^[A-Z0-9]$/"
6060
},
61+
"ApiVersion": {
62+
"type": "string",
63+
"const": "2024-01-01",
64+
"description": "The API version"
65+
},
66+
"MaxRetries": {
67+
"type": "integer",
68+
"const": 3
69+
},
70+
"DefaultEnabled": {
71+
"const": true
72+
},
73+
"NullableNotes": {
74+
"type": ["string", "null"],
75+
"description": "Optional notes field"
76+
},
77+
"NullableDiscount": {
78+
"type": ["number", "null"]
79+
},
80+
"NullableStatus": {
81+
"type": ["string", "null"],
82+
"enum": ["active", "paused", "cancelled"]
83+
},
84+
"AccountTier": {
85+
"type": "string",
86+
"enum": ["free", "pro", "enterprise"]
87+
},
6188
"LongRunningOperationIndeterminate": {
6289
"type": "object",
6390
"additionalProperties": false,
@@ -778,6 +805,74 @@
778805
}
779806
},
780807
"paths": {
808+
"/billing-accounts/{billingAccountId}/import": {
809+
"post": {
810+
"operationId": "importBillingDataCommand",
811+
"tags": [],
812+
"parameters": [
813+
{
814+
"$ref": "#/components/parameters/BillingAccountIdParameter"
815+
},
816+
{
817+
"name": "Content-Type",
818+
"in": "header",
819+
"required": true,
820+
"description": "The content type of the import data",
821+
"schema": {
822+
"type": "string",
823+
"enum": [
824+
"application/json",
825+
"text/csv",
826+
"application/xml"
827+
]
828+
}
829+
},
830+
{
831+
"name": "Content-Length",
832+
"in": "header",
833+
"required": true,
834+
"description": "The size of the import data in bytes",
835+
"schema": {
836+
"type": "integer",
837+
"format": "int64"
838+
}
839+
},
840+
{
841+
"name": "X-Idempotency-Key",
842+
"in": "header",
843+
"required": false,
844+
"description": "Optional idempotency key for safe retries",
845+
"schema": {
846+
"type": "string",
847+
"format": "uuid"
848+
}
849+
}
850+
],
851+
"requestBody": {
852+
"required": true,
853+
"content": {
854+
"application/octet-stream": {
855+
"schema": {
856+
"type": "string",
857+
"format": "binary"
858+
}
859+
}
860+
}
861+
},
862+
"responses": {
863+
"200": {
864+
"description": "Import 200 response",
865+
"content": {
866+
"application/json": {
867+
"schema": {
868+
"$ref": "#/components/schemas/LongRunningOperation"
869+
}
870+
}
871+
}
872+
}
873+
}
874+
}
875+
},
781876
"/operations/{operationId}": {
782877
"get": {
783878
"operationId": "getOperationCommand",

__tests__/fixtures/test1/commands.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* WARN: Do not edit directly.
55
*
6-
* Generated on 2026-03-17T13:15:38.356Z
6+
* Generated on 2026-04-02T04:52:00.193Z
77
*
88
*/
99
/** eslint-disable max-classes */
@@ -40,7 +40,8 @@ import type {
4040
} from "./types.js";
4141

4242
/**
43-
* Tagged template literal that applies encodeURIComponent to all interpolated values, protecting path integrity from characters like `/` and `#`.
43+
* Tagged template literal that applies encodeURIComponent to all interpolated
44+
* values, protecting path integrity from characters like `/` and `#`.
4445
* @example encodePath`/users/${userId}` // "/users/foo%2Fbar"
4546
*/
4647
function encodePath(

__tests__/fixtures/test1/enums.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* This file was auto generated by @block65/openapi-codegen
3+
*
4+
* WARN: Do not edit directly.
5+
*
6+
* Generated on 2026-04-02T04:52:00.193Z
7+
*
8+
*/
9+
export const nullableStatus = ["active", "paused", "cancelled"] as const;
10+
export const accountTier = ["free", "pro", "enterprise"] as const;
11+
export const billingSubscriptionStatus = ["active", "inactive"] as const;
12+
export const billingSubscriptionInterval = ["monthly", "yearly"] as const;
13+
export const planSku = [
14+
"donotuse",
15+
"plasku1",
16+
"plasku2",
17+
"plasku3",
18+
"plasku4",
19+
] as const;
20+
export const paymentMethodBrand = [
21+
"amex",
22+
"diners",
23+
"discover",
24+
"jcb",
25+
"mastercard",
26+
"unionpay",
27+
"visa",
28+
"unknown",
29+
] as const;
30+
export const billingLocale = ["en"] as const;
31+
export const billingAccountType = ["standard", "agency", "reseller"] as const;
32+
export const currency = ["usd", "aud", "sgd", "myr", "gbp"] as const;
33+
export const billingAccountStatus = ["nominal", "delinquent"] as const;
34+
export const billingCountry = ["us", "au", "sg", "my", "gb"] as const;

__tests__/fixtures/test1/hono-valibot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* WARN: Do not edit directly.
55
*
6-
* Generated on 2026-03-17T13:15:38.356Z
6+
* Generated on 2026-04-02T04:52:00.193Z
77
*
88
*/
99

__tests__/fixtures/test1/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* WARN: Do not edit directly.
55
*
6-
* Generated on 2026-03-17T13:15:38.356Z
6+
* Generated on 2026-04-02T04:52:00.193Z
77
*
88
*/
99
import {

__tests__/fixtures/test1/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@
33
*
44
* WARN: Do not edit directly.
55
*
6-
* Generated on 2026-03-17T13:15:38.356Z
6+
* Generated on 2026-04-02T04:52:00.193Z
77
*
88
*/
99
import type { Jsonifiable, Jsonify } from "type-fest";
1010

1111
export type PromoCode = string;
12+
/** The API version */
13+
export type ApiVersion = "2024-01-01";
14+
export type MaxRetries = 3;
15+
export type DefaultEnabled = true;
16+
/** Optional notes field */
17+
export type NullableNotes = string | null;
18+
export type NullableDiscount = number | null;
19+
export type NullableStatus = "active" | "paused" | "cancelled" | null;
20+
export type AccountTier = "free" | "pro" | "enterprise";
1221
export type StripeId = string;
1322
export type DateTime = Jsonify<Date>;
1423
export type BillingSubscriptionStatus = "active" | "inactive";

__tests__/fixtures/test1/valibot.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* WARN: Do not edit directly.
55
*
6-
* Generated on 2026-03-17T13:15:38.356Z
6+
* Generated on 2026-04-02T04:52:00.193Z
77
*
88
*/
99
import * as v from "valibot";
@@ -14,6 +14,17 @@ export const promoCodeSchema = v.pipe(
1414
v.maxLength(256),
1515
v.regex(/\/^[A-Z0-9]$\//),
1616
);
17+
/** The API version */
18+
export const apiVersionSchema = v.literal("2024-01-01");
19+
export const maxRetriesSchema = v.literal(3);
20+
export const defaultEnabledSchema = v.literal(true);
21+
/** Optional notes field */
22+
export const nullableNotesSchema = v.nullable(v.string());
23+
export const nullableDiscountSchema = v.nullable(v.number());
24+
export const nullableStatusSchema = v.nullable(
25+
v.picklist(["active", "paused", "cancelled"]),
26+
);
27+
export const accountTierSchema = v.picklist(["free", "pro", "enterprise"]);
1728
export const stripeIdSchema = v.pipe(
1829
v.string(),
1930
v.minLength(11),

0 commit comments

Comments
 (0)