@techspokes/typescript-wsdl-client 0.6.2 → 0.7.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 +242 -166
- package/dist/cli.js +223 -1
- package/dist/compiler/schemaCompiler.d.ts +54 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +74 -7
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/emit/catalogEmitter.d.ts +18 -0
- package/dist/emit/catalogEmitter.d.ts.map +1 -1
- package/dist/emit/catalogEmitter.js +31 -0
- package/dist/emit/clientEmitter.d.ts +17 -0
- package/dist/emit/clientEmitter.d.ts.map +1 -1
- package/dist/emit/clientEmitter.js +33 -3
- package/dist/emit/typesEmitter.d.ts +16 -5
- package/dist/emit/typesEmitter.d.ts.map +1 -1
- package/dist/emit/typesEmitter.js +30 -5
- package/dist/emit/utilsEmitter.d.ts +18 -0
- package/dist/emit/utilsEmitter.d.ts.map +1 -1
- package/dist/emit/utilsEmitter.js +30 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -1
- package/dist/loader/fetch.d.ts +31 -0
- package/dist/loader/fetch.d.ts.map +1 -1
- package/dist/loader/fetch.js +31 -0
- package/dist/loader/wsdlLoader.d.ts +32 -0
- package/dist/loader/wsdlLoader.d.ts.map +1 -1
- package/dist/loader/wsdlLoader.js +80 -9
- package/dist/openapi/buildPaths.d.ts +74 -0
- package/dist/openapi/buildPaths.d.ts.map +1 -0
- package/dist/openapi/buildPaths.js +66 -0
- package/dist/openapi/buildSchemas.d.ts +44 -0
- package/dist/openapi/buildSchemas.d.ts.map +1 -0
- package/dist/openapi/buildSchemas.js +207 -0
- package/dist/openapi/casing.d.ts +38 -0
- package/dist/openapi/casing.d.ts.map +1 -0
- package/dist/openapi/casing.js +49 -0
- package/dist/openapi/generateOpenAPI.d.ts +57 -0
- package/dist/openapi/generateOpenAPI.d.ts.map +1 -0
- package/dist/openapi/generateOpenAPI.js +174 -0
- package/dist/openapi/security.d.ts +82 -0
- package/dist/openapi/security.d.ts.map +1 -0
- package/dist/openapi/security.js +145 -0
- package/dist/pipeline.d.ts +37 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +72 -0
- package/dist/util/tools.d.ts +100 -7
- package/dist/util/tools.d.ts.map +1 -1
- package/dist/util/tools.js +85 -7
- package/dist/xsd/primitives.d.ts +33 -0
- package/dist/xsd/primitives.d.ts.map +1 -1
- package/dist/xsd/primitives.js +59 -7
- package/package.json +7 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { toPathSegment } from "./casing.js";
|
|
2
|
+
export function buildPaths(compiled, opts) {
|
|
3
|
+
const paths = {};
|
|
4
|
+
const base = normalizeBase(opts.basePath || "/");
|
|
5
|
+
for (const op of compiled.operations) {
|
|
6
|
+
const seg = toPathSegment(op.name, opts.pathStyle);
|
|
7
|
+
const fullPath = (base.endsWith("/") ? base.slice(0, -1) : base) + "/" + seg;
|
|
8
|
+
const override = opts.overrides?.[op.name] || {};
|
|
9
|
+
const method = (override.method || opts.defaultMethod || "post").toLowerCase();
|
|
10
|
+
const tag = opts.tagsMap?.[op.name] || opts.defaultTag;
|
|
11
|
+
const inputRef = op.inputElement?.local ? { $ref: `#/components/schemas/${op.inputElement.local}` } : { type: "object" };
|
|
12
|
+
const outputRef = op.outputElement?.local ? { $ref: `#/components/schemas/${op.outputElement.local}` } : { type: "object" };
|
|
13
|
+
const parameters = [];
|
|
14
|
+
// Header parameters from security builder
|
|
15
|
+
const headerParamNames = opts.opHeaderParameters[op.name] || [];
|
|
16
|
+
for (const pName of headerParamNames) {
|
|
17
|
+
parameters.push({ $ref: `#/components/parameters/${pName}` });
|
|
18
|
+
}
|
|
19
|
+
const operationObject = {
|
|
20
|
+
operationId: op.name,
|
|
21
|
+
tags: [tag],
|
|
22
|
+
requestBody: {
|
|
23
|
+
required: true,
|
|
24
|
+
content: {
|
|
25
|
+
"application/json": { schema: inputRef }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
responses: {
|
|
29
|
+
"200": {
|
|
30
|
+
description: "Successful SOAP operation response",
|
|
31
|
+
content: {
|
|
32
|
+
"application/json": { schema: outputRef }
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
default: {
|
|
36
|
+
description: "Error response",
|
|
37
|
+
content: {
|
|
38
|
+
"application/json": { schema: { $ref: "#/components/schemas/ErrorEnvelope" } }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
if (override.summary)
|
|
44
|
+
operationObject.summary = override.summary;
|
|
45
|
+
if (override.description)
|
|
46
|
+
operationObject.description = override.description;
|
|
47
|
+
if (override.deprecated)
|
|
48
|
+
operationObject.deprecated = true;
|
|
49
|
+
if (parameters.length)
|
|
50
|
+
operationObject.parameters = parameters;
|
|
51
|
+
const opSec = opts.opSecurity[op.name];
|
|
52
|
+
if (opSec)
|
|
53
|
+
operationObject.security = opSec;
|
|
54
|
+
if (!paths[fullPath])
|
|
55
|
+
paths[fullPath] = {};
|
|
56
|
+
paths[fullPath][method] = operationObject;
|
|
57
|
+
}
|
|
58
|
+
return paths;
|
|
59
|
+
}
|
|
60
|
+
function normalizeBase(base) {
|
|
61
|
+
if (!base.startsWith("/"))
|
|
62
|
+
base = "/" + base;
|
|
63
|
+
if (base.endsWith("/"))
|
|
64
|
+
return base;
|
|
65
|
+
return base;
|
|
66
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI Schema Component Builder
|
|
3
|
+
*
|
|
4
|
+
* This module transforms the compiled TypeScript types from the WSDL catalog
|
|
5
|
+
* into OpenAPI 3.1 schema components. It handles the conversion of complex types,
|
|
6
|
+
* type aliases, arrays, and primitive types to their JSON Schema equivalents.
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Converts TypeScript interfaces to JSON Schema objects
|
|
10
|
+
* - Handles type aliases and enumerations
|
|
11
|
+
* - Supports array types with appropriate item definitions
|
|
12
|
+
* - Manages property requirements based on XML schema constraints
|
|
13
|
+
* - Optionally enforces closed schemas with additionalProperties:false
|
|
14
|
+
* - Can prune schemas that aren't referenced by any operation
|
|
15
|
+
*/
|
|
16
|
+
import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
|
|
17
|
+
/**
|
|
18
|
+
* Options for building OpenAPI schema components
|
|
19
|
+
*
|
|
20
|
+
* @interface BuildSchemasOptions
|
|
21
|
+
* @property {boolean} [closedSchemas] - Whether to add additionalProperties:false to object schemas
|
|
22
|
+
* @property {boolean} [pruneUnusedSchemas] - Whether to exclude schemas not referenced by operations
|
|
23
|
+
*/
|
|
24
|
+
export interface BuildSchemasOptions {
|
|
25
|
+
closedSchemas?: boolean;
|
|
26
|
+
pruneUnusedSchemas?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export type ComponentsSchemas = Record<string, any>;
|
|
29
|
+
/**
|
|
30
|
+
* Transforms the compiled WSDL catalog into OpenAPI schema components
|
|
31
|
+
*
|
|
32
|
+
* @function buildSchemas
|
|
33
|
+
* @param {CompiledCatalog} compiled - The compiled WSDL catalog containing types, aliases, and operations
|
|
34
|
+
* @param {BuildSchemasOptions} opts - Options for schema generation
|
|
35
|
+
* @returns {ComponentsSchemas} - A record of schema component names to their JSON Schema definitions
|
|
36
|
+
*
|
|
37
|
+
* @throws Will throw an error if there are unknown referenced types or bases while building schemas
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Example usage: building schemas with closed schemas enforcement and unused schema pruning
|
|
41
|
+
* const schemas = buildSchemas(compiledCatalog, { closedSchemas: true, pruneUnusedSchemas: true });
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildSchemas(compiled: CompiledCatalog, opts: BuildSchemasOptions): ComponentsSchemas;
|
|
44
|
+
//# sourceMappingURL=buildSchemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildSchemas.d.ts","sourceRoot":"","sources":["../../src/openapi/buildSchemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAgB,eAAe,EAAe,MAAM,+BAA+B,CAAC;AAEhG;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAmIpD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,CA0DpG"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
function isLiteralUnion(ts) {
|
|
2
|
+
// very naive: split by | and ensure each trimmed starts and ends with quotes
|
|
3
|
+
const parts = ts.split("|").map(p => p.trim()).filter(Boolean);
|
|
4
|
+
if (!parts.length)
|
|
5
|
+
return null;
|
|
6
|
+
if (parts.every(p => /^".*"$/.test(p))) {
|
|
7
|
+
return parts.map(p => p.slice(1, -1));
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function primitiveSchema(ts) {
|
|
12
|
+
switch (ts) {
|
|
13
|
+
case "string":
|
|
14
|
+
return { type: "string" };
|
|
15
|
+
case "number":
|
|
16
|
+
return { type: "number" };
|
|
17
|
+
case "boolean":
|
|
18
|
+
return { type: "boolean" };
|
|
19
|
+
case "any":
|
|
20
|
+
return {};
|
|
21
|
+
default:
|
|
22
|
+
if (ts.endsWith("[]")) {
|
|
23
|
+
return { type: "array", items: primitiveSchema(ts.slice(0, -2)) };
|
|
24
|
+
}
|
|
25
|
+
return { $ref: `#/components/schemas/${ts}` };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function buildAliasSchema(a) {
|
|
29
|
+
const lit = isLiteralUnion(a.tsType);
|
|
30
|
+
if (lit) {
|
|
31
|
+
return { type: "string", enum: lit };
|
|
32
|
+
}
|
|
33
|
+
// If alias wraps primitive
|
|
34
|
+
if (["string", "number", "boolean", "any"].includes(a.tsType) || a.tsType.endsWith("[]")) {
|
|
35
|
+
return primitiveSchema(a.tsType);
|
|
36
|
+
}
|
|
37
|
+
// alias of another complex/alias type -> allOf wrapper preserves name
|
|
38
|
+
return { allOf: [{ $ref: `#/components/schemas/${a.tsType}` }] };
|
|
39
|
+
}
|
|
40
|
+
function isArrayWrapper(t) {
|
|
41
|
+
if (t.attrs.length !== 0)
|
|
42
|
+
return null;
|
|
43
|
+
if (t.elems.length !== 1)
|
|
44
|
+
return null;
|
|
45
|
+
const e = t.elems[0];
|
|
46
|
+
if (e.max !== "unbounded" && !(e.max > 1))
|
|
47
|
+
return null;
|
|
48
|
+
return { itemType: e.tsType };
|
|
49
|
+
}
|
|
50
|
+
function buildComplexSchema(t, closed, knownTypeNames, aliasNames) {
|
|
51
|
+
// Use knownTypeNames/aliasNames to validate $ref targets so we surface
|
|
52
|
+
// compiler issues early instead of emitting dangling references in OpenAPI output.
|
|
53
|
+
function refOrPrimitive(ts) {
|
|
54
|
+
switch (ts) {
|
|
55
|
+
case "string":
|
|
56
|
+
return { type: "string" };
|
|
57
|
+
case "number":
|
|
58
|
+
return { type: "number" };
|
|
59
|
+
case "boolean":
|
|
60
|
+
return { type: "boolean" };
|
|
61
|
+
case "any":
|
|
62
|
+
return {}; // intentionally permissive
|
|
63
|
+
default:
|
|
64
|
+
if (ts.endsWith("[]")) {
|
|
65
|
+
const inner = ts.slice(0, -2);
|
|
66
|
+
return { type: "array", items: refOrPrimitive(inner) };
|
|
67
|
+
}
|
|
68
|
+
if (!knownTypeNames.has(ts) && !aliasNames.has(ts)) {
|
|
69
|
+
// Fail fast: this indicates a mismatch between schemaCompiler output and OpenAPI builder expectations.
|
|
70
|
+
throw new Error(`[openapi] unknown referenced type '${ts}' while building schema '${t.name}'`);
|
|
71
|
+
}
|
|
72
|
+
return { $ref: `#/components/schemas/${ts}` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const arrayWrap = isArrayWrapper(t);
|
|
76
|
+
if (arrayWrap) {
|
|
77
|
+
const item = refOrPrimitive(String(arrayWrap.itemType));
|
|
78
|
+
return { type: "array", items: item };
|
|
79
|
+
}
|
|
80
|
+
const properties = {};
|
|
81
|
+
const required = [];
|
|
82
|
+
// attributes
|
|
83
|
+
for (const a of t.attrs) {
|
|
84
|
+
properties[a.name] = refOrPrimitive(a.tsType);
|
|
85
|
+
if (a.use === "required")
|
|
86
|
+
required.push(a.name);
|
|
87
|
+
}
|
|
88
|
+
// elements
|
|
89
|
+
for (const e of t.elems) {
|
|
90
|
+
const baseSchema = refOrPrimitive(e.tsType);
|
|
91
|
+
const isArray = e.max === "unbounded" || (e.max > 1);
|
|
92
|
+
let schema = baseSchema;
|
|
93
|
+
if (isArray)
|
|
94
|
+
schema = { type: "array", items: baseSchema };
|
|
95
|
+
if (e.nillable) {
|
|
96
|
+
schema = { anyOf: [schema, { type: "null" }] };
|
|
97
|
+
}
|
|
98
|
+
properties[e.name] = schema;
|
|
99
|
+
if (e.name === "$value") {
|
|
100
|
+
// never required
|
|
101
|
+
}
|
|
102
|
+
else if (e.min >= 1) {
|
|
103
|
+
required.push(e.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const obj = {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties,
|
|
109
|
+
};
|
|
110
|
+
if (required.length)
|
|
111
|
+
obj.required = Array.from(new Set(required));
|
|
112
|
+
if (closed)
|
|
113
|
+
obj.additionalProperties = false;
|
|
114
|
+
// inheritance via base => allOf
|
|
115
|
+
if (t.base) {
|
|
116
|
+
const baseName = t.base;
|
|
117
|
+
// Validate base reference explicitly (using helper ensures error if unknown)
|
|
118
|
+
if (!knownTypeNames.has(baseName) && !aliasNames.has(baseName)) {
|
|
119
|
+
throw new Error(`[openapi] unknown base type '${baseName}' while building schema '${t.name}'`);
|
|
120
|
+
}
|
|
121
|
+
obj.allOf = [{ $ref: `#/components/schemas/${baseName}` }, { ...obj }];
|
|
122
|
+
delete obj.type; // inner object part handled in allOf
|
|
123
|
+
delete obj.properties;
|
|
124
|
+
if (!required.length)
|
|
125
|
+
delete obj.required;
|
|
126
|
+
if (!closed)
|
|
127
|
+
delete obj.additionalProperties; // put closed only on leaf part
|
|
128
|
+
}
|
|
129
|
+
return obj;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Transforms the compiled WSDL catalog into OpenAPI schema components
|
|
133
|
+
*
|
|
134
|
+
* @function buildSchemas
|
|
135
|
+
* @param {CompiledCatalog} compiled - The compiled WSDL catalog containing types, aliases, and operations
|
|
136
|
+
* @param {BuildSchemasOptions} opts - Options for schema generation
|
|
137
|
+
* @returns {ComponentsSchemas} - A record of schema component names to their JSON Schema definitions
|
|
138
|
+
*
|
|
139
|
+
* @throws Will throw an error if there are unknown referenced types or bases while building schemas
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* // Example usage: building schemas with closed schemas enforcement and unused schema pruning
|
|
143
|
+
* const schemas = buildSchemas(compiledCatalog, { closedSchemas: true, pruneUnusedSchemas: true });
|
|
144
|
+
*/
|
|
145
|
+
export function buildSchemas(compiled, opts) {
|
|
146
|
+
const closed = !!opts.closedSchemas;
|
|
147
|
+
const schemas = {};
|
|
148
|
+
const typeNames = new Set(compiled.types.map(t => t.name));
|
|
149
|
+
const aliasNames = new Set(compiled.aliases.map(a => a.name));
|
|
150
|
+
// Build alias schemas first so complex types can reference them
|
|
151
|
+
for (const a of compiled.aliases) {
|
|
152
|
+
schemas[a.name] = buildAliasSchema(a);
|
|
153
|
+
}
|
|
154
|
+
for (const t of compiled.types) {
|
|
155
|
+
schemas[t.name] = buildComplexSchema(t, closed, typeNames, aliasNames);
|
|
156
|
+
}
|
|
157
|
+
if (opts.pruneUnusedSchemas) {
|
|
158
|
+
// Root references: each operation's inputElement.local, outputElement.local
|
|
159
|
+
const roots = new Set();
|
|
160
|
+
for (const op of compiled.operations) {
|
|
161
|
+
if (op.inputElement?.local)
|
|
162
|
+
roots.add(op.inputElement.local);
|
|
163
|
+
if (op.outputElement?.local)
|
|
164
|
+
roots.add(op.outputElement.local);
|
|
165
|
+
}
|
|
166
|
+
// BFS through $ref graph
|
|
167
|
+
const reachable = new Set();
|
|
168
|
+
const queue = Array.from(roots);
|
|
169
|
+
while (queue.length) {
|
|
170
|
+
const cur = queue.shift();
|
|
171
|
+
if (reachable.has(cur))
|
|
172
|
+
continue;
|
|
173
|
+
if (!schemas[cur])
|
|
174
|
+
continue; // unknown
|
|
175
|
+
reachable.add(cur);
|
|
176
|
+
const scan = (node) => {
|
|
177
|
+
if (!node || typeof node !== "object")
|
|
178
|
+
return;
|
|
179
|
+
if (node.$ref) {
|
|
180
|
+
const name = node.$ref.split("/").pop();
|
|
181
|
+
if (name && !reachable.has(name))
|
|
182
|
+
queue.push(name);
|
|
183
|
+
}
|
|
184
|
+
for (const v of Object.values(node))
|
|
185
|
+
scan(v);
|
|
186
|
+
};
|
|
187
|
+
scan(schemas[cur]);
|
|
188
|
+
}
|
|
189
|
+
// prune
|
|
190
|
+
for (const k of Object.keys(schemas)) {
|
|
191
|
+
if (!reachable.has(k))
|
|
192
|
+
delete schemas[k];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Standard error envelope (always include)
|
|
196
|
+
if (!schemas.ErrorEnvelope) {
|
|
197
|
+
schemas.ErrorEnvelope = {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: {
|
|
200
|
+
message: { type: "string" },
|
|
201
|
+
faultCode: { type: "string" },
|
|
202
|
+
detail: { type: "object", additionalProperties: true },
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return schemas;
|
|
207
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Segment Styling for OpenAPI Generation
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for converting operation names to appropriately styled
|
|
5
|
+
* path segments in the OpenAPI specification. It supports multiple styling options to
|
|
6
|
+
* accommodate different API design preferences:
|
|
7
|
+
*
|
|
8
|
+
* - kebab-case: Transforms "GetUserDetails" to "get-user-details"
|
|
9
|
+
* - as-is: Preserves the original operation name without modification
|
|
10
|
+
* - lowercase: Converts to lowercase and removes all non-alphanumeric characters
|
|
11
|
+
*
|
|
12
|
+
* These transformations ensure that the generated API paths follow consistent conventions
|
|
13
|
+
* and are properly formatted for RESTful API design.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Path segment styling options for OpenAPI generation
|
|
17
|
+
*
|
|
18
|
+
* @property {"kebab"} string Convert to kebab-case (e.g., "get-user-details")
|
|
19
|
+
* @property {"asis"} string Keep the original operation name unchanged
|
|
20
|
+
* @property {"lower"} string Convert to lowercase and remove non-alphanumeric characters
|
|
21
|
+
*/
|
|
22
|
+
export type PathStyle = "kebab" | "asis" | "lower";
|
|
23
|
+
/**
|
|
24
|
+
* Converts an operation name to a path segment according to the specified style
|
|
25
|
+
*
|
|
26
|
+
* This function transforms operation names like "GetUserDetails" or "createNewOrder"
|
|
27
|
+
* into appropriately styled path segments based on the selected style:
|
|
28
|
+
*
|
|
29
|
+
* - kebab: "GetUserDetails" → "get-user-details"
|
|
30
|
+
* - asis: "GetUserDetails" → "GetUserDetails"
|
|
31
|
+
* - lower: "GetUserDetails" → "getuserdetails"
|
|
32
|
+
*
|
|
33
|
+
* @param {string} name - Operation name to convert
|
|
34
|
+
* @param {PathStyle} style - Path segment style to apply
|
|
35
|
+
* @returns {string} - Formatted path segment
|
|
36
|
+
*/
|
|
37
|
+
export declare function toPathSegment(name: string, style: PathStyle): string;
|
|
38
|
+
//# sourceMappingURL=casing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"casing.d.ts","sourceRoot":"","sources":["../../src/openapi/casing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAQnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAepE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Segment Styling for OpenAPI Generation
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for converting operation names to appropriately styled
|
|
5
|
+
* path segments in the OpenAPI specification. It supports multiple styling options to
|
|
6
|
+
* accommodate different API design preferences:
|
|
7
|
+
*
|
|
8
|
+
* - kebab-case: Transforms "GetUserDetails" to "get-user-details"
|
|
9
|
+
* - as-is: Preserves the original operation name without modification
|
|
10
|
+
* - lowercase: Converts to lowercase and removes all non-alphanumeric characters
|
|
11
|
+
*
|
|
12
|
+
* These transformations ensure that the generated API paths follow consistent conventions
|
|
13
|
+
* and are properly formatted for RESTful API design.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Regular expression for detecting boundaries between words in camelCase
|
|
17
|
+
* Matches transitions from lowercase/digit to uppercase letter
|
|
18
|
+
*/
|
|
19
|
+
const CAMEL_BOUNDARY = /([a-z0-9])([A-Z])/g;
|
|
20
|
+
/**
|
|
21
|
+
* Converts an operation name to a path segment according to the specified style
|
|
22
|
+
*
|
|
23
|
+
* This function transforms operation names like "GetUserDetails" or "createNewOrder"
|
|
24
|
+
* into appropriately styled path segments based on the selected style:
|
|
25
|
+
*
|
|
26
|
+
* - kebab: "GetUserDetails" → "get-user-details"
|
|
27
|
+
* - asis: "GetUserDetails" → "GetUserDetails"
|
|
28
|
+
* - lower: "GetUserDetails" → "getuserdetails"
|
|
29
|
+
*
|
|
30
|
+
* @param {string} name - Operation name to convert
|
|
31
|
+
* @param {PathStyle} style - Path segment style to apply
|
|
32
|
+
* @returns {string} - Formatted path segment
|
|
33
|
+
*/
|
|
34
|
+
export function toPathSegment(name, style) {
|
|
35
|
+
switch (style) {
|
|
36
|
+
case "asis":
|
|
37
|
+
return name;
|
|
38
|
+
case "lower":
|
|
39
|
+
return name.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
|
|
40
|
+
case "kebab":
|
|
41
|
+
default:
|
|
42
|
+
// insert hyphen between camelCase boundaries then sanitize
|
|
43
|
+
return name
|
|
44
|
+
.replace(CAMEL_BOUNDARY, "$1-$2")
|
|
45
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
46
|
+
.replace(/^-+|-+$/g, "")
|
|
47
|
+
.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type CompiledCatalog } from "../compiler/schemaCompiler.js";
|
|
2
|
+
import type { PathStyle } from "./casing.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for OpenAPI generation from WSDL
|
|
5
|
+
*
|
|
6
|
+
* @interface GenerateOpenAPIOptions
|
|
7
|
+
* @property {string} [wsdl] - Path or URL to WSDL file (exclusive with catalogFile and compiledCatalog)
|
|
8
|
+
* @property {string} [catalogFile] - Path to existing compiled catalog.json (exclusive with wsdl and compiledCatalog)
|
|
9
|
+
* @property {CompiledCatalog} [compiledCatalog] - Pre-compiled catalog in memory (exclusive with wsdl and catalogFile)
|
|
10
|
+
* @property {string} [outFile] - Output path for generated OpenAPI specification
|
|
11
|
+
* @property {string} [title] - API title (defaults to derived service name)
|
|
12
|
+
* @property {string} [version] - API version for info.version (default 0.0.0)
|
|
13
|
+
* @property {string} [description] - API description
|
|
14
|
+
* @property {string[]} [servers] - List of server URLs
|
|
15
|
+
* @property {string} [basePath] - Base path prefix (e.g., /v1/soap)
|
|
16
|
+
* @property {PathStyle} [pathStyle] - Path segment style: kebab, asis, or lower
|
|
17
|
+
* @property {string} [defaultMethod] - Default HTTP method: post, get, put, patch, delete
|
|
18
|
+
* @property {string} [securityConfigFile] - Path to security.json configuration
|
|
19
|
+
* @property {string} [tagsFile] - Path to tags.json mapping operation names to tags
|
|
20
|
+
* @property {string} [opsFile] - Path to ops.json with per-operation overrides
|
|
21
|
+
* @property {boolean} [closedSchemas] - Whether to emit additionalProperties:false
|
|
22
|
+
* @property {boolean} [pruneUnusedSchemas] - Whether to exclude schemas not referenced by operations
|
|
23
|
+
* @property {boolean} [asYaml] - Force YAML output regardless of extension (deprecated)
|
|
24
|
+
* @property {boolean} [validate] - Whether to validate using swagger-parser
|
|
25
|
+
* @property {"default"|"first"|"service"} [tagStyle] - Heuristic for deriving tags
|
|
26
|
+
* @property {"json"|"yaml"|"both"} [format] - Output format (default: json)
|
|
27
|
+
* @property {boolean} [skipValidate] - Skip validation (default: false)
|
|
28
|
+
*/
|
|
29
|
+
export interface GenerateOpenAPIOptions {
|
|
30
|
+
wsdl?: string;
|
|
31
|
+
catalogFile?: string;
|
|
32
|
+
outFile?: string;
|
|
33
|
+
title?: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
servers?: string[];
|
|
37
|
+
basePath?: string;
|
|
38
|
+
pathStyle?: PathStyle;
|
|
39
|
+
defaultMethod?: string;
|
|
40
|
+
securityConfigFile?: string;
|
|
41
|
+
tagsFile?: string;
|
|
42
|
+
opsFile?: string;
|
|
43
|
+
closedSchemas?: boolean;
|
|
44
|
+
pruneUnusedSchemas?: boolean;
|
|
45
|
+
asYaml?: boolean;
|
|
46
|
+
validate?: boolean;
|
|
47
|
+
tagStyle?: "default" | "first" | "service";
|
|
48
|
+
compiledCatalog?: CompiledCatalog;
|
|
49
|
+
format?: "json" | "yaml" | "both";
|
|
50
|
+
skipValidate?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function generateOpenAPI(opts: GenerateOpenAPIOptions): Promise<{
|
|
53
|
+
doc: any;
|
|
54
|
+
jsonPath?: string;
|
|
55
|
+
yamlPath?: string;
|
|
56
|
+
}>;
|
|
57
|
+
//# sourceMappingURL=generateOpenAPI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateOpenAPI.d.ts","sourceRoot":"","sources":["../../src/openapi/generateOpenAPI.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAiB,KAAK,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAInF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC;IAC3E,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,CAgJD"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI 3.1 Generator from WSDL
|
|
3
|
+
*
|
|
4
|
+
* This module generates OpenAPI 3.1 specifications from WSDL documents or compiled catalogs.
|
|
5
|
+
* It bridges the gap between SOAP web services and REST APIs by creating an OpenAPI
|
|
6
|
+
* representation that mirrors the TypeScript model generated by the WSDL client generator.
|
|
7
|
+
*
|
|
8
|
+
* The generator supports:
|
|
9
|
+
* - Multiple input sources (WSDL URL/path, pre-compiled catalog, or in-memory catalog)
|
|
10
|
+
* - Customizable paths, operations, and schemas
|
|
11
|
+
* - Security scheme configuration
|
|
12
|
+
* - Custom operation tagging and metadata
|
|
13
|
+
* - Multiple output formats (JSON, YAML, or both)
|
|
14
|
+
* - OpenAPI validation
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "fs";
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
import * as yaml from "js-yaml";
|
|
19
|
+
import { loadWsdl } from "../loader/wsdlLoader.js";
|
|
20
|
+
import { compileCatalog } from "../compiler/schemaCompiler.js";
|
|
21
|
+
import { buildSchemas } from "./buildSchemas.js";
|
|
22
|
+
import { buildPaths } from "./buildPaths.js";
|
|
23
|
+
import { buildSecurity, loadSecurityConfig } from "./security.js";
|
|
24
|
+
export async function generateOpenAPI(opts) {
|
|
25
|
+
// Normalize format (back-compat: asYaml overrides if provided and format not set)
|
|
26
|
+
let format = opts.format || (opts.asYaml ? "yaml" : "json");
|
|
27
|
+
if (format === "yaml" && opts.asYaml && opts.outFile && /\.json$/i.test(opts.outFile)) {
|
|
28
|
+
// user asked for yaml but provided .json path → we'll still switch extension
|
|
29
|
+
}
|
|
30
|
+
if (!opts.compiledCatalog && !opts.wsdl && !opts.catalogFile) {
|
|
31
|
+
throw new Error("Provide one of: compiledCatalog, wsdl, or catalogFile");
|
|
32
|
+
}
|
|
33
|
+
if ((opts.wsdl && opts.catalogFile) || (opts.compiledCatalog && (opts.wsdl || opts.catalogFile))) {
|
|
34
|
+
// Not strictly an error, but disallow ambiguous multi-source inputs to keep deterministic
|
|
35
|
+
// Users should supply only ONE source of truth.
|
|
36
|
+
throw new Error("Provide only one source: compiledCatalog OR wsdl OR catalogFile");
|
|
37
|
+
}
|
|
38
|
+
let compiled;
|
|
39
|
+
if (opts.compiledCatalog) {
|
|
40
|
+
compiled = opts.compiledCatalog;
|
|
41
|
+
}
|
|
42
|
+
else if (opts.catalogFile) {
|
|
43
|
+
const raw = fs.readFileSync(opts.catalogFile, "utf8");
|
|
44
|
+
compiled = JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const wsdlCatalog = await loadWsdl(String(opts.wsdl));
|
|
48
|
+
compiled = compileCatalog(wsdlCatalog, {
|
|
49
|
+
// minimal compiler options (no generation side effects needed here)
|
|
50
|
+
wsdl: String(opts.wsdl),
|
|
51
|
+
out: "",
|
|
52
|
+
imports: "js",
|
|
53
|
+
catalog: false,
|
|
54
|
+
primitive: { int64As: "string", bigIntegerAs: "string", decimalAs: "string", dateAs: "string" },
|
|
55
|
+
choice: "all-optional",
|
|
56
|
+
failOnUnresolved: false,
|
|
57
|
+
attributesKey: "$attributes",
|
|
58
|
+
nillableAsOptional: false,
|
|
59
|
+
clientName: undefined,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const title = opts.title || (compiled.serviceName ? `${compiled.serviceName} SOAP API` : "Generated SOAP API");
|
|
63
|
+
const infoVersion = opts.version || "0.0.0";
|
|
64
|
+
// Load external config files (optional)
|
|
65
|
+
const tagsMap = opts.tagsFile ? safeJson(opts.tagsFile) : undefined;
|
|
66
|
+
const opsOverrides = opts.opsFile ? safeJson(opts.opsFile) : undefined;
|
|
67
|
+
const securityCfg = loadSecurityConfig(opts.securityConfigFile);
|
|
68
|
+
const securityBuilt = buildSecurity(securityCfg);
|
|
69
|
+
// Build components.schemas
|
|
70
|
+
const schemas = buildSchemas(compiled, {
|
|
71
|
+
closedSchemas: opts.closedSchemas,
|
|
72
|
+
pruneUnusedSchemas: opts.pruneUnusedSchemas
|
|
73
|
+
});
|
|
74
|
+
// Build paths
|
|
75
|
+
const tagStyle = opts.tagStyle || "default";
|
|
76
|
+
const defaultTag = (() => {
|
|
77
|
+
if (tagStyle === "service")
|
|
78
|
+
return compiled.serviceName || "SOAP";
|
|
79
|
+
if (tagStyle === "first")
|
|
80
|
+
return "General"; // fallback; per-op derivation below
|
|
81
|
+
return compiled.serviceName || "SOAP";
|
|
82
|
+
})();
|
|
83
|
+
const paths = buildPaths(compiled, {
|
|
84
|
+
basePath: opts.basePath || "/",
|
|
85
|
+
pathStyle: opts.pathStyle || "kebab",
|
|
86
|
+
defaultMethod: opts.defaultMethod || "post",
|
|
87
|
+
tagsMap,
|
|
88
|
+
overrides: opsOverrides,
|
|
89
|
+
defaultTag,
|
|
90
|
+
opSecurity: securityBuilt.opSecurity,
|
|
91
|
+
opHeaderParameters: securityBuilt.opHeaderParameters,
|
|
92
|
+
});
|
|
93
|
+
// Apply tag heuristics for operations missing explicit tag if style=first
|
|
94
|
+
if (tagStyle === "first") {
|
|
95
|
+
for (const p of Object.values(paths)) {
|
|
96
|
+
for (const methodObj of Object.values(p)) {
|
|
97
|
+
if (Array.isArray(methodObj.tags) && methodObj.tags[0] === "General") {
|
|
98
|
+
const opId = methodObj.operationId || "Op";
|
|
99
|
+
const seg = opId.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean)[0] || "General";
|
|
100
|
+
methodObj.tags = [seg];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const doc = {
|
|
106
|
+
openapi: "3.1.0",
|
|
107
|
+
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema",
|
|
108
|
+
info: { title, version: infoVersion },
|
|
109
|
+
paths,
|
|
110
|
+
components: {
|
|
111
|
+
schemas,
|
|
112
|
+
...(securityBuilt.securitySchemes ? { securitySchemes: securityBuilt.securitySchemes } : {}),
|
|
113
|
+
...(Object.keys(securityBuilt.headerParameters).length ? { parameters: securityBuilt.headerParameters } : {}),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
if (opts.description)
|
|
117
|
+
doc.info.description = opts.description;
|
|
118
|
+
if (opts.servers && opts.servers.length) {
|
|
119
|
+
doc.servers = opts.servers.map(u => ({ url: u }));
|
|
120
|
+
}
|
|
121
|
+
if (opts.skipValidate !== true) {
|
|
122
|
+
try {
|
|
123
|
+
const parser = await import("@apidevtools/swagger-parser");
|
|
124
|
+
await parser.default.validate(JSON.parse(JSON.stringify(doc)));
|
|
125
|
+
console.log("OpenAPI validation: OK");
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
console.error("OpenAPI validation failed:", e instanceof Error ? e.message : e);
|
|
129
|
+
throw e;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.log("OpenAPI validation skipped by flag");
|
|
134
|
+
}
|
|
135
|
+
// Determine base path for writing
|
|
136
|
+
let base = opts.outFile;
|
|
137
|
+
if (base) {
|
|
138
|
+
const extMatch = base.match(/\.(json|ya?ml)$/i);
|
|
139
|
+
if (extMatch) {
|
|
140
|
+
base = base.slice(0, -extMatch[0].length); // strip extension
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let jsonPath;
|
|
144
|
+
let yamlPath;
|
|
145
|
+
if (opts.outFile) {
|
|
146
|
+
const dir = path.dirname(opts.outFile);
|
|
147
|
+
if (!fs.existsSync(dir))
|
|
148
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
149
|
+
if (format === "json") {
|
|
150
|
+
jsonPath = base ? `${base}.json` : opts.outFile;
|
|
151
|
+
fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2), "utf8");
|
|
152
|
+
}
|
|
153
|
+
else if (format === "yaml") {
|
|
154
|
+
yamlPath = base ? `${base}.yaml` : opts.outFile.replace(/\.(json)$/i, ".yaml");
|
|
155
|
+
fs.writeFileSync(yamlPath, yaml.dump(doc), "utf8");
|
|
156
|
+
}
|
|
157
|
+
else { // both
|
|
158
|
+
jsonPath = `${base}.json`;
|
|
159
|
+
yamlPath = `${base}.yaml`;
|
|
160
|
+
fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2), "utf8");
|
|
161
|
+
fs.writeFileSync(yamlPath, yaml.dump(doc), "utf8");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { doc, jsonPath, yamlPath };
|
|
165
|
+
}
|
|
166
|
+
function safeJson(file) {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
console.warn(`⚠️ Failed to parse JSON file '${file}': ${e instanceof Error ? e.message : String(e)}`);
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|