Skip to content

Commit

Permalink
fix(type-safe-api): fix generated react hooks compilation issues
Browse files Browse the repository at this point in the history
@tanstack/react-query v5 was released earlier, and the hooks project did not pin the version
resulting in code which failed to compile. Additionally the @paginated trait in Smithy specifies the
Smithy member name which is not the name used in the final generated code, since Smithy -> OpenAPI
translation will rename parameters to the name provided in the @HTTPHeader or @httpQuery traits.
Additionally the generated typescript client will use valid parameter names, eg kebab-case becomes
camelCase, so address this too.

Fixes #604
  • Loading branch information
cogwirrel committed Oct 18, 2023
1 parent e3f9cac commit 05b84f9
Show file tree
Hide file tree
Showing 9 changed files with 1,411 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ SPDX-License-Identifier: Apache-2.0 */
import * as fs from "fs";
import * as path from "path";
import kebabCase from "lodash/kebabCase";
import camelCase from "lodash/camelCase";
import { parse } from "ts-command-line-args";

// Used to split OpenAPI generated files into multiple files in order to work around
Expand Down Expand Up @@ -32,6 +33,8 @@ const applyReplacementFunction = (functionConfig: FunctionConfig): string => {
switch (functionConfig.function) {
case "kebabCase":
return kebabCase(functionConfig.args[0]);
case "camelCase":
return camelCase(functionConfig.args[0]);
default:
throw new Error(`Unsupported TSAPI_FN function ${functionConfig.function}`);
}
Expand Down Expand Up @@ -127,6 +130,9 @@ void (async () => {

// Delete the original file
fs.rmSync(filePath);
} else {
// Apply the replacement functions directly
fs.writeFileSync(filePath, applyReplacementFunctions(contents));
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const use{{operationIdCamelCase}} = <TError = ResponseError>(
if (!api) {
throw NO_API_ERROR;
}
return useInfiniteQuery(["{{nickname}}"{{#allParams.0}}, params{{/allParams.0}}], ({ pageParam }) => api.{{nickname}}({ {{#allParams.0}}...params, {{/allParams.0}}{{vendorExtensions.x-paginated.inputToken}}: pageParam }), {
return useInfiniteQuery(["{{nickname}}"{{#allParams.0}}, params{{/allParams.0}}], ({ pageParam }) => api.{{nickname}}({ {{#allParams.0}}...params, {{/allParams.0}}###TSAPI_FN###{"function": "camelCase", "args": ["{{vendorExtensions.x-paginated.inputToken}}"]}###/TSAPI_FN###: pageParam }), {
getNextPageParam: (response) => response.{{vendorExtensions.x-paginated.outputToken}},
context: {{classname}}DefaultContext,
...options as any,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,42 @@ SPDX-License-Identifier: Apache-2.0 */
import SwaggerParser from "@apidevtools/swagger-parser";
import { writeFile } from "projen/lib/util";
import { parse } from "ts-command-line-args";
import * as path from 'path';
import * as _ from "lodash";
import fs from "fs";
import type { OpenAPIV3 } from "openapi-types";

// Smithy HTTP trait is used to map Smithy operations to their location in the spec
const SMITHY_HTTP_TRAIT_ID = "smithy.api#http";

// The OpenAPI vendor extension used for paginated operations
const PAGINATED_VENDOR_EXTENSION = "x-paginated";

// Traits that will "rename" members in the generated OpenAPI spec
const SMITHY_RENAME_TRAITS = [
"smithy.api#httpQuery",
"smithy.api#httpHeader",
];

// Maps traits to specific vendor extensions which we also support specifying in OpenAPI
const TRAIT_TO_SUPPORTED_OPENAPI_VENDOR_EXTENSION: { [key: string]: string } = {
"smithy.api#paginated": "x-paginated",
"smithy.api#paginated": PAGINATED_VENDOR_EXTENSION,
};

interface SmithyMember {
readonly target: string;
readonly traits?: { [key: string]: any };
}

interface SmithyOperationInput {
readonly type: string;
readonly members?: { [key: string]: SmithyMember }
}

interface SmithyOperationDetails {
readonly id: string;
readonly method: string;
readonly path: string;
readonly traits: { [key: string]: any };
readonly input?: SmithyOperationInput;
}

interface InvalidRequestParameter {
Expand Down Expand Up @@ -80,6 +98,7 @@ void (async () => {
method: shape.traits[SMITHY_HTTP_TRAIT_ID].method?.toLowerCase(),
path: shape.traits[SMITHY_HTTP_TRAIT_ID].uri,
traits: shape.traits,
input: smithyModel.shapes[shape.input?.target],
}));

// Apply all operation-level traits as vendor extensions to the relevant operation in the spec
Expand All @@ -96,7 +115,20 @@ void (async () => {
if (traitId.endsWith("#handler")) {
vendorExtension = "x-handler";
}
spec.paths[operation.path][operation.method][vendorExtension] = value;

let extensionValue = value;

// The smithy paginated trait is written in terms of inputs which may have different names in openapi
// so we must map them here
if (vendorExtension === PAGINATED_VENDOR_EXTENSION) {
extensionValue = Object.fromEntries(Object.entries(value as {[key: string]: string}).map(([traitProperty, memberName]) => {
const member = operation.input?.members?.[memberName];
const renamedMemberName = SMITHY_RENAME_TRAITS.map(trait => member?.traits?.[trait]).find(x => x) ?? memberName;
return [traitProperty, renamedMemberName];
}));
}

spec.paths[operation.path][operation.method][vendorExtension] = extensionValue;
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class TypescriptReactQueryHooksLibrary extends TypeScriptProject {
}

// Add dependencies on react-query and react
this.addDeps("@tanstack/react-query");
this.addDeps("@tanstack/react-query@^4"); // Pin at 4 for now - requires generated code updates to upgrade to 5
this.addDevDeps("react", "@types/react");
this.addPeerDeps("react");

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 05b84f9

Please sign in to comment.