@stackql/provider-utils 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/LICENSE +21 -0
- package/README.md +320 -0
- package/package.json +52 -0
- package/src/docgen/generator.js +298 -0
- package/src/docgen/helpers.js +572 -0
- package/src/docgen/index.js +5 -0
- package/src/docgen/resource/examples/delete-example.js +74 -0
- package/src/docgen/resource/examples/exec-example.js +121 -0
- package/src/docgen/resource/examples/insert-example.js +211 -0
- package/src/docgen/resource/examples/select-example.js +73 -0
- package/src/docgen/resource/examples/update-example.js +154 -0
- package/src/docgen/resource/examples.js +31 -0
- package/src/docgen/resource/fields.js +82 -0
- package/src/docgen/resource/methods.js +90 -0
- package/src/docgen/resource/overview.js +39 -0
- package/src/docgen/resource/parameters.js +76 -0
- package/src/docgen/resource-content.js +25 -0
- package/src/index.js +4 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
// src/docgen/helpers.js
|
|
2
|
+
|
|
3
|
+
// exported functions for use in other modules
|
|
4
|
+
|
|
5
|
+
export function getIndefiniteArticle(resourceName) {
|
|
6
|
+
// Determine whether to use "a" or "an" based on the first letter's pronunciation
|
|
7
|
+
const firstLetter = resourceName.charAt(0).toLowerCase();
|
|
8
|
+
const vowelSounds = ['a', 'e', 'i', 'o', 'u'];
|
|
9
|
+
// Special case for 'h' when it's silent (like in "hour")
|
|
10
|
+
const specialCaseH = resourceName.toLowerCase().startsWith('hour');
|
|
11
|
+
// Special case for words starting with 'u' but pronounced with 'y' sound (like "user")
|
|
12
|
+
const specialCaseU = firstLetter === 'u' && !resourceName.toLowerCase().startsWith('un');
|
|
13
|
+
|
|
14
|
+
let article = 'a';
|
|
15
|
+
if (vowelSounds.includes(firstLetter) && !specialCaseU) {
|
|
16
|
+
article = 'an';
|
|
17
|
+
}
|
|
18
|
+
// Handle special case for words starting with 'h' where 'h' is silent
|
|
19
|
+
if (firstLetter === 'h' && specialCaseH) {
|
|
20
|
+
article = 'an';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return article;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function cleanDescription(description) {
|
|
27
|
+
if (!description) return '';
|
|
28
|
+
|
|
29
|
+
// Replace <a> tags with markdown equivalent
|
|
30
|
+
description = description.replace(/<a\s+(?:[^>]*?\s+)?href="([^"]*)"(?:[^>]*?)>(.*?)<\/a>/gi, '[$2]($1)');
|
|
31
|
+
|
|
32
|
+
// Remove <p> tags and replace them with a single space
|
|
33
|
+
description = description.replace(/<\/?p>/gi, ' ');
|
|
34
|
+
|
|
35
|
+
// Replace <br> tags with a single space
|
|
36
|
+
description = description.replace(/<br\s*\/?>/gi, ' ');
|
|
37
|
+
|
|
38
|
+
// Replace <code> and <pre> tags with markdown code blocks
|
|
39
|
+
description = description.replace(/<(code|pre)>(.*?)<\/\1>/gi, '`$2`');
|
|
40
|
+
|
|
41
|
+
// Convert <ul> and <li> tags into a comma-separated list
|
|
42
|
+
description = description.replace(/<\/?ul>/gi, '');
|
|
43
|
+
description = description.replace(/<li>(.*?)<\/li>/gi, '$1, ');
|
|
44
|
+
|
|
45
|
+
// Remove <name>, <td>, <tr>, and <table> tags
|
|
46
|
+
description = description.replace(/<\/?(name|td|tr|table)>/gi, '');
|
|
47
|
+
|
|
48
|
+
// Replace multiple spaces with a single space
|
|
49
|
+
description = description.replace(/\s+/g, ' ');
|
|
50
|
+
|
|
51
|
+
// Escape pipe characters to prevent breaking markdown tables
|
|
52
|
+
description = description.replace(/\|/g, '\\|');
|
|
53
|
+
|
|
54
|
+
// Remove any trailing commas, spaces, and line breaks
|
|
55
|
+
description = description.replace(/,s*$/, '').trim();
|
|
56
|
+
|
|
57
|
+
description = description.replace(/</g, '{');
|
|
58
|
+
description = description.replace(/>/g, '}');
|
|
59
|
+
|
|
60
|
+
return description;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, sqlVerb) {
|
|
64
|
+
const methods = {};
|
|
65
|
+
|
|
66
|
+
if (sqlVerb === 'exec') {
|
|
67
|
+
// Get all SQL verb methods
|
|
68
|
+
const allSqlMethodNames = new Set();
|
|
69
|
+
const sqlVerbTypes = ['select', 'insert', 'update', 'delete', 'replace'];
|
|
70
|
+
|
|
71
|
+
for (const verb of sqlVerbTypes) {
|
|
72
|
+
if (resourceData.sqlVerbs[verb] && resourceData.sqlVerbs[verb].length > 0) {
|
|
73
|
+
for (const method of resourceData.sqlVerbs[verb]) {
|
|
74
|
+
const { methodName } = getHttpOperationForSqlVerb(method.$ref, resourceData);
|
|
75
|
+
allSqlMethodNames.add(methodName);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Process each method that's not in any SQL verb
|
|
81
|
+
for (const [methodName, methodData] of Object.entries(resourceData.methods)) {
|
|
82
|
+
if (!allSqlMethodNames.has(methodName)) {
|
|
83
|
+
const { path, httpVerb, mediaType, openAPIDocKey } = methodData.operation;
|
|
84
|
+
let resolvedPath = path;
|
|
85
|
+
let resolvedVerb = httpVerb;
|
|
86
|
+
|
|
87
|
+
// If operation uses $ref, resolve it
|
|
88
|
+
if (methodData.operation.$ref) {
|
|
89
|
+
const refPath = methodData.operation.$ref;
|
|
90
|
+
|
|
91
|
+
// Extract the path and verb from the $ref
|
|
92
|
+
// The path format is typically '#/paths/~1api~1v2~1accounts~1{name}:undrop/post'
|
|
93
|
+
const pathMatch = refPath.match(/#\/paths\/(.+)\/([^/]+)$/);
|
|
94
|
+
|
|
95
|
+
if (pathMatch && pathMatch.length === 3) {
|
|
96
|
+
// Replace the escaped characters in the path
|
|
97
|
+
let path = pathMatch[1]
|
|
98
|
+
.replace(/~1/g, '/') // Replace ~1 with /
|
|
99
|
+
.replace(/~0/g, '~') // Replace ~0 with ~ if needed
|
|
100
|
+
|
|
101
|
+
// Don't modify path parts with special characters like ':undrop'
|
|
102
|
+
resolvedPath = path;
|
|
103
|
+
resolvedVerb = pathMatch[2];
|
|
104
|
+
|
|
105
|
+
console.log(`Resolved path: ${resolvedPath}, verb: ${resolvedVerb}`);
|
|
106
|
+
} else {
|
|
107
|
+
console.warn(`Could not parse $ref path: ${refPath}`);
|
|
108
|
+
// Skip this method if we can't parse the path
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Get response and params using the same function as for SQL verbs
|
|
114
|
+
const { respProps, respDescription, opDescription, requestBody } = getHttpOperationInfo(
|
|
115
|
+
dereferencedAPI,
|
|
116
|
+
resolvedPath,
|
|
117
|
+
resolvedVerb,
|
|
118
|
+
methodData.response.mediaType || '',
|
|
119
|
+
methodData.response.openAPIDocKey || '200',
|
|
120
|
+
''
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const { requiredParams, optionalParams } = getHttpOperationParams(
|
|
124
|
+
dereferencedAPI,
|
|
125
|
+
resolvedPath,
|
|
126
|
+
resolvedVerb
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Initialize the method with the same structure as SQL methods
|
|
130
|
+
methods[methodName] = {
|
|
131
|
+
opDescription,
|
|
132
|
+
respDescription,
|
|
133
|
+
properties: {},
|
|
134
|
+
requiredParams: requiredParams || {},
|
|
135
|
+
optionalParams: optionalParams || {},
|
|
136
|
+
requestBody: requestBody || {},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Format and sort the properties using our helper functions
|
|
140
|
+
const allProperties = formatProperties(respProps);
|
|
141
|
+
sortAndAddProperties(methods[methodName], allProperties);
|
|
142
|
+
|
|
143
|
+
console.info(`Processed exec method: ${methodName}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return methods;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!resourceData.sqlVerbs[sqlVerb] || resourceData.sqlVerbs[sqlVerb].length === 0) {
|
|
151
|
+
return methods;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const thisMethod of resourceData.sqlVerbs[sqlVerb]) {
|
|
155
|
+
const {path, httpVerb, mediaType, openAPIDocKey, objectKey, methodName} = getHttpOperationForSqlVerb(thisMethod.$ref, resourceData);
|
|
156
|
+
const {respProps, respDescription, opDescription, requestBody} = getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey);
|
|
157
|
+
const {requiredParams, optionalParams} = getHttpOperationParams(dereferencedAPI, path, httpVerb);
|
|
158
|
+
|
|
159
|
+
// Initialize the method object with description and params
|
|
160
|
+
methods[methodName] = {
|
|
161
|
+
opDescription,
|
|
162
|
+
respDescription,
|
|
163
|
+
properties: {},
|
|
164
|
+
requiredParams: requiredParams || {},
|
|
165
|
+
optionalParams: optionalParams || {},
|
|
166
|
+
requestBody: requestBody || {},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Format and sort the properties using our helper functions
|
|
170
|
+
const allProperties = formatProperties(respProps);
|
|
171
|
+
sortAndAddProperties(methods[methodName], allProperties);
|
|
172
|
+
|
|
173
|
+
console.info(`Processed method: ${methodName}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return methods;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// internal helper functions for use in this module only
|
|
180
|
+
|
|
181
|
+
// Extract the property sorting logic into a separate function
|
|
182
|
+
function sortAndAddProperties(methodObj, allProperties) {
|
|
183
|
+
// First group: "id" and "name" fields exactly
|
|
184
|
+
const exactIdNameFields = Object.keys(allProperties).filter(
|
|
185
|
+
propName => propName === 'id' || propName === 'name'
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Second group: fields ending with "_id"
|
|
189
|
+
const idSuffixFields = Object.keys(allProperties).filter(
|
|
190
|
+
propName => propName !== 'id' && propName.endsWith('_id')
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Third group: fields ending with "_name"
|
|
194
|
+
const nameSuffixFields = Object.keys(allProperties).filter(
|
|
195
|
+
propName => propName !== 'name' && propName.endsWith('_name')
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Fourth group: all other fields
|
|
199
|
+
const otherFields = Object.keys(allProperties).filter(
|
|
200
|
+
propName => !exactIdNameFields.includes(propName) &&
|
|
201
|
+
!idSuffixFields.includes(propName) &&
|
|
202
|
+
!nameSuffixFields.includes(propName)
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Create a sorted array of all field names according to priority
|
|
206
|
+
const sortedFieldNames = [
|
|
207
|
+
...exactIdNameFields.sort(), // Sort alphabetically within group
|
|
208
|
+
...idSuffixFields.sort(), // Sort alphabetically within group
|
|
209
|
+
...nameSuffixFields.sort(), // Sort alphabetically within group
|
|
210
|
+
...otherFields.sort() // Sort alphabetically within group
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
// Add properties to the method in the sorted order
|
|
214
|
+
for (const propName of sortedFieldNames) {
|
|
215
|
+
methodObj.properties[propName] = allProperties[propName];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// And also extract the property formatting logic
|
|
220
|
+
function formatProperties(respProps) {
|
|
221
|
+
const allProperties = {};
|
|
222
|
+
for (const [propName, propDetails] of Object.entries(respProps)) {
|
|
223
|
+
let typeString = propDetails.type || '';
|
|
224
|
+
if (propDetails.format) {
|
|
225
|
+
typeString += ` (${propDetails.format})`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get the base description
|
|
229
|
+
let fullDescription = propDetails.description || '';
|
|
230
|
+
fullDescription = fullDescription.replace(/\n/g, ' ');
|
|
231
|
+
let additionalDescriptionPaths = [];
|
|
232
|
+
|
|
233
|
+
// Add all other fields to description parts
|
|
234
|
+
for (const [fieldName, fieldValue] of Object.entries(propDetails)) {
|
|
235
|
+
// Skip the fields we're handling separately
|
|
236
|
+
if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description') {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (typeof fieldValue != 'string') {
|
|
241
|
+
continue;
|
|
242
|
+
} else {
|
|
243
|
+
additionalDescriptionPaths.push(`${fieldName}: ${String(fieldValue)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fullDescription += additionalDescriptionPaths.length > 0 ? ` (${additionalDescriptionPaths.join(', ')})` : '';
|
|
248
|
+
|
|
249
|
+
// Store formatted property details
|
|
250
|
+
allProperties[propName] = {
|
|
251
|
+
type: typeString,
|
|
252
|
+
description: escapeHtml(fullDescription),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return allProperties;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function escapeHtml(text) {
|
|
259
|
+
if (!text) return '';
|
|
260
|
+
return text
|
|
261
|
+
.replace(/&/g, '&')
|
|
262
|
+
.replace(/</g, '<')
|
|
263
|
+
.replace(/>/g, '>')
|
|
264
|
+
.replace(/"/g, '"')
|
|
265
|
+
.replace(/'/g, ''');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function sanitizeHtml(text) {
|
|
269
|
+
return text
|
|
270
|
+
// Replace "<" unless it's followed by "a", "/a", "b", "/b", "strong", or "/strong"
|
|
271
|
+
.replace(/<(?!\/?(?:a|b|strong)\b)/gi, '<')
|
|
272
|
+
// Replace ">" unless it's preceded by "</a", "<a ...>", "</b", "<b ...>", "</strong", or "<strong ...>"
|
|
273
|
+
.replace(/(?<!<\/?(?:a|b|strong)[^>]*)>/gi, '>')
|
|
274
|
+
// Add quotes around unquoted href values (within <a ...>)
|
|
275
|
+
.replace(/(<a\b[^>]*?)href=([^"' \t\r\n>]+)/gi, '$1href="$2"')
|
|
276
|
+
// Wrap backticked text with <code>
|
|
277
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
278
|
+
// Replace { and }
|
|
279
|
+
.replace(/{/g, '{')
|
|
280
|
+
.replace(/}/g, '}')
|
|
281
|
+
// Escape backslash
|
|
282
|
+
.replace(/\\/g, '\\\\')
|
|
283
|
+
// Replace " with " UNLESS inside <code>...</code> OR inside an href="...".
|
|
284
|
+
// The alternation matches either a whole <code>...</code> block, an href="...",
|
|
285
|
+
// or a bare " to replace.
|
|
286
|
+
.replace(/(<code>[\s\S]*?<\/code>)|(\bhref="[^"]*")|"/gi, (m, code, href) => {
|
|
287
|
+
if (code) return code; // keep code blocks untouched
|
|
288
|
+
if (href) return href; // keep href="..." quotes untouched
|
|
289
|
+
return '"'; // everything else: replace "
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getRequiredServerVars(dereferencedAPI) {
|
|
294
|
+
const serverVars = {};
|
|
295
|
+
|
|
296
|
+
// Check if servers and variables exist in the API spec
|
|
297
|
+
if (!dereferencedAPI.servers ||
|
|
298
|
+
!dereferencedAPI.servers[0] ||
|
|
299
|
+
!dereferencedAPI.servers[0].variables) {
|
|
300
|
+
return serverVars;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Process each server variable
|
|
304
|
+
for (const [varName, varDetails] of Object.entries(dereferencedAPI.servers[0].variables)) {
|
|
305
|
+
// Start with base type and description
|
|
306
|
+
let typeString = 'string'; // Base type for server variables
|
|
307
|
+
let description = varDetails.description || '';
|
|
308
|
+
|
|
309
|
+
// Replace newlines with spaces
|
|
310
|
+
description = description.replace(/\n/g, ' ');
|
|
311
|
+
|
|
312
|
+
// If format exists, add it to type
|
|
313
|
+
if (varDetails.format) {
|
|
314
|
+
typeString += ` (${varDetails.format})`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Collect additional fields for description
|
|
318
|
+
let additionalFields = [];
|
|
319
|
+
|
|
320
|
+
for (const [fieldName, fieldValue] of Object.entries(varDetails)) {
|
|
321
|
+
// Skip description and format which are already handled
|
|
322
|
+
if (fieldName === 'description' || fieldName === 'format') {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Format the field value appropriately
|
|
327
|
+
let formattedValue;
|
|
328
|
+
if (Array.isArray(fieldValue)) {
|
|
329
|
+
formattedValue = `[${fieldValue.join(', ')}]`;
|
|
330
|
+
} else if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
331
|
+
formattedValue = JSON.stringify(fieldValue);
|
|
332
|
+
} else {
|
|
333
|
+
formattedValue = String(fieldValue);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
additionalFields.push(`${fieldName}: ${formattedValue}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Add the additional fields to description if any exist
|
|
340
|
+
if (additionalFields.length > 0) {
|
|
341
|
+
if (description) {
|
|
342
|
+
description += ' ';
|
|
343
|
+
}
|
|
344
|
+
description += `(${additionalFields.join(', ')})`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Create the server variable entry
|
|
348
|
+
serverVars[varName] = {
|
|
349
|
+
type: typeString,
|
|
350
|
+
description: description
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return serverVars;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function getHttpOperationForSqlVerb(sqlVerbRef, resourceData){
|
|
358
|
+
|
|
359
|
+
console.log(`Getting http operation for sql verb...`);
|
|
360
|
+
|
|
361
|
+
// get path and verb
|
|
362
|
+
const methodName = sqlVerbRef.split('/').pop();
|
|
363
|
+
const methodObj = resourceData.methods[methodName]
|
|
364
|
+
const operationRef = methodObj.operation.$ref.split('#/paths/').pop();
|
|
365
|
+
const httpVerb = operationRef.split('/').pop()
|
|
366
|
+
const path = operationRef.split('/')[0].replaceAll('~1','/');
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
path,
|
|
370
|
+
httpVerb,
|
|
371
|
+
mediaType: methodObj.response.mediaType,
|
|
372
|
+
openAPIDocKey: methodObj.response.openAPIDocKey,
|
|
373
|
+
objectKey: methodObj.response.objectKey || false,
|
|
374
|
+
methodName
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getHttpOperationInfo(dereferencedAPI, path, httpVerb, mediaType, openAPIDocKey, objectKey) {
|
|
379
|
+
console.log(`Getting response for ${path}/${httpVerb}...`);
|
|
380
|
+
|
|
381
|
+
// Check if the path exists in the dereferencedAPI
|
|
382
|
+
if (!dereferencedAPI.paths[path]) {
|
|
383
|
+
throw new Error(`Path '${path}' not found in dereferencedAPI.paths`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check if the HTTP verb exists for this path
|
|
387
|
+
if (!dereferencedAPI.paths[path][httpVerb]) {
|
|
388
|
+
throw new Error(`HTTP verb '${httpVerb}' not found for path '${path}'`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// get op description
|
|
392
|
+
const opDescription = dereferencedAPI.paths[path][httpVerb].description || '';
|
|
393
|
+
|
|
394
|
+
// Extract request body if it exists
|
|
395
|
+
let requestBody = {};
|
|
396
|
+
if (dereferencedAPI.paths[path][httpVerb].requestBody &&
|
|
397
|
+
dereferencedAPI.paths[path][httpVerb].requestBody.content) {
|
|
398
|
+
|
|
399
|
+
// Get first content type available in requestBody
|
|
400
|
+
const contentTypes = Object.keys(dereferencedAPI.paths[path][httpVerb].requestBody.content);
|
|
401
|
+
if (contentTypes.length > 0) {
|
|
402
|
+
const firstContentType = contentTypes[0];
|
|
403
|
+
const reqBodySchema = dereferencedAPI.paths[path][httpVerb].requestBody.content[firstContentType].schema;
|
|
404
|
+
if (reqBodySchema) {
|
|
405
|
+
// If schema is a reference, use it directly
|
|
406
|
+
if (reqBodySchema.$ref) {
|
|
407
|
+
requestBody = reqBodySchema;
|
|
408
|
+
}
|
|
409
|
+
// If schema is an object with properties, get them
|
|
410
|
+
else if (reqBodySchema.properties) {
|
|
411
|
+
requestBody = {
|
|
412
|
+
properties: reqBodySchema.properties,
|
|
413
|
+
required: reqBodySchema.required || []
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
// If schema is something else, use it as is
|
|
417
|
+
else {
|
|
418
|
+
requestBody = reqBodySchema;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check if the response exists
|
|
425
|
+
if (!dereferencedAPI.paths[path][httpVerb].responses ||
|
|
426
|
+
!dereferencedAPI.paths[path][httpVerb].responses[openAPIDocKey]) {
|
|
427
|
+
console.warn(`Response '${openAPIDocKey}' not found for ${path}/${httpVerb}`);
|
|
428
|
+
return {
|
|
429
|
+
respProps: {},
|
|
430
|
+
respDescription: '',
|
|
431
|
+
opDescription,
|
|
432
|
+
requestBody
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Check if there's a content section with the mediaType
|
|
437
|
+
const responseObj = dereferencedAPI.paths[path][httpVerb].responses[openAPIDocKey];
|
|
438
|
+
|
|
439
|
+
// If no content or no mediaType in the response, return empty properties
|
|
440
|
+
if (!responseObj.content || !mediaType || !responseObj.content[mediaType] || !responseObj.content[mediaType].schema) {
|
|
441
|
+
return {
|
|
442
|
+
respProps: {},
|
|
443
|
+
respDescription: responseObj.description || '',
|
|
444
|
+
opDescription,
|
|
445
|
+
requestBody
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const schema = responseObj.content[mediaType].schema;
|
|
450
|
+
|
|
451
|
+
if (schema.type === 'array') {
|
|
452
|
+
return {
|
|
453
|
+
respProps: schema.items.properties || {},
|
|
454
|
+
respDescription: schema.items.description || responseObj.description || '',
|
|
455
|
+
opDescription,
|
|
456
|
+
requestBody
|
|
457
|
+
};
|
|
458
|
+
} else if (schema.type === 'object') {
|
|
459
|
+
return {
|
|
460
|
+
respProps: schema.properties || {},
|
|
461
|
+
respDescription: schema.description || responseObj.description || '',
|
|
462
|
+
opDescription,
|
|
463
|
+
requestBody
|
|
464
|
+
};
|
|
465
|
+
} else {
|
|
466
|
+
// For primitive types or when schema structure is unexpected
|
|
467
|
+
return {
|
|
468
|
+
respProps: {},
|
|
469
|
+
respDescription: schema.description || responseObj.description || '',
|
|
470
|
+
opDescription,
|
|
471
|
+
requestBody
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function getHttpOperationParams(dereferencedAPI, path, httpVerb) {
|
|
477
|
+
const requiredParams = {};
|
|
478
|
+
const optionalParams = {};
|
|
479
|
+
|
|
480
|
+
// Get the parameters array from the operation
|
|
481
|
+
const params = dereferencedAPI.paths[path][httpVerb].parameters || [];
|
|
482
|
+
|
|
483
|
+
// Process each parameter
|
|
484
|
+
for (const param of params) {
|
|
485
|
+
// Skip parameters without a name or schema
|
|
486
|
+
if (!param.name || !param.schema) continue;
|
|
487
|
+
|
|
488
|
+
// Format the type string
|
|
489
|
+
let typeString = param.schema.type || '';
|
|
490
|
+
if (param.schema.format) {
|
|
491
|
+
typeString += ` (${param.schema.format})`;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Get the base description and clean it up
|
|
495
|
+
let description = param.description || '';
|
|
496
|
+
// Replace newlines with spaces to avoid string concatenation in output
|
|
497
|
+
description = description.replace(/\n/g, ' ');
|
|
498
|
+
|
|
499
|
+
let additionalDescriptionParts = [];
|
|
500
|
+
|
|
501
|
+
for (const [fieldName, fieldValue] of Object.entries(param.schema)) {
|
|
502
|
+
if (fieldName === 'type' || fieldName === 'format' || fieldName === 'description' || fieldName === 'pattern') {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
let formattedValue;
|
|
507
|
+
if (Array.isArray(fieldValue)) {
|
|
508
|
+
formattedValue = `[${fieldValue.join(', ')}]`;
|
|
509
|
+
} else if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
510
|
+
formattedValue = JSON.stringify(fieldValue);
|
|
511
|
+
} else {
|
|
512
|
+
formattedValue = String(fieldValue);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// if (fieldName === 'pattern') {
|
|
516
|
+
// additionalDescriptionParts.push(`pattern: <code>${formattedValue}</code>`);
|
|
517
|
+
// } else {
|
|
518
|
+
// additionalDescriptionParts.push(`${fieldName}: ${formattedValue}`);
|
|
519
|
+
// }
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Add any fields from the parameter itself that might contain metadata
|
|
523
|
+
for (const [fieldName, fieldValue] of Object.entries(param)) {
|
|
524
|
+
// Skip fields we've already processed or don't need
|
|
525
|
+
if (fieldName === 'name' || fieldName === 'schema' ||
|
|
526
|
+
fieldName === 'required' || fieldName === 'in' ||
|
|
527
|
+
fieldName === 'description') {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Handle example field specifically (it might exist at param level not schema level)
|
|
532
|
+
if (fieldName === 'example' && !param.schema.example) {
|
|
533
|
+
let formattedValue;
|
|
534
|
+
if (Array.isArray(fieldValue)) {
|
|
535
|
+
formattedValue = `[${fieldValue.join(', ')}]`;
|
|
536
|
+
} else if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
537
|
+
formattedValue = JSON.stringify(fieldValue);
|
|
538
|
+
} else {
|
|
539
|
+
formattedValue = String(fieldValue);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
additionalDescriptionParts.push(`example: ${formattedValue}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Add additional description parts in parentheses if there are any
|
|
547
|
+
if (additionalDescriptionParts.length > 0) {
|
|
548
|
+
description += ` (${additionalDescriptionParts.join(', ')})`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Create the parameter details object
|
|
552
|
+
const paramDetails = {
|
|
553
|
+
type: typeString,
|
|
554
|
+
description: description // Apply escapeHtml here if needed
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// Add to the appropriate category based on required flag
|
|
558
|
+
if (param.required === true) {
|
|
559
|
+
requiredParams[param.name] = paramDetails;
|
|
560
|
+
} else {
|
|
561
|
+
optionalParams[param.name] = paramDetails;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Get server variables and merge them into requiredParams
|
|
566
|
+
const serverVars = getRequiredServerVars(dereferencedAPI);
|
|
567
|
+
|
|
568
|
+
// Merge server variables into requiredParams
|
|
569
|
+
Object.assign(requiredParams, serverVars);
|
|
570
|
+
|
|
571
|
+
return { requiredParams, optionalParams };
|
|
572
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/docgen/resource/examples/delete-example.js
|
|
2
|
+
import {
|
|
3
|
+
getSqlMethodsWithOrderedFields,
|
|
4
|
+
} from '../../helpers.js';
|
|
5
|
+
|
|
6
|
+
export function createDeleteExamples(providerName, serviceName, resourceName, resourceData, dereferencedAPI) {
|
|
7
|
+
const deleteMethods = getSqlMethodsWithOrderedFields(resourceData, dereferencedAPI, 'delete');
|
|
8
|
+
|
|
9
|
+
// if there are no delete methods, return empty content
|
|
10
|
+
if (Object.keys(deleteMethods).length === 0) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let content = '\n\n## `DELETE` examples\n\n';
|
|
15
|
+
|
|
16
|
+
// Create tab structure with values array
|
|
17
|
+
content += '<Tabs\n defaultValue="' + Object.keys(deleteMethods)[0] + '"\n values={[\n';
|
|
18
|
+
|
|
19
|
+
// Add each method as a tab option
|
|
20
|
+
Object.keys(deleteMethods).forEach((methodName, index, arr) => {
|
|
21
|
+
content += ' { label: \'' + methodName + '\', value: \'' + methodName + '\' }';
|
|
22
|
+
content += index < arr.length - 1 ? ',\n' : '\n';
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
content += ' ]}\n>\n';
|
|
26
|
+
|
|
27
|
+
// Create each method tab content
|
|
28
|
+
Object.entries(deleteMethods).forEach(([methodName, methodDetails]) => {
|
|
29
|
+
content += '<TabItem value="' + methodName + '">\n\n';
|
|
30
|
+
|
|
31
|
+
// Add method description
|
|
32
|
+
content += methodDetails.opDescription || methodDetails.respDescription || 'No description available.';
|
|
33
|
+
|
|
34
|
+
// Create SQL example
|
|
35
|
+
content += '\n\n```sql\nDELETE FROM ' + providerName + '.' + serviceName + '.' + resourceName;
|
|
36
|
+
|
|
37
|
+
// Add WHERE clause with parameters
|
|
38
|
+
const requiredParams = Object.keys(methodDetails.requiredParams || {});
|
|
39
|
+
const optionalParams = Object.keys(methodDetails.optionalParams || {});
|
|
40
|
+
|
|
41
|
+
if (requiredParams.length > 0 || optionalParams.length > 0) {
|
|
42
|
+
content += '\nWHERE ';
|
|
43
|
+
|
|
44
|
+
// Add required parameters
|
|
45
|
+
requiredParams.forEach((param, index) => {
|
|
46
|
+
content += param + ' = \'{{ ' + param + ' }}\' --required';
|
|
47
|
+
content += index < requiredParams.length - 1 || optionalParams.length > 0 ? '\nAND ' : '';
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Add optional parameters
|
|
51
|
+
optionalParams.forEach((param, index) => {
|
|
52
|
+
// For boolean parameters, we can add a comment about their default value
|
|
53
|
+
const paramDetails = methodDetails.optionalParams[param];
|
|
54
|
+
const isBoolean = paramDetails.type === 'boolean';
|
|
55
|
+
const defaultValue = paramDetails.default;
|
|
56
|
+
|
|
57
|
+
content += param + ' = \'{{ ' + param + ' }}\'';
|
|
58
|
+
|
|
59
|
+
if (isBoolean && defaultValue !== undefined) {
|
|
60
|
+
content += ' -- default: ' + defaultValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
content += index < optionalParams.length - 1 ? '\nAND ' : '';
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
content += ';\n```\n</TabItem>\n';
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Close tabs
|
|
71
|
+
content += '</Tabs>\n';
|
|
72
|
+
|
|
73
|
+
return content;
|
|
74
|
+
}
|