genoc 0.1.1 → 0.1.3
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 +1 -1
- package/dist/analyzer/path-analyzer.js +2 -1
- package/dist/analyzer/schema-mapper.js +4 -2
- package/dist/generator/client-generator.js +3 -3
- package/dist/generator/contracts-generator.js +42 -24
- package/dist/generator/error-types.d.ts +4 -5
- package/dist/generator/error-types.js +4 -11
- package/dist/generator/method-generator.js +10 -15
- package/dist/utils/generator-helpers.d.ts +18 -0
- package/dist/utils/generator-helpers.js +52 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RefResolver } from '../parser/ref-resolver.js';
|
|
2
|
+
import { sanitizeTypeName } from '../utils/generator-helpers.js';
|
|
2
3
|
import { getMethodName } from './naming.js';
|
|
3
4
|
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'];
|
|
4
5
|
function isRef(obj) {
|
|
@@ -21,7 +22,7 @@ function schemaToTsType(schema, resolver) {
|
|
|
21
22
|
if (isRef(schema)) {
|
|
22
23
|
const resolved = resolver.resolve(schema);
|
|
23
24
|
const refStr = schema.$ref;
|
|
24
|
-
const lastSegment = refStr.split('/').pop();
|
|
25
|
+
const lastSegment = sanitizeTypeName(refStr.split('/').pop());
|
|
25
26
|
if (lastSegment && resolved.type) {
|
|
26
27
|
return lastSegment;
|
|
27
28
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RefResolver } from '../parser/ref-resolver.js';
|
|
2
2
|
import { formatToBrandTypeName } from '../utils/case.js';
|
|
3
|
+
import { sanitizeTypeName } from '../utils/generator-helpers.js';
|
|
3
4
|
import { quoteKey } from '../utils/string.js';
|
|
4
5
|
/**
|
|
5
6
|
* Default: extracts the last segment of the JSON pointer.
|
|
@@ -7,7 +8,8 @@ import { quoteKey } from '../utils/string.js';
|
|
|
7
8
|
*/
|
|
8
9
|
function defaultTypeNameGenerator(refString) {
|
|
9
10
|
const segments = refString.split('/');
|
|
10
|
-
|
|
11
|
+
const rawSegment = segments[segments.length - 1] || 'unknown';
|
|
12
|
+
return sanitizeTypeName(rawSegment);
|
|
11
13
|
}
|
|
12
14
|
function isRefObject(obj) {
|
|
13
15
|
return (obj !== null &&
|
|
@@ -230,7 +232,7 @@ export class SchemaMapper {
|
|
|
230
232
|
}
|
|
231
233
|
}
|
|
232
234
|
resolveDiscriminatorInfo(schema, refStr) {
|
|
233
|
-
const schemaName = refStr.split('/').pop();
|
|
235
|
+
const schemaName = sanitizeTypeName(refStr.split('/').pop());
|
|
234
236
|
const target = this.discriminatorTargets.get(schemaName);
|
|
235
237
|
if (target)
|
|
236
238
|
return target;
|
|
@@ -89,12 +89,12 @@ function buildClientMethodBody(op) {
|
|
|
89
89
|
if (op.queryParams.length > 0) {
|
|
90
90
|
opts.push('query');
|
|
91
91
|
}
|
|
92
|
-
if (op.headerParams.length > 0) {
|
|
93
|
-
opts.push('headers');
|
|
94
|
-
}
|
|
95
92
|
if (op.requestBody) {
|
|
96
93
|
opts.push('body');
|
|
97
94
|
}
|
|
95
|
+
if (op.headerParams.length > 0) {
|
|
96
|
+
opts.push('headers');
|
|
97
|
+
}
|
|
98
98
|
const lines = [];
|
|
99
99
|
if (op.requestBody?.isMultipart && op.requestBody.schema) {
|
|
100
100
|
const schema = op.requestBody.schema;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { analyzePaths } from '../analyzer/path-analyzer.js';
|
|
2
2
|
import { SchemaMapper } from '../analyzer/schema-mapper.js';
|
|
3
3
|
import { RefResolver } from '../parser/ref-resolver.js';
|
|
4
|
-
import { toPascalCase, getOperationTypePrefix, makeHeader } from '../utils/generator-helpers.js';
|
|
4
|
+
import { RESERVED_TYPE_NAMES, buildSchemaRenameMap, sanitizeTypeName, toPascalCase, getOperationTypePrefix, makeHeader, } from '../utils/generator-helpers.js';
|
|
5
5
|
function isBinaryContentType(ct) {
|
|
6
6
|
if (ct === 'application/octet-stream')
|
|
7
7
|
return true;
|
|
@@ -17,22 +17,24 @@ function isBinaryContentType(ct) {
|
|
|
17
17
|
* If the schema is a $ref to a discriminated base type (or an array whose items
|
|
18
18
|
* are), replace the type name with the {Base}Variant union type.
|
|
19
19
|
*/
|
|
20
|
-
function substituteDiscriminatedType(tsType, schema, discriminatorInfo) {
|
|
20
|
+
function substituteDiscriminatedType(tsType, schema, discriminatorInfo, renameMap) {
|
|
21
21
|
const refSchema = schema;
|
|
22
22
|
if (!refSchema || typeof refSchema !== 'object')
|
|
23
23
|
return tsType;
|
|
24
24
|
if (typeof refSchema.$ref === 'string') {
|
|
25
|
-
const schemaName = refSchema.$ref.split('/').pop();
|
|
25
|
+
const schemaName = sanitizeTypeName(refSchema.$ref.split('/').pop());
|
|
26
26
|
if (schemaName && discriminatorInfo.has(schemaName)) {
|
|
27
|
-
|
|
27
|
+
const renamed = renameMap.get(schemaName) ?? schemaName;
|
|
28
|
+
return tsType.replace(new RegExp(`\\b${renamed}\\b`, 'g'), `${renamed}Variant`);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
if (refSchema.items && typeof refSchema.items === 'object') {
|
|
31
32
|
const items = refSchema.items;
|
|
32
33
|
if (typeof items.$ref === 'string') {
|
|
33
|
-
const schemaName = items.$ref.split('/').pop();
|
|
34
|
+
const schemaName = sanitizeTypeName(items.$ref.split('/').pop());
|
|
34
35
|
if (schemaName && discriminatorInfo.has(schemaName)) {
|
|
35
|
-
|
|
36
|
+
const renamed = renameMap.get(schemaName) ?? schemaName;
|
|
37
|
+
return tsType.replace(new RegExp(`\\b${renamed}\\b`, 'g'), `${renamed}Variant`);
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -162,6 +164,19 @@ function topologicalSort(entries, allNames) {
|
|
|
162
164
|
* 7b. UnspecifiedApiError class
|
|
163
165
|
*/
|
|
164
166
|
export function generateContracts(doc, resolver) {
|
|
167
|
+
const schemaNameList = doc.components?.schemas ? Object.keys(doc.components.schemas) : [];
|
|
168
|
+
const renameMap = buildSchemaRenameMap(schemaNameList, RESERVED_TYPE_NAMES);
|
|
169
|
+
for (const [original, renamed] of renameMap) {
|
|
170
|
+
// TODO: Replace with structured logging solution
|
|
171
|
+
// oxlint-disable-next-line no-console
|
|
172
|
+
console.warn(`Warning: Schema "${original}" collides with a built-in type and was renamed to "${renamed}".`);
|
|
173
|
+
}
|
|
174
|
+
const renamingTypeGenerator = (refString) => {
|
|
175
|
+
const segments = refString.split('/');
|
|
176
|
+
const rawSegment = segments[segments.length - 1] || 'unknown';
|
|
177
|
+
const lastSegment = sanitizeTypeName(rawSegment);
|
|
178
|
+
return renameMap.get(lastSegment) ?? lastSegment;
|
|
179
|
+
};
|
|
165
180
|
const discriminatorInfo = new Map();
|
|
166
181
|
if (doc.components?.schemas) {
|
|
167
182
|
for (const [name, schema] of Object.entries(doc.components.schemas)) {
|
|
@@ -170,11 +185,12 @@ export function generateContracts(doc, resolver) {
|
|
|
170
185
|
const mapping = new Map();
|
|
171
186
|
if (resolved.discriminator.mapping) {
|
|
172
187
|
for (const [key, ref] of Object.entries(resolved.discriminator.mapping)) {
|
|
173
|
-
const targetName = ref.split('/').pop() || key;
|
|
174
|
-
|
|
188
|
+
const targetName = sanitizeTypeName(ref.split('/').pop() || key);
|
|
189
|
+
const renamedTarget = renameMap.get(targetName) ?? targetName;
|
|
190
|
+
mapping.set(key, renamedTarget);
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
|
-
discriminatorInfo.set(name, {
|
|
193
|
+
discriminatorInfo.set(sanitizeTypeName(name), {
|
|
178
194
|
propertyName: resolved.discriminator.propertyName,
|
|
179
195
|
mapping,
|
|
180
196
|
});
|
|
@@ -190,25 +206,26 @@ export function generateContracts(doc, resolver) {
|
|
|
190
206
|
});
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
|
-
// reservedNames: prevent branded types from colliding with user-defined schema names
|
|
194
209
|
const allSchemaNames = new Set();
|
|
195
210
|
if (doc.components?.schemas) {
|
|
196
211
|
for (const name of Object.keys(doc.components.schemas)) {
|
|
197
|
-
allSchemaNames.add(name);
|
|
212
|
+
allSchemaNames.add(sanitizeTypeName(name));
|
|
198
213
|
}
|
|
199
214
|
}
|
|
200
|
-
const mapper = new SchemaMapper(resolver,
|
|
215
|
+
const mapper = new SchemaMapper(resolver, renamingTypeGenerator, discriminatorTargets, allSchemaNames);
|
|
201
216
|
const lines = [];
|
|
202
217
|
lines.push(makeHeader(doc.openapi));
|
|
203
218
|
// Section 1: Schema types
|
|
204
219
|
const schemaEntries = [];
|
|
205
220
|
if (doc.components?.schemas) {
|
|
206
221
|
for (const [name, schema] of Object.entries(doc.components.schemas)) {
|
|
207
|
-
const
|
|
222
|
+
const sanitizedName = sanitizeTypeName(name);
|
|
223
|
+
const renamedName = renameMap.get(sanitizedName) ?? sanitizedName;
|
|
224
|
+
const result = mapper.mapSchema(schema, sanitizedName);
|
|
208
225
|
const resolved = resolver.resolve(schema);
|
|
209
|
-
const definition = `export type ${
|
|
226
|
+
const definition = `export type ${renamedName} = ${result.tsType};`;
|
|
210
227
|
schemaEntries.push({
|
|
211
|
-
name,
|
|
228
|
+
name: renamedName,
|
|
212
229
|
kind: 'type',
|
|
213
230
|
definition,
|
|
214
231
|
jsDoc: buildJsDoc(resolved.description),
|
|
@@ -228,9 +245,10 @@ export function generateContracts(doc, resolver) {
|
|
|
228
245
|
if (subtypeNames.length === 0)
|
|
229
246
|
continue;
|
|
230
247
|
const unionType = subtypeNames.join(' | ');
|
|
248
|
+
const renamedBase = renameMap.get(sanitizeTypeName(baseName)) ?? sanitizeTypeName(baseName);
|
|
231
249
|
lines.push('');
|
|
232
|
-
lines.push(`export type ${
|
|
233
|
-
allSchemaNames.add(`${
|
|
250
|
+
lines.push(`export type ${renamedBase}Variant = ${unionType};`);
|
|
251
|
+
allSchemaNames.add(`${renamedBase}Variant`);
|
|
234
252
|
}
|
|
235
253
|
// Section 1b: Security scheme types
|
|
236
254
|
const securitySchemes = doc.components?.securitySchemes;
|
|
@@ -376,7 +394,7 @@ export function generateContracts(doc, resolver) {
|
|
|
376
394
|
return 'StreamResponse';
|
|
377
395
|
if (r.schema) {
|
|
378
396
|
const result = mapper.mapSchema(r.schema, undefined, 'response').tsType;
|
|
379
|
-
return substituteDiscriminatedType(result, r.schema, discriminatorInfo);
|
|
397
|
+
return substituteDiscriminatedType(result, r.schema, discriminatorInfo, renameMap);
|
|
380
398
|
}
|
|
381
399
|
return r.tsType;
|
|
382
400
|
});
|
|
@@ -442,16 +460,16 @@ export function generateContracts(doc, resolver) {
|
|
|
442
460
|
lines.push(' constructor(');
|
|
443
461
|
lines.push(' public readonly data: ReadableStream<Uint8Array>,');
|
|
444
462
|
lines.push(' public readonly filename?: string,');
|
|
445
|
-
lines.push(' public readonly headers:
|
|
463
|
+
lines.push(' public readonly headers: Record<string, string> = {},');
|
|
446
464
|
lines.push(' ) {}');
|
|
447
465
|
lines.push('}');
|
|
448
466
|
lines.push('');
|
|
449
467
|
lines.push(`export function streamResponse(
|
|
450
468
|
data: ReadableStream<Uint8Array>,
|
|
451
469
|
filename?: string,
|
|
452
|
-
headers?:
|
|
470
|
+
headers?: Record<string, string>,
|
|
453
471
|
): StreamResponse {
|
|
454
|
-
return new StreamResponse(data, filename, headers ??
|
|
472
|
+
return new StreamResponse(data, filename, headers ?? {});
|
|
455
473
|
}`);
|
|
456
474
|
// ErrorResponse class
|
|
457
475
|
lines.push('');
|
|
@@ -459,7 +477,7 @@ export function generateContracts(doc, resolver) {
|
|
|
459
477
|
constructor(
|
|
460
478
|
public readonly status: number,
|
|
461
479
|
public readonly data: unknown,
|
|
462
|
-
public readonly headers:
|
|
480
|
+
public readonly headers: Record<string, string>,
|
|
463
481
|
public readonly message?: string,
|
|
464
482
|
) {}
|
|
465
483
|
}`);
|
|
@@ -468,10 +486,10 @@ export function generateContracts(doc, resolver) {
|
|
|
468
486
|
lines.push(`export function errorResponse(
|
|
469
487
|
status: number,
|
|
470
488
|
data: unknown,
|
|
471
|
-
headers?:
|
|
489
|
+
headers?: Record<string, string>,
|
|
472
490
|
message?: string,
|
|
473
491
|
): ErrorResponse {
|
|
474
|
-
return new ErrorResponse(status, data, headers ??
|
|
492
|
+
return new ErrorResponse(status, data, headers ?? {}, message);
|
|
475
493
|
}`);
|
|
476
494
|
// RequesterFailError class
|
|
477
495
|
lines.push('');
|
|
@@ -15,10 +15,9 @@ export declare class DefaultApiError<TData> extends Error {
|
|
|
15
15
|
* Produces:
|
|
16
16
|
* 1. `ApiError<TStatus, TData>` class
|
|
17
17
|
* 2. `UnspecifiedApiError` class (extends ApiError, for status codes not in spec)
|
|
18
|
-
* 3.
|
|
19
|
-
* 4. Per-operation error
|
|
20
|
-
* 5.
|
|
21
|
-
* 6. `
|
|
22
|
-
* 7. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
|
|
18
|
+
* 3. Per-operation error union types (e.g. `GetApiV1ProductsErrors`)
|
|
19
|
+
* 4. Per-operation per-status error type aliases (e.g. `GetApiV1ProductsError400`)
|
|
20
|
+
* 5. `DefaultErrorBody` type for `default` responses
|
|
21
|
+
* 6. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
|
|
23
22
|
*/
|
|
24
23
|
export declare function generateErrorTypes(operations: AnalyzedOperation[]): string;
|
|
@@ -24,11 +24,10 @@ export class DefaultApiError extends Error {
|
|
|
24
24
|
* Produces:
|
|
25
25
|
* 1. `ApiError<TStatus, TData>` class
|
|
26
26
|
* 2. `UnspecifiedApiError` class (extends ApiError, for status codes not in spec)
|
|
27
|
-
* 3.
|
|
28
|
-
* 4. Per-operation error
|
|
29
|
-
* 5.
|
|
30
|
-
* 6. `
|
|
31
|
-
* 7. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
|
|
27
|
+
* 3. Per-operation error union types (e.g. `GetApiV1ProductsErrors`)
|
|
28
|
+
* 4. Per-operation per-status error type aliases (e.g. `GetApiV1ProductsError400`)
|
|
29
|
+
* 5. `DefaultErrorBody` type for `default` responses
|
|
30
|
+
* 6. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
|
|
32
31
|
*/
|
|
33
32
|
export function generateErrorTypes(operations) {
|
|
34
33
|
const lines = [];
|
|
@@ -54,12 +53,6 @@ export function generateErrorTypes(operations) {
|
|
|
54
53
|
}
|
|
55
54
|
}`);
|
|
56
55
|
lines.push('');
|
|
57
|
-
lines.push(`export function isError<T extends { status: number }, S extends number>(
|
|
58
|
-
response: T,
|
|
59
|
-
status: S,
|
|
60
|
-
): response is Extract<T, { status: S }> {
|
|
61
|
-
return response.status === status;
|
|
62
|
-
}`);
|
|
63
56
|
let needsDefaultErrorBody = false;
|
|
64
57
|
for (const op of operations) {
|
|
65
58
|
const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
|
|
@@ -7,7 +7,7 @@ function buildParameters(op) {
|
|
|
7
7
|
if (op.queryParams.length > 0) {
|
|
8
8
|
const prefix = getOperationTypePrefix(op);
|
|
9
9
|
const allOptional = op.queryParams.every((p) => !p.required);
|
|
10
|
-
const hasRequiredAfter = op.headerParams.some((p) => p.required)
|
|
10
|
+
const hasRequiredAfter = !!op.requestBody?.required || op.headerParams.some((p) => p.required);
|
|
11
11
|
if (allOptional && hasRequiredAfter) {
|
|
12
12
|
// All optional query params + required param after: use explicit undefined to avoid "required param cannot follow optional" error
|
|
13
13
|
params.push(`query: ${prefix}Query | undefined`);
|
|
@@ -18,22 +18,17 @@ function buildParameters(op) {
|
|
|
18
18
|
params.push(`query${optional}: ${prefix}Query`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
if (op.headerParams.length > 0) {
|
|
22
|
-
const prefix = getOperationTypePrefix(op);
|
|
23
|
-
const allOptional = op.headerParams.every((p) => !p.required);
|
|
24
|
-
if (allOptional && op.requestBody?.required) {
|
|
25
|
-
params.push(`headers: ${prefix}Headers | undefined`);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
const optional = allOptional ? '?' : '';
|
|
29
|
-
params.push(`headers${optional}: ${prefix}Headers`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
21
|
if (op.requestBody) {
|
|
33
22
|
const prefix = getOperationTypePrefix(op);
|
|
34
23
|
const optional = op.requestBody.required ? '' : '?';
|
|
35
24
|
params.push(`body${optional}: ${prefix}Body`);
|
|
36
25
|
}
|
|
26
|
+
if (op.headerParams.length > 0) {
|
|
27
|
+
const prefix = getOperationTypePrefix(op);
|
|
28
|
+
const allOptional = op.headerParams.every((p) => !p.required);
|
|
29
|
+
const optional = allOptional ? '?' : '';
|
|
30
|
+
params.push(`headers${optional}: ${prefix}Headers`);
|
|
31
|
+
}
|
|
37
32
|
return params.join(', ');
|
|
38
33
|
}
|
|
39
34
|
function buildJsDoc(op) {
|
|
@@ -117,12 +112,12 @@ function buildImplementation(op) {
|
|
|
117
112
|
if (op.queryParams.length > 0) {
|
|
118
113
|
requesterOpts.push('query');
|
|
119
114
|
}
|
|
120
|
-
if (op.headerParams.length > 0) {
|
|
121
|
-
requesterOpts.push('headers');
|
|
122
|
-
}
|
|
123
115
|
if (op.requestBody) {
|
|
124
116
|
requesterOpts.push('body');
|
|
125
117
|
}
|
|
118
|
+
if (op.headerParams.length > 0) {
|
|
119
|
+
requesterOpts.push('headers');
|
|
120
|
+
}
|
|
126
121
|
// Error responses for status-specific checks
|
|
127
122
|
const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
|
|
128
123
|
// Lines for inside `if (result instanceof ErrorResponse) { ... }`
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import type { AnalyzedOperation } from '../analyzer/path-analyzer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reserved type names used by the generated output's built-in classes,
|
|
4
|
+
* functions, and types. User-defined schema names that collide with these
|
|
5
|
+
* are automatically renamed with a suffix to prevent duplicate identifiers.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RESERVED_TYPE_NAMES: ReadonlySet<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Sanitize a schema type name by converting dot-separated segments into
|
|
10
|
+
* PascalCase. Names without dots pass through unchanged.
|
|
11
|
+
*
|
|
12
|
+
* "Api.Error" → "ApiError", "Simple" → "Simple"
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeTypeName(name: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build a mapping of sanitized schema names → renamed names for any that
|
|
17
|
+
* collide with reserved type names.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildSchemaRenameMap(schemaNames: Iterable<string>, reserved: ReadonlySet<string>, suffix?: string): Map<string, string>;
|
|
2
20
|
/**
|
|
3
21
|
* Convert a string to PascalCase, handling camelCase, kebab-case, snake_case,
|
|
4
22
|
* and colon-separated segments.
|
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reserved type names used by the generated output's built-in classes,
|
|
3
|
+
* functions, and types. User-defined schema names that collide with these
|
|
4
|
+
* are automatically renamed with a suffix to prevent duplicate identifiers.
|
|
5
|
+
*/
|
|
6
|
+
export const RESERVED_TYPE_NAMES = new Set([
|
|
7
|
+
// Classes in contracts.ts
|
|
8
|
+
'StreamResponse',
|
|
9
|
+
'ErrorResponse',
|
|
10
|
+
'ApiError',
|
|
11
|
+
'UnspecifiedApiError',
|
|
12
|
+
'DefaultApiError',
|
|
13
|
+
'RequesterFailError',
|
|
14
|
+
// Functions in contracts.ts
|
|
15
|
+
'streamResponse',
|
|
16
|
+
'errorResponse',
|
|
17
|
+
// Types / functions in client.ts
|
|
18
|
+
'Requester',
|
|
19
|
+
'isDefinedError',
|
|
20
|
+
'decorateWithErrors',
|
|
21
|
+
'ApiClient',
|
|
22
|
+
// Interface in contracts.ts (when file uploads present)
|
|
23
|
+
'FileInput',
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize a schema type name by converting dot-separated segments into
|
|
27
|
+
* PascalCase. Names without dots pass through unchanged.
|
|
28
|
+
*
|
|
29
|
+
* "Api.Error" → "ApiError", "Simple" → "Simple"
|
|
30
|
+
*/
|
|
31
|
+
export function sanitizeTypeName(name) {
|
|
32
|
+
if (!name.includes('.'))
|
|
33
|
+
return name;
|
|
34
|
+
return name
|
|
35
|
+
.split('.')
|
|
36
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
37
|
+
.join('');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build a mapping of sanitized schema names → renamed names for any that
|
|
41
|
+
* collide with reserved type names.
|
|
42
|
+
*/
|
|
43
|
+
export function buildSchemaRenameMap(schemaNames, reserved, suffix = 'Model') {
|
|
44
|
+
const renameMap = new Map();
|
|
45
|
+
for (const name of schemaNames) {
|
|
46
|
+
const sanitized = sanitizeTypeName(name);
|
|
47
|
+
if (reserved.has(sanitized)) {
|
|
48
|
+
renameMap.set(sanitized, `${sanitized}${suffix}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return renameMap;
|
|
52
|
+
}
|
|
1
53
|
/**
|
|
2
54
|
* Convert a string to PascalCase, handling camelCase, kebab-case, snake_case,
|
|
3
55
|
* and colon-separated segments.
|