openapi-explorer 0.6.221 → 0.6.225

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openapi-explorer",
3
- "version": "0.6.221",
3
+ "version": "0.6.225",
4
4
  "description": "OpenAPI Explorer - API viewer with dynamically generated components, documentation, and interaction console",
5
5
  "author": "Rhosys Developers <developers@rhosys.ch>",
6
6
  "repository": {
@@ -145,8 +145,8 @@ export default class ApiRequest extends LitElement {
145
145
  tableRows.push(html`
146
146
  <tr>
147
147
  <td style="width:160px; min-width:50px;">
148
- <div class="param-name">
149
- ${param.required ? html`<span style='color:var(--red)'>*</span>` : ''}${param.name}
148
+ <div class="param-name ${paramSchema.deprecated ? 'deprecated' : ''}">
149
+ ${param.name}${!paramSchema.deprecated && param.required ? html`<span style='color:var(--red);'>*</span>` : ''}
150
150
  </div>
151
151
  <div class="param-type">
152
152
  ${paramSchema.type === 'array'
@@ -497,11 +497,8 @@ export default class ApiRequest extends LitElement {
497
497
  formDataTableRows.push(html`
498
498
  <tr>
499
499
  <td style="width:160px; min-width:100px;">
500
- <div class="param-name">
501
- ${fieldSchema.required
502
- ? html`<span style='color:var(--red);'>*</span>${fieldName}`
503
- : html`${fieldName}`
504
- }
500
+ <div class="param-name ${fieldSchema.deprecated ? 'deprecated' : ''}">
501
+ ${fieldName}${!fieldSchema.deprecated && (schema.required?.includes(fieldName) || fieldSchema.required) ? html`<span style='color:var(--red);'>*</span>` : ''}
505
502
  </div>
506
503
  <div class="param-type">${paramSchema.type}</div>
507
504
  </td>
@@ -813,7 +810,7 @@ export default class ApiRequest extends LitElement {
813
810
  fetchUrl = this.path;
814
811
  const fetchOptions = {
815
812
  method: this.method.toUpperCase(),
816
- headers: {},
813
+ headers: new Headers()
817
814
  };
818
815
  // Generate URL using Path Params
819
816
  pathParamEls.map((el) => {
@@ -896,7 +893,7 @@ export default class ApiRequest extends LitElement {
896
893
  }
897
894
 
898
895
  // Otherwise put it in the header
899
- fetchOptions.headers[v.name] = v.finalKeyValue;
896
+ fetchOptions.headers.append(v.name, v.finalKeyValue);
900
897
  curlHeaders += ` -H "${v.name}: ${v.finalKeyValue}" \\\n`;
901
898
  });
902
899
 
@@ -912,17 +909,17 @@ export default class ApiRequest extends LitElement {
912
909
 
913
910
  if (acceptHeader) {
914
911
  // Uses the acceptHeader from Response panel
915
- fetchOptions.headers.Accept = acceptHeader;
912
+ fetchOptions.headers.append('Accept', acceptHeader);
916
913
  curlHeaders += ` -H "Accept: ${acceptHeader}" \\\n`;
917
914
  } else if (this.accept) {
918
- fetchOptions.headers.Accept = this.accept;
915
+ fetchOptions.headers.append('Accept', this.accept);
919
916
  curlHeaders += ` -H "Accept: ${this.accept}" \\\n`;
920
917
  }
921
918
 
922
919
  // Add Header Params
923
920
  headerParamEls.map((el) => {
924
921
  if (el.value) {
925
- fetchOptions.headers[el.dataset.pname] = el.value;
922
+ fetchOptions.headers.append(el.dataset.pname, el.value);
926
923
  curlHeaders += ` -H "${el.dataset.pname}: ${el.value}" \\\n`;
927
924
  }
928
925
  });
@@ -1004,18 +1001,22 @@ export default class ApiRequest extends LitElement {
1004
1001
  const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
1005
1002
  if (exampleTextAreaEl && exampleTextAreaEl.value) {
1006
1003
  fetchOptions.body = exampleTextAreaEl.value;
1007
- // curlData = ` -d ${JSON.stringify(exampleTextAreaEl.value.replace(/(\r\n|\n|\r)/gm, '')).replace(/\\"/g, "'")} \\ \n`;
1008
- try {
1009
- curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
1010
- } catch (err) {
1011
- curlData = ` -d '${exampleTextAreaEl.value.replace(/(\r\n|\n|\r)/gm, '')}' \\\n`;
1004
+ if (requestBodyType.includes('json')) {
1005
+ try {
1006
+ curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
1007
+ } catch (err) { /* Ignore unparseable JSON */ }
1008
+ }
1009
+
1010
+ if (!curlData) {
1011
+ // Save single quotes wrapped => 'text' => `"'"text"'"`
1012
+ curlData = ` -d '${exampleTextAreaEl.value.replace(/'/g, '\'"\'"\'')}' \\\n`;
1012
1013
  }
1013
1014
  }
1014
1015
  }
1015
1016
  // Common for all request-body
1016
1017
  if (!requestBodyType.includes('form-data')) {
1017
1018
  // For multipart/form-data don't set the content-type to allow creation of browser generated part boundaries
1018
- fetchOptions.headers['Content-Type'] = requestBodyType;
1019
+ fetchOptions.headers.append('Content-Type', requestBodyType);
1019
1020
  }
1020
1021
  curlHeaders += ` -H "Content-Type: ${requestBodyType}" \\\n`;
1021
1022
  }
@@ -199,23 +199,6 @@ export default class OpenApiExplorer extends LitElement {
199
199
  background-image: linear-gradient(to right, rgba(0,0,0,0), var(--border-color), rgba(0,0,0,0));
200
200
  }
201
201
 
202
- .section-tag-header:hover::after {
203
- position:absolute;
204
- margin-left:-24px;
205
- font-size:20px;
206
- top: calc(50% - 14px);
207
- color:var(--primary-color);
208
- content: '⬆';
209
- }
210
-
211
- .collapsed .section-tag-header::after {
212
- position:absolute;
213
- margin-left:-24px;
214
- font-size:20px;
215
- top: calc(50% - 14px);
216
- color: var(--border-color);
217
- content: '⬇';
218
- }
219
202
  .collapsed .section-tag-header:hover::after {
220
203
  color:var(--primary-color);
221
204
  }
@@ -19,6 +19,9 @@ export default css`
19
19
  color: var(--fg);
20
20
  font-family: var(--font-mono);
21
21
  }
22
+ .api-request .param-name.deprecated {
23
+ text-decoration: line-through;
24
+ }
22
25
  .api-request .param-type {
23
26
  color: var(--light-fg);
24
27
  font-family: var(--font-regular);
@@ -21,21 +21,33 @@ function toggleExpand(path) {
21
21
  this.requestUpdate();
22
22
  }
23
23
 
24
- export function expandCollapseAll(operationsRootEl, action = 'expand-all') {
25
- const elList = [...operationsRootEl.querySelectorAll('.section-tag')];
26
- if (action === 'expand-all') {
27
- elList.map((el) => {
28
- el.classList.replace('collapsed', 'expanded');
29
- });
24
+ function toggleTag(tagElement, tagId) {
25
+ const sectionTag = tagElement.target.closest('.section-tag');
26
+ const tag = this.resolvedSpec.tags.find(t => t.elementId === tagId);
27
+ tag.expanded = !tag.expanded;
28
+ if (tag.expanded) {
29
+ sectionTag.classList.remove('collapsed');
30
+ sectionTag.classList.add('expanded');
30
31
  } else {
31
- elList.map((el) => {
32
- el.classList.replace('expanded', 'collapsed');
33
- });
32
+ sectionTag.classList.remove('expanded');
33
+ sectionTag.classList.add('collapsed');
34
34
  }
35
+ this.requestUpdate();
35
36
  }
36
-
37
- function onExpandCollapseAll(e, action = 'expand-all') {
38
- expandCollapseAll.call(this, e.target.closest('.operations-root'), action);
37
+ export function expandCollapseAll(currentElement, action = 'expand-all') {
38
+ const operationsRootEl = currentElement.target.closest('.operations-root');
39
+ const elList = [...operationsRootEl.querySelectorAll('.section-tag')];
40
+ const expand = action === 'expand-all';
41
+ this.resolvedSpec.tags.forEach(t => t.expanded = expand);
42
+ elList.map((el) => {
43
+ if (expand) {
44
+ el.classList.remove('collapsed');
45
+ el.classList.add('expanded');
46
+ } else {
47
+ el.classList.remove('expanded');
48
+ el.classList.add('collapsed');
49
+ }
50
+ });
39
51
  }
40
52
 
41
53
  /* eslint-disable indent */
@@ -127,14 +139,14 @@ function endpointBodyTemplate(path) {
127
139
  export default function endpointTemplate() {
128
140
  return html`
129
141
  <div style="display:flex; justify-content:flex-end; padding-right: 1rem; font-size: 14px;">
130
- <span @click="${(e) => onExpandCollapseAll(e, 'expand-all')}" style="color:var(--primary-color); cursor: pointer;">Expand</span>
142
+ <span @click="${(e) => expandCollapseAll.call(this, e, 'expand-all')}" style="color:var(--primary-color); cursor: pointer;">Expand</span>
131
143
  &nbsp;|&nbsp;
132
- <span @click="${(e) => onExpandCollapseAll(e, 'collapse-all')}" style="color:var(--primary-color); cursor: pointer;">Collapse</span>
144
+ <span @click="${(e) => expandCollapseAll.call(this, e, 'collapse-all')}" style="color:var(--primary-color); cursor: pointer;">Collapse</span>
133
145
  </div>
134
146
  ${(this.resolvedSpec && this.resolvedSpec.tags || []).map((tag) => html`
135
147
  <div class='regular-font section-gap section-tag ${tag.expanded ? 'expanded' : 'collapsed'}' >
136
148
 
137
- <div class='section-tag-header' @click="${() => { tag.expanded = !tag.expanded; this.requestUpdate(); }}">
149
+ <div class='section-tag-header' @click="${(e) => toggleTag.call(this, e, tag.elementId)}">
138
150
  <div id='${tag.elementId}' class="sub-title tag" style="color:var(--primary-color)">${tag.name}</div>
139
151
  </div>
140
152
  <div class='section-tag-body'>
@@ -5,11 +5,13 @@ import { pathIsInSearch } from '../utils/common-utils';
5
5
  export function expandCollapseNavBarTag(navLinkEl, action = 'toggle') {
6
6
  const tagAndPathEl = navLinkEl && navLinkEl.closest('.nav-bar-tag-and-paths');
7
7
  if (tagAndPathEl) {
8
- const isExpanded = tagAndPathEl.classList.contains('expanded');
9
- if (isExpanded && (action === 'toggle' || action === 'collapse')) {
10
- tagAndPathEl.classList.replace('expanded', 'collapsed');
11
- } else if (!isExpanded && (action === 'toggle' || action === 'expand')) {
12
- tagAndPathEl.classList.replace('collapsed', 'expanded');
8
+ const expand = tagAndPathEl.classList.contains('collapsed') && action === 'toggle' || action === 'expand';
9
+ if (expand) {
10
+ tagAndPathEl.classList.remove('collapsed');
11
+ tagAndPathEl.classList.add('expanded');
12
+ } else {
13
+ tagAndPathEl.classList.remove('expanded');
14
+ tagAndPathEl.classList.add('collapsed');
13
15
  }
14
16
  }
15
17
  }
@@ -18,11 +20,11 @@ export function expandCollapseAll(navEl, action = 'expand-all') {
18
20
  const elList = [...navEl.querySelectorAll('.nav-bar-tag-and-paths')];
19
21
  if (action === 'expand-all') {
20
22
  elList.map((el) => {
21
- el.classList.replace('collapsed', 'expanded');
23
+ expandCollapseNavBarTag(el, 'expand');
22
24
  });
23
25
  } else {
24
26
  elList.map((el) => {
25
- el.classList.replace('expanded', 'collapsed');
27
+ expandCollapseNavBarTag(el, 'collapse');
26
28
  });
27
29
  }
28
30
  }
@@ -386,25 +386,35 @@ export default function securitySchemeTemplate() {
386
386
  `;
387
387
  }
388
388
 
389
+ function getOauthScopeTemplate(scopes) {
390
+ if (!scopes || !scopes.length || !Array.isArray(scopes)) {
391
+ return '';
392
+ }
393
+
394
+ return html`
395
+ <div>
396
+ <b>Required scopes:</b>
397
+ <br/>
398
+ <div style="margin-left:8px">
399
+ ${scopes.map(scope => html`<span>${scope}</span>&nbsp;`)}
400
+ </div>
401
+ </div>`;
402
+ }
403
+
389
404
  export function pathSecurityTemplate(pathSecurity) {
390
405
  if (this.resolvedSpec.securitySchemes && pathSecurity) {
391
406
  const orSecurityKeys1 = [];
392
407
  pathSecurity.forEach((pSecurity) => {
393
408
  const andSecurityKeys1 = [];
394
409
  const andKeyTypes = [];
395
- let pathScopes = '';
396
410
  Object.keys(pSecurity).forEach((pathSecurityKey) => {
397
411
  const s = this.resolvedSpec.securitySchemes.find((ss) => ss.apiKeyId === pathSecurityKey);
398
- if (!pathScopes) {
399
- pathScopes = pSecurity[pathSecurityKey].join(', ');
400
- }
401
412
  if (s) {
402
413
  andKeyTypes.push(s.typeDisplay);
403
- andSecurityKeys1.push(s);
414
+ andSecurityKeys1.push({ ...s, scopes: pSecurity[pathSecurityKey] });
404
415
  }
405
416
  });
406
417
  orSecurityKeys1.push({
407
- pathScopes,
408
418
  securityTypes: andKeyTypes.length > 1 ? `${andKeyTypes[0]} + ${andKeyTypes.length - 1} more` : andKeyTypes[0],
409
419
  securityDefs: andSecurityKeys1,
410
420
  });
@@ -430,22 +440,21 @@ export function pathSecurityTemplate(pathSecurity) {
430
440
  ? html`
431
441
  <div>
432
442
  ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> &nbsp;` : html`Requires`}
433
- OAuth Token (${andSecurityItem.apiKeyId}) in <b>Authorization header</b>
434
- ${orSecurityItem1.pathScopes !== ''
435
- ? html`. Required scopes: <ul>${orSecurityItem1.pathScopes.split(',').map((scope) => html`<li>${scope}</li>`)}</ul>`
436
- : ''
437
- }
443
+ OAuth token (${andSecurityItem.apiKeyId}) in <b>Authorization header</b>
444
+ ${getOauthScopeTemplate(andSecurityItem.scopes)}
438
445
  </div>`
439
446
  : andSecurityItem.type === 'http'
440
447
  ? html`
441
448
  <div>
442
449
  ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> &nbsp;` : html`Requires`}
443
450
  ${andSecurityItem.scheme === 'basic' ? 'Base 64 encoded username:password' : 'Bearer Token'} in <b>Authorization header</b>
451
+ ${getOauthScopeTemplate(andSecurityItem.scopes)}
444
452
  </div>`
445
453
  : html`
446
454
  <div>
447
455
  ${orSecurityItem1.securityDefs.length > 1 ? html`<b>${j + 1}.</b> &nbsp;` : html`Requires`}
448
456
  Token in <b>${andSecurityItem.name} ${andSecurityItem.in}</b>
457
+ ${getOauthScopeTemplate(andSecurityItem.scopes)}
449
458
  </div>`
450
459
  }
451
460
  `)}
@@ -631,7 +631,8 @@ export function schemaInObjectNotation(schema, obj, level = 0, suffix = '') {
631
631
  }
632
632
 
633
633
  /* Create Example object */
634
- export function generateExample(examples, example, schema, mimeType, includeReadOnly = true, includeWriteOnly = true, outputType) {
634
+ export function generateExample(examples, example, schema, rawMimeType, includeReadOnly = true, includeWriteOnly = true, outputType) {
635
+ const mimeType = rawMimeType || 'application/json';
635
636
  const finalExamples = [];
636
637
  // First check if examples is provided
637
638
  if (examples) {
@@ -54,7 +54,7 @@ export default async function ProcessSpec(requiresLookup, specUrlOrObject, gener
54
54
  } else if (v.type === 'apiKey') {
55
55
  v.typeDisplay = `API Key (${v.name})`;
56
56
  } else if (v.type === 'oauth2') {
57
- v.typeDisplay = `OAuth (${v.apiKeyId})`;
57
+ v.typeDisplay = 'OAuth2.0';
58
58
  } else {
59
59
  v.typeDisplay = v.type;
60
60
  }
@@ -281,6 +281,18 @@ function groupByTags(openApiSpec, generateMissingTags = false) {
281
281
  finalParameters = pathOrHookObj.parameters ? pathOrHookObj.parameters.slice(0) : [];
282
282
  }
283
283
 
284
+ // Remove bad callbacks
285
+ if (pathOrHookObj.callbacks) {
286
+ for (const [callbackName, callbackConfig] of Object.entries(pathOrHookObj.callbacks)) {
287
+ const originalCallbackEntries = Object.entries(callbackConfig);
288
+ const filteredCallbacks = originalCallbackEntries.filter((entry) => typeof entry[1] === 'object') || [];
289
+ pathOrHookObj.callbacks[callbackName] = Object.fromEntries(filteredCallbacks);
290
+ if (filteredCallbacks.length !== originalCallbackEntries.length) {
291
+ console.warn(`OpenAPI Explorer: Invalid Callback found in ${callbackName}`); // eslint-disable-line no-console
292
+ }
293
+ }
294
+ }
295
+
284
296
  // Update Responses
285
297
  tagObj.paths.push({
286
298
  show: true,