@devup-api/generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/__tests__/convert-case.test.d.ts +2 -0
- package/dist/__tests__/convert-case.test.d.ts.map +1 -0
- package/dist/__tests__/create-url-map.test.d.ts +2 -0
- package/dist/__tests__/create-url-map.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/wrap-interface-key-guard.test.d.ts +2 -0
- package/dist/__tests__/wrap-interface-key-guard.test.d.ts.map +1 -0
- package/dist/convert-case.d.ts +5 -0
- package/dist/convert-case.d.ts.map +1 -0
- package/dist/create-url-map.d.ts +4 -0
- package/dist/create-url-map.d.ts.map +1 -0
- package/dist/generate-interface.d.ts +15 -0
- package/dist/generate-interface.d.ts.map +1 -0
- package/dist/generate-schema.d.ts +44 -0
- package/dist/generate-schema.d.ts.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/wrap-interface-key-guard.d.ts +2 -0
- package/dist/wrap-interface-key-guard.d.ts.map +1 -0
- package/package.json +27 -0
- package/src/__tests__/convert-case.test.ts +125 -0
- package/src/__tests__/create-url-map.test.ts +318 -0
- package/src/__tests__/index.test.ts +9 -0
- package/src/__tests__/wrap-interface-key-guard.test.ts +42 -0
- package/src/convert-case.ts +22 -0
- package/src/create-url-map.ts +43 -0
- package/src/generate-interface.ts +594 -0
- package/src/generate-schema.ts +482 -0
- package/src/index.ts +2 -0
- package/src/wrap-interface-key-guard.ts +6 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import type { UrlMapValue } from '@devup-api/core'
|
|
3
|
+
import type { OpenAPIV3_1 } from 'openapi-types'
|
|
4
|
+
import { createUrlMap } from '../create-url-map'
|
|
5
|
+
|
|
6
|
+
test.each([
|
|
7
|
+
[
|
|
8
|
+
'camel',
|
|
9
|
+
undefined,
|
|
10
|
+
'get_users',
|
|
11
|
+
{
|
|
12
|
+
getUsers: { method: 'GET', url: '/users' },
|
|
13
|
+
'/users': { method: 'GET', url: '/users' },
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
'snake',
|
|
18
|
+
{ convertCase: 'snake' as const },
|
|
19
|
+
'getUsers',
|
|
20
|
+
{
|
|
21
|
+
get_users: { method: 'GET', url: '/users' },
|
|
22
|
+
'/users': { method: 'GET', url: '/users' },
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
'pascal',
|
|
27
|
+
{ convertCase: 'pascal' as const },
|
|
28
|
+
'get_users',
|
|
29
|
+
{
|
|
30
|
+
GetUsers: { method: 'GET', url: '/users' },
|
|
31
|
+
'/users': { method: 'GET', url: '/users' },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
'maintain',
|
|
36
|
+
{ convertCase: 'maintain' as const },
|
|
37
|
+
'get_users',
|
|
38
|
+
{
|
|
39
|
+
get_users: { method: 'GET', url: '/users' },
|
|
40
|
+
'/users': { method: 'GET', url: '/users' },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
])('creates url map with %s case conversion', (_, options, operationId, expected) => {
|
|
44
|
+
const schema: OpenAPIV3_1.Document = {
|
|
45
|
+
openapi: '3.1.0',
|
|
46
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
47
|
+
paths: {
|
|
48
|
+
'/users': {
|
|
49
|
+
get: {
|
|
50
|
+
operationId,
|
|
51
|
+
responses: {},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = createUrlMap(schema, options)
|
|
58
|
+
|
|
59
|
+
expect(result).toEqual(expected as Record<string, UrlMapValue>)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('converts path parameters based on convertCase', () => {
|
|
63
|
+
const schema: OpenAPIV3_1.Document = {
|
|
64
|
+
openapi: '3.1.0',
|
|
65
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
66
|
+
paths: {
|
|
67
|
+
'/users/{user_id}/posts/{post_id}': {
|
|
68
|
+
get: {
|
|
69
|
+
operationId: 'get_user_post',
|
|
70
|
+
responses: {},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = createUrlMap(schema, { convertCase: 'camel' })
|
|
77
|
+
|
|
78
|
+
expect(result).toEqual({
|
|
79
|
+
getUserPost: {
|
|
80
|
+
method: 'GET',
|
|
81
|
+
url: '/users/{userId}/posts/{postId}',
|
|
82
|
+
},
|
|
83
|
+
'/users/{userId}/posts/{postId}': {
|
|
84
|
+
method: 'GET',
|
|
85
|
+
url: '/users/{userId}/posts/{postId}',
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test.each([
|
|
91
|
+
['get', 'get_users', 'getUsers', 'GET'],
|
|
92
|
+
['post', 'create_user', 'createUser', 'POST'],
|
|
93
|
+
['put', 'update_user', 'updateUser', 'PUT'],
|
|
94
|
+
['delete', 'delete_user', 'deleteUser', 'DELETE'],
|
|
95
|
+
['patch', 'patch_user', 'patchUser', 'PATCH'],
|
|
96
|
+
])('handles %s HTTP method', (method, operationId, expectedKey, expectedMethod) => {
|
|
97
|
+
const schema: OpenAPIV3_1.Document = {
|
|
98
|
+
openapi: '3.1.0',
|
|
99
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
100
|
+
paths: {
|
|
101
|
+
'/users': {
|
|
102
|
+
[method]: {
|
|
103
|
+
operationId,
|
|
104
|
+
responses: {},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = createUrlMap(schema)
|
|
111
|
+
|
|
112
|
+
expect(result).toHaveProperty(expectedKey)
|
|
113
|
+
expect(result[expectedKey]?.method).toBe(
|
|
114
|
+
expectedMethod as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
|
|
115
|
+
)
|
|
116
|
+
expect(result).toHaveProperty('/users')
|
|
117
|
+
expect(result['/users']?.method).toBe(
|
|
118
|
+
expectedMethod as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('handles operation without operationId', () => {
|
|
123
|
+
const schema: OpenAPIV3_1.Document = {
|
|
124
|
+
openapi: '3.1.0',
|
|
125
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
126
|
+
paths: {
|
|
127
|
+
'/users': {
|
|
128
|
+
get: {
|
|
129
|
+
responses: {},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = createUrlMap(schema)
|
|
136
|
+
|
|
137
|
+
expect(result).toEqual({
|
|
138
|
+
'/users': {
|
|
139
|
+
method: 'GET',
|
|
140
|
+
url: '/users',
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
expect(result).not.toHaveProperty('getUsers')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('handles multiple paths', () => {
|
|
147
|
+
const schema: OpenAPIV3_1.Document = {
|
|
148
|
+
openapi: '3.1.0',
|
|
149
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
150
|
+
paths: {
|
|
151
|
+
'/users': {
|
|
152
|
+
get: {
|
|
153
|
+
operationId: 'get_users',
|
|
154
|
+
responses: {},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
'/posts': {
|
|
158
|
+
get: {
|
|
159
|
+
operationId: 'get_posts',
|
|
160
|
+
responses: {},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result = createUrlMap(schema)
|
|
167
|
+
|
|
168
|
+
expect(result).toHaveProperty('getUsers')
|
|
169
|
+
expect(result).toHaveProperty('getPosts')
|
|
170
|
+
expect(result).toHaveProperty('/users')
|
|
171
|
+
expect(result).toHaveProperty('/posts')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('handles empty paths', () => {
|
|
175
|
+
const schema: OpenAPIV3_1.Document = {
|
|
176
|
+
openapi: '3.1.0',
|
|
177
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
178
|
+
paths: {},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result = createUrlMap(schema)
|
|
182
|
+
|
|
183
|
+
expect(result).toEqual({})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('handles undefined paths', () => {
|
|
187
|
+
const schema: OpenAPIV3_1.Document = {
|
|
188
|
+
openapi: '3.1.0',
|
|
189
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
190
|
+
components: {},
|
|
191
|
+
paths: {},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const result = createUrlMap(schema)
|
|
195
|
+
|
|
196
|
+
expect(result).toEqual({})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('handles undefined pathItem', () => {
|
|
200
|
+
const schema: OpenAPIV3_1.Document = {
|
|
201
|
+
openapi: '3.1.0',
|
|
202
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
203
|
+
paths: {
|
|
204
|
+
'/users': undefined,
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = createUrlMap(schema)
|
|
209
|
+
|
|
210
|
+
expect(result).toEqual({})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
test('skips operations that do not exist', () => {
|
|
214
|
+
const schema: OpenAPIV3_1.Document = {
|
|
215
|
+
openapi: '3.1.0',
|
|
216
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
217
|
+
paths: {
|
|
218
|
+
'/users': {
|
|
219
|
+
get: {
|
|
220
|
+
operationId: 'get_users',
|
|
221
|
+
responses: {},
|
|
222
|
+
},
|
|
223
|
+
// post, put, delete, patch are not defined
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const result = createUrlMap(schema)
|
|
229
|
+
|
|
230
|
+
expect(result).toEqual({
|
|
231
|
+
getUsers: {
|
|
232
|
+
method: 'GET',
|
|
233
|
+
url: '/users',
|
|
234
|
+
},
|
|
235
|
+
'/users': {
|
|
236
|
+
method: 'GET',
|
|
237
|
+
url: '/users',
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('handles complex path with multiple parameters', () => {
|
|
243
|
+
const schema: OpenAPIV3_1.Document = {
|
|
244
|
+
openapi: '3.1.0',
|
|
245
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
246
|
+
paths: {
|
|
247
|
+
'/api/v1/users/{user_id}/posts/{post_id}/comments/{comment_id}': {
|
|
248
|
+
get: {
|
|
249
|
+
operationId: 'get_user_post_comment',
|
|
250
|
+
responses: {},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const result = createUrlMap(schema, { convertCase: 'snake' })
|
|
257
|
+
|
|
258
|
+
expect(result).toEqual({
|
|
259
|
+
get_user_post_comment: {
|
|
260
|
+
method: 'GET',
|
|
261
|
+
url: '/api/v1/users/{user_id}/posts/{post_id}/comments/{comment_id}',
|
|
262
|
+
},
|
|
263
|
+
'/api/v1/users/{user_id}/posts/{post_id}/comments/{comment_id}': {
|
|
264
|
+
method: 'GET',
|
|
265
|
+
url: '/api/v1/users/{user_id}/posts/{post_id}/comments/{comment_id}',
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test.each([
|
|
271
|
+
['camel', '/users/{userId}', '/users/{userId}'],
|
|
272
|
+
['snake', '/users/{user_id}', '/users/{user_id}'],
|
|
273
|
+
['pascal', '/users/{UserId}', '/users/{UserId}'],
|
|
274
|
+
])('converts path parameters with %s case: %s', (caseType, expectedPath, expectedUrl) => {
|
|
275
|
+
const schema: OpenAPIV3_1.Document = {
|
|
276
|
+
openapi: '3.1.0',
|
|
277
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
278
|
+
paths: {
|
|
279
|
+
'/users/{user_id}': {
|
|
280
|
+
get: {
|
|
281
|
+
operationId: 'get_user',
|
|
282
|
+
responses: {},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const result = createUrlMap(schema, {
|
|
289
|
+
convertCase: caseType as 'camel' | 'snake' | 'pascal',
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
expect(result[expectedPath]?.url).toBe(expectedUrl)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test.each([
|
|
296
|
+
['camel', 'getUserList'],
|
|
297
|
+
['snake', 'get_user_list'],
|
|
298
|
+
['pascal', 'GetUserList'],
|
|
299
|
+
])('converts operationId with %s case: %s', (caseType, expectedKey) => {
|
|
300
|
+
const schema: OpenAPIV3_1.Document = {
|
|
301
|
+
openapi: '3.1.0',
|
|
302
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
303
|
+
paths: {
|
|
304
|
+
'/users': {
|
|
305
|
+
get: {
|
|
306
|
+
operationId: 'get_user_list',
|
|
307
|
+
responses: {},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const result = createUrlMap(schema, {
|
|
314
|
+
convertCase: caseType as 'camel' | 'snake' | 'pascal',
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
expect(result).toHaveProperty(expectedKey)
|
|
318
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
import { wrapInterfaceKeyGuard } from '../wrap-interface-key-guard'
|
|
3
|
+
|
|
4
|
+
test.each([
|
|
5
|
+
['getUsers', 'getUsers'],
|
|
6
|
+
['createUser', 'createUser'],
|
|
7
|
+
['updateUser', 'updateUser'],
|
|
8
|
+
['deleteUser', 'deleteUser'],
|
|
9
|
+
['testKey', 'testKey'],
|
|
10
|
+
['camelCase', 'camelCase'],
|
|
11
|
+
['snake_case', 'snake_case'],
|
|
12
|
+
['PascalCase', 'PascalCase'],
|
|
13
|
+
] as const)('wrapInterfaceKeyGuard returns key as-is when no slash: %s -> %s', (key, expected) => {
|
|
14
|
+
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test.each([
|
|
18
|
+
['/users', '[`/users`]'],
|
|
19
|
+
['/users/{id}', '[`/users/{id}`]'],
|
|
20
|
+
['/api/v1/users', '[`/api/v1/users`]'],
|
|
21
|
+
['/users/{userId}/posts/{postId}', '[`/users/{userId}/posts/{postId}`]'],
|
|
22
|
+
['/api/v1/users/{id}/profile', '[`/api/v1/users/{id}/profile`]'],
|
|
23
|
+
] as const)('wrapInterfaceKeyGuard wraps key with backticks when slash present: %s -> %s', (key, expected) => {
|
|
24
|
+
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test.each([
|
|
28
|
+
['', ''],
|
|
29
|
+
['/', '[`/`]'],
|
|
30
|
+
['//', '[`//`]'],
|
|
31
|
+
['///', '[`///`]'],
|
|
32
|
+
] as const)('wrapInterfaceKeyGuard handles edge cases: %s -> %s', (key, expected) => {
|
|
33
|
+
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test.each([
|
|
37
|
+
['users/123', '[`users/123`]'],
|
|
38
|
+
['test/path/here', '[`test/path/here`]'],
|
|
39
|
+
['a/b/c/d', '[`a/b/c/d`]'],
|
|
40
|
+
] as const)('wrapInterfaceKeyGuard wraps key with multiple slashes: %s -> %s', (key, expected) => {
|
|
41
|
+
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
|
|
42
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { toCamel, toPascal, toSnake } from '@devup-api/utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert string based on convertCase option
|
|
5
|
+
*/
|
|
6
|
+
export function convertCase(
|
|
7
|
+
str: string,
|
|
8
|
+
caseType: 'snake' | 'camel' | 'pascal' | 'maintain' = 'camel',
|
|
9
|
+
): string {
|
|
10
|
+
switch (caseType) {
|
|
11
|
+
case 'snake':
|
|
12
|
+
return toSnake(str)
|
|
13
|
+
case 'camel':
|
|
14
|
+
return toCamel(str)
|
|
15
|
+
case 'pascal':
|
|
16
|
+
return toPascal(str)
|
|
17
|
+
case 'maintain':
|
|
18
|
+
return str
|
|
19
|
+
default:
|
|
20
|
+
return str
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DevupApiTypeGeneratorOptions, UrlMapValue } from '@devup-api/core'
|
|
2
|
+
import type { OpenAPIV3_1 } from 'openapi-types'
|
|
3
|
+
import { convertCase } from './convert-case'
|
|
4
|
+
|
|
5
|
+
export function createUrlMap(
|
|
6
|
+
schema: OpenAPIV3_1.Document,
|
|
7
|
+
options?: DevupApiTypeGeneratorOptions,
|
|
8
|
+
) {
|
|
9
|
+
const convertCaseType = options?.convertCase ?? 'camel'
|
|
10
|
+
const urlMap: Record<string, UrlMapValue> = {}
|
|
11
|
+
for (const [path, pathItem] of Object.entries(schema.paths ?? {})) {
|
|
12
|
+
if (!pathItem) continue
|
|
13
|
+
for (const method of ['get', 'post', 'put', 'delete', 'patch'] as const) {
|
|
14
|
+
const operation = pathItem[method]
|
|
15
|
+
if (!operation) continue
|
|
16
|
+
const normalizedPath = path.replace(/\{([^}]+)\}/g, (_, param) => {
|
|
17
|
+
// Convert param name based on case type
|
|
18
|
+
return `{${convertCase(param, convertCaseType)}}`
|
|
19
|
+
})
|
|
20
|
+
if (operation.operationId) {
|
|
21
|
+
urlMap[convertCase(operation.operationId, convertCaseType)] = {
|
|
22
|
+
method: method.toUpperCase() as
|
|
23
|
+
| 'GET'
|
|
24
|
+
| 'POST'
|
|
25
|
+
| 'PUT'
|
|
26
|
+
| 'DELETE'
|
|
27
|
+
| 'PATCH',
|
|
28
|
+
url: normalizedPath,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
urlMap[normalizedPath] = {
|
|
32
|
+
method: method.toUpperCase() as
|
|
33
|
+
| 'GET'
|
|
34
|
+
| 'POST'
|
|
35
|
+
| 'PUT'
|
|
36
|
+
| 'DELETE'
|
|
37
|
+
| 'PATCH',
|
|
38
|
+
url: normalizedPath,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return urlMap
|
|
43
|
+
}
|