Skip to content

Commit 918ed5d

Browse files
authored
Merge pull request #3416 from hey-api/copilot/add-schema-key-rename-transformer
2 parents a8ae16c + f8f44c3 commit 918ed5d

21 files changed

Lines changed: 737 additions & 0 deletions

File tree

.changeset/quick-carrots-tie.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hey-api/openapi-ts": patch
3+
"@hey-api/shared": patch
4+
---
5+
6+
**parser(transforms)**: add `schemaName` transform

docs/openapi-ts/configuration/parser.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,53 @@ export default {
447447

448448
You can customize the naming and casing pattern for `requests` and `responses` schemas using the `.name` and `.case` options.
449449

450+
### Schema Name
451+
452+
Sometimes your schema names are auto-generated or follow a naming convention that produces verbose or awkward type names. You can rename schema component keys throughout the specification, automatically updating all `$ref` pointers. For example, stripping version markers from schema names, removing vendor prefixes, converting naming conventions, or shortening deeply qualified names.
453+
454+
::: code-group
455+
456+
<!-- prettier-ignore-start -->
457+
```js [function]
458+
export default {
459+
input: 'hey-api/backend', // sign up at app.heyapi.dev
460+
output: 'src/client',
461+
parser: {
462+
transforms: {
463+
schemaName: (name) => { // [!code ++]
464+
// Strip version markers: ServiceRoot_v1_20_0_ServiceRoot → ServiceRoot // [!code ++]
465+
let clean = name.replace(/([A-Za-z\d]+)_v\d+_\d+_\d+_([A-Za-z\d]*)/g, (_, p1, p2) => // [!code ++]
466+
p2.startsWith(p1) ? p2 : p1 + p2, // [!code ++]
467+
); // [!code ++]
468+
// Deduplicate prefixes: Foo_Foo → Foo // [!code ++]
469+
const m = clean.match(/^([A-Za-z\d]+)_\1([A-Za-z\d]*)$/); // [!code ++]
470+
if (m) clean = m[1] + m[2]; // [!code ++]
471+
return clean; // [!code ++]
472+
}, // [!code ++]
473+
},
474+
},
475+
};
476+
```
477+
<!-- prettier-ignore-end -->
478+
479+
```js [template]
480+
export default {
481+
input: 'hey-api/backend', // sign up at app.heyapi.dev
482+
output: 'src/client',
483+
parser: {
484+
transforms: {
485+
schemaName: 'Api{{name}}', // [!code ++]
486+
},
487+
},
488+
};
489+
```
490+
491+
:::
492+
493+
::: tip Name Collisions
494+
If a transformed schema name conflicts with an existing schema, the rename is skipped for that schema to prevent overwrites. The original name is preserved.
495+
:::
496+
450497
## Pagination
451498

452499
Paginated operations are detected by having a pagination keyword in its parameters or request body. By default, we consider the following to be pagination keywords: `after`, `before`, `cursor`, `offset`, `page`, and `start`.

packages/openapi-ts-tests/main/test/2.0.x.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,28 @@ describe(`OpenAPI ${version}`, () => {
287287
}),
288288
description: 'handles read-only and write-only types',
289289
},
290+
{
291+
config: createConfig({
292+
input: 'transforms-schemas-name.yaml',
293+
output: 'transforms-schemas-name',
294+
parser: {
295+
transforms: {
296+
schemaName: (name: string) => {
297+
// Strip version markers: User_v1_0_0_User → User
298+
let clean = name.replace(/([A-Za-z\d]+)_v\d+_\d+_\d+_([A-Za-z\d]*)/g, (_, p1, p2) =>
299+
p2.startsWith(p1) ? p2 : p1 + p2,
300+
);
301+
// Deduplicate prefixes: Foo_Foo → Foo
302+
const m = clean.match(/^([A-Za-z\d]+)_\1([A-Za-z\d]*)$/);
303+
if (m) clean = m[1]! + m[2]!;
304+
return clean;
305+
},
306+
},
307+
},
308+
plugins: ['@hey-api/typescript'],
309+
}),
310+
description: 'handles schema name transforms',
311+
},
290312
{
291313
config: createConfig({
292314
input: 'schema-unknown.yaml',

packages/openapi-ts-tests/main/test/3.0.x.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,44 @@ describe(`OpenAPI ${version}`, () => {
590590
}),
591591
description: 'handles read-only and write-only types',
592592
},
593+
{
594+
config: createConfig({
595+
input: 'transforms-schemas-name.yaml',
596+
output: 'transforms-schemas-name',
597+
parser: {
598+
transforms: {
599+
schemaName: (name: string) => {
600+
// Strip version markers: User_v1_0_0_User → User
601+
let clean = name.replace(/([A-Za-z\d]+)_v\d+_\d+_\d+_([A-Za-z\d]*)/g, (_, p1, p2) =>
602+
p2.startsWith(p1) ? p2 : p1 + p2,
603+
);
604+
// Deduplicate prefixes: Foo_Foo → Foo
605+
const m = clean.match(/^([A-Za-z\d]+)_\1([A-Za-z\d]*)$/);
606+
if (m) clean = m[1]! + m[2]!;
607+
return clean;
608+
},
609+
},
610+
},
611+
plugins: ['@hey-api/typescript'],
612+
}),
613+
description: 'handles schema name transforms',
614+
},
615+
{
616+
config: createConfig({
617+
input: 'transforms-schemas-name-collision.yaml',
618+
output: 'transforms-schemas-name-collision',
619+
parser: {
620+
transforms: {
621+
schemaName: (name: string) =>
622+
// Try to rename all _vX_User schemas to "User"
623+
// This should cause collisions since "User" already exists
624+
name.replace(/_v\d+_User$/, ''),
625+
},
626+
},
627+
plugins: ['@hey-api/typescript'],
628+
}),
629+
description: 'handles schema name collision prevention',
630+
},
593631
{
594632
config: createConfig({
595633
input: 'security-api-key.yaml',

packages/openapi-ts-tests/main/test/3.1.x.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,28 @@ describe(`OpenAPI ${version}`, () => {
692692
}),
693693
description: 'handles read-only and write-only types',
694694
},
695+
{
696+
config: createConfig({
697+
input: 'transforms-schemas-name.yaml',
698+
output: 'transforms-schemas-name',
699+
parser: {
700+
transforms: {
701+
schemaName: (name: string) => {
702+
// Strip version markers: User_v1_0_0_User → User
703+
let clean = name.replace(/([A-Za-z\d]+)_v\d+_\d+_\d+_([A-Za-z\d]*)/g, (_, p1, p2) =>
704+
p2.startsWith(p1) ? p2 : p1 + p2,
705+
);
706+
// Deduplicate prefixes: Foo_Foo → Foo
707+
const m = clean.match(/^([A-Za-z\d]+)_\1([A-Za-z\d]*)$/);
708+
if (m) clean = m[1]! + m[2]!;
709+
return clean;
710+
},
711+
},
712+
},
713+
plugins: ['@hey-api/typescript'],
714+
}),
715+
description: 'handles schema name transforms',
716+
},
695717
{
696718
config: createConfig({
697719
input: 'transforms-read-write-nested.yaml',
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type { ClientOptions, Comment, GetUsersData, GetUsersResponse, GetUsersResponses, Post, PostPostsData, PostPostsResponse, PostPostsResponses, User, UserProfile } from './types.gen';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type ClientOptions = {
4+
baseUrl: string;
5+
};
6+
7+
export type User = {
8+
id?: string;
9+
name?: string;
10+
profile?: UserProfile;
11+
};
12+
13+
export type UserProfile = {
14+
bio?: string;
15+
avatar?: string;
16+
};
17+
18+
export type Post = {
19+
id?: string;
20+
title?: string;
21+
author?: User;
22+
comments?: Array<Comment>;
23+
};
24+
25+
export type Comment = {
26+
id?: string;
27+
text?: string;
28+
author?: User;
29+
};
30+
31+
export type GetUsersData = {
32+
body?: never;
33+
path?: never;
34+
query?: never;
35+
url: '/users';
36+
};
37+
38+
export type GetUsersResponses = {
39+
/**
40+
* Success
41+
*/
42+
200: User;
43+
};
44+
45+
export type GetUsersResponse = GetUsersResponses[keyof GetUsersResponses];
46+
47+
export type PostPostsData = {
48+
body?: Post;
49+
path?: never;
50+
query?: never;
51+
url: '/posts';
52+
};
53+
54+
export type PostPostsResponses = {
55+
/**
56+
* Created
57+
*/
58+
201: Post;
59+
};
60+
61+
export type PostPostsResponse = PostPostsResponses[keyof PostPostsResponses];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type { ClientOptions, GetTestData, GetTestResponse, GetTestResponses, User, UserV1User, UserV2User } from './types.gen';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type ClientOptions = {
4+
baseUrl: `${string}://${string}` | (string & {});
5+
};
6+
7+
export type UserV1User = {
8+
id?: string;
9+
version?: 'v1';
10+
};
11+
12+
export type User = {
13+
name?: string;
14+
};
15+
16+
export type UserV2User = {
17+
email?: string;
18+
version?: 'v2';
19+
};
20+
21+
export type GetTestData = {
22+
body?: never;
23+
path?: never;
24+
query?: never;
25+
url: '/test';
26+
};
27+
28+
export type GetTestResponses = {
29+
/**
30+
* Success
31+
*/
32+
200: {
33+
user1?: UserV1User;
34+
user2?: User;
35+
user3?: UserV2User;
36+
};
37+
};
38+
39+
export type GetTestResponse = GetTestResponses[keyof GetTestResponses];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type { ClientOptions, Comment, GetUsersData, GetUsersResponse, GetUsersResponses, Post, PostPostsData, PostPostsResponse, PostPostsResponses, User, UserProfile } from './types.gen';

0 commit comments

Comments
 (0)