Skip to content

Commit 2275ee3

Browse files
authored
Merge pull request #17 from maxholman/feat/command-constructor-headers
feat: wire header params into Command constructor
2 parents f6ad2cc + 85cdf49 commit 2275ee3

3 files changed

Lines changed: 72 additions & 7 deletions

File tree

__tests__/__snapshots__/nullables.test.ts.snap

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,34 @@ export type UploadDataCommandInput = UploadDataCommandParams;
3333
`;
3434

3535
exports[`header parameters 2`] = `
36+
"import { Command } from "@block65/rest-client";
37+
import type { UploadDataCommandHeader, UploadDataCommandInput, UploadStatus } from "./types.js";
38+
39+
/**
40+
* Tagged template literal that applies encodeURIComponent to all interpolated
41+
* values, protecting path integrity from characters like \`/\` and \`#\`.
42+
* @example encodePath\`/users/\${userId}\` // "/users/foo%2Fbar"
43+
*/
44+
function encodePath(strings: TemplateStringsArray, ...values: string[]): string {
45+
return String.raw({ raw: strings }, ...values.map(encodeURIComponent));
46+
}
47+
48+
/**
49+
* UploadDataCommand
50+
*
51+
*/
52+
export class UploadDataCommand extends Command<UploadDataCommandInput, UploadStatus, never, UploadDataCommandHeader> {
53+
public override method = "post" as const;
54+
55+
constructor(input: UploadDataCommandInput, headers: UploadDataCommandHeader) {
56+
const {uploadId } = input;
57+
super(encodePath\`/uploads/\${uploadId}\`, undefined, undefined, headers);
58+
}
59+
}
60+
"
61+
`;
62+
63+
exports[`header parameters 3`] = `
3664
"import * as v from "valibot";
3765
3866
export const uploadStatusSchema = v.picklist(["pending", "complete"]);
@@ -47,7 +75,7 @@ export const uploadDataCommandHeaderSchema = v.object({
4775
"
4876
`;
4977
50-
exports[`header parameters 3`] = `
78+
exports[`header parameters 4`] = `
5179
"import { validator } from "hono/validator";
5280
import * as v from "valibot";
5381
import { PublicValibotHonoError } from "@block65/rest-client";

__tests__/nullables.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ test("header parameters", async () => {
280280
);
281281

282282
expect(result.typesFile.getText()).toMatchSnapshot();
283+
expect(result.commandsFile.getText()).toMatchSnapshot();
283284
expect(result.valibotFile.getText()).toMatchSnapshot();
284285
expect(result.honoValibotFile.getText()).toMatchSnapshot();
285286
});

lib/process-document.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,19 @@ export async function processOpenApiDocument(
948948
?.addTypeArgument(queryType.getName());
949949
}
950950

951+
// headers type argument (4th generic on Command)
952+
if (headerType) {
953+
// fill in query slot if missing
954+
if (!queryType) {
955+
commandClassDeclaration
956+
.getExtends()
957+
?.addTypeArgument(neverKeyword);
958+
}
959+
commandClassDeclaration
960+
.getExtends()
961+
?.addTypeArgument(headerType.getName());
962+
}
963+
951964
const hasPathParams = path.includes("{");
952965
const pathname = hasPathParams
953966
? `encodePath\`${path.replaceAll(/{/g, "${")}\``
@@ -967,7 +980,9 @@ export async function processOpenApiDocument(
967980
!isUnspecifiedKeyword(paramsType) &&
968981
pathParameters.length > 0;
969982

970-
if (hasNonJsonBody || hasJsonBody || hasQuery || hasParams) {
983+
const hasHeaders = !!headerType && headerParameters.length > 0;
984+
985+
if (hasNonJsonBody || hasJsonBody || hasQuery || hasParams || hasHeaders) {
971986
const ctor = commandClassDeclaration.addConstructor();
972987

973988
const queryParameterNames = queryParameters
@@ -989,6 +1004,13 @@ export async function processOpenApiDocument(
9891004
type: inputType.getName(),
9901005
});
9911006

1007+
if (hasHeaders) {
1008+
ctor.addParameter({
1009+
name: "headers",
1010+
type: headerType.getName(),
1011+
});
1012+
}
1013+
9921014
ctor.addStatements([
9931015
{
9941016
kind: StructureKind.VariableStatement,
@@ -1034,6 +1056,8 @@ export async function processOpenApiDocument(
10341056
SyntaxKind.CallExpression,
10351057
);
10361058

1059+
const headersArg = hasHeaders ? "headers" : undefined;
1060+
10371061
// type narrowing
10381062
if (Node.isCallExpression(callExpr)) {
10391063
if (hasJsonBody) {
@@ -1042,23 +1066,35 @@ export async function processOpenApiDocument(
10421066
`jsonStringify(${inputBodyName})`,
10431067
...(hasQuery
10441068
? [`stripUndefined({${queryParameterNames.join(", ")}})`]
1045-
: []),
1069+
: hasHeaders
1070+
? [emptyKeyword]
1071+
: []),
1072+
...(headersArg ? [headersArg] : []),
10461073
]);
10471074
} else if (hasNonJsonBody) {
10481075
callExpr.addArguments([
10491076
pathname,
10501077
nonJsonBodyPropName,
10511078
...(hasQuery
10521079
? [`stripUndefined({${queryParameterNames.join(", ")}})`]
1053-
: []),
1080+
: hasHeaders
1081+
? [emptyKeyword]
1082+
: []),
1083+
...(headersArg ? [headersArg] : []),
10541084
]);
10551085
} else if (hasQuery) {
10561086
callExpr.addArguments([
10571087
pathname,
10581088
emptyKeyword,
1059-
...(hasQuery
1060-
? [`stripUndefined({${queryParameterNames.join(", ")}})`]
1061-
: []),
1089+
`stripUndefined({${queryParameterNames.join(", ")}})`,
1090+
...(headersArg ? [headersArg] : []),
1091+
]);
1092+
} else if (hasHeaders && headersArg) {
1093+
callExpr.addArguments([
1094+
pathname,
1095+
emptyKeyword,
1096+
emptyKeyword,
1097+
headersArg,
10621098
]);
10631099
} else {
10641100
callExpr.addArguments([pathname]);

0 commit comments

Comments
 (0)