openapi-explorer 0.9.310 → 0.9.314

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/{openapi-explorer.min.js → browser/openapi-explorer.min.js} +3 -3
  3. package/dist/{openapi-explorer.min.js.map → browser/openapi-explorer.min.js.map} +0 -0
  4. package/dist/es/components/api-request.js +936 -0
  5. package/dist/es/components/api-response.js +184 -0
  6. package/dist/es/components/json-tree.js +67 -0
  7. package/{src → dist/es}/components/mime-types.js +11 -17
  8. package/dist/es/components/schema-table.js +156 -0
  9. package/dist/es/components/schema-tree.js +191 -0
  10. package/dist/es/components/tag-input.js +67 -0
  11. package/{src → dist/es}/openapi-explorer-oauth-handler.js +2 -2
  12. package/{src → dist/es}/openapi-explorer.js +364 -371
  13. package/dist/es/styles/advanced-search-styles.js +2 -0
  14. package/dist/es/styles/api-request-styles.js +2 -0
  15. package/dist/es/styles/border-styles.js +2 -0
  16. package/dist/es/styles/endpoint-styles.js +2 -0
  17. package/dist/es/styles/flex-styles.js +2 -0
  18. package/dist/es/styles/font-styles.js +2 -0
  19. package/dist/es/styles/info-styles.js +2 -0
  20. package/dist/es/styles/input-styles.js +4 -0
  21. package/dist/es/styles/nav-styles.js +2 -0
  22. package/dist/es/styles/prism-styles.js +2 -0
  23. package/dist/es/styles/schema-styles.js +2 -0
  24. package/dist/es/styles/tab-styles.js +2 -0
  25. package/dist/es/styles/table-styles.js +2 -0
  26. package/dist/es/templates/advance-search-template.js +37 -0
  27. package/dist/es/templates/callback-template.js +7 -0
  28. package/dist/es/templates/code-samples-template.js +26 -0
  29. package/dist/es/templates/components-template.js +17 -0
  30. package/dist/es/templates/endpoint-template.js +94 -0
  31. package/dist/es/templates/expanded-endpoint-template.js +32 -0
  32. package/{src → dist/es}/templates/focused-endpoint-template.js +15 -15
  33. package/dist/es/templates/navbar-template.js +46 -0
  34. package/dist/es/templates/overview-template.js +9 -0
  35. package/dist/es/templates/responsiveViewMainBodyTemplate.js +30 -0
  36. package/dist/es/templates/security-scheme-template.js +330 -0
  37. package/dist/es/templates/server-template.js +42 -0
  38. package/{src → dist/es}/utils/color-utils.js +53 -16
  39. package/{src → dist/es}/utils/common-utils.js +18 -18
  40. package/{src → dist/es}/utils/schema-utils.js +248 -124
  41. package/{src → dist/es}/utils/spec-parser.js +112 -71
  42. package/dist/es/utils/theme.js +75 -0
  43. package/{src → dist/es}/utils/xml/xml.js +41 -38
  44. package/dist/lib/components/api-request.js +957 -0
  45. package/dist/lib/components/api-response.js +206 -0
  46. package/dist/lib/components/json-tree.js +82 -0
  47. package/dist/lib/components/mime-types.js +70 -0
  48. package/dist/lib/components/schema-table.js +170 -0
  49. package/dist/lib/components/schema-tree.js +206 -0
  50. package/dist/lib/components/tag-input.js +76 -0
  51. package/dist/lib/openapi-explorer-oauth-handler.js +19 -0
  52. package/dist/lib/openapi-explorer.js +817 -0
  53. package/dist/lib/styles/advanced-search-styles.js +10 -0
  54. package/dist/lib/styles/api-request-styles.js +10 -0
  55. package/dist/lib/styles/border-styles.js +10 -0
  56. package/dist/lib/styles/endpoint-styles.js +10 -0
  57. package/dist/lib/styles/flex-styles.js +10 -0
  58. package/dist/lib/styles/font-styles.js +10 -0
  59. package/dist/lib/styles/info-styles.js +10 -0
  60. package/dist/lib/styles/input-styles.js +11 -0
  61. package/dist/lib/styles/nav-styles.js +10 -0
  62. package/dist/lib/styles/prism-styles.js +10 -0
  63. package/dist/lib/styles/schema-styles.js +10 -0
  64. package/dist/lib/styles/tab-styles.js +10 -0
  65. package/dist/lib/styles/table-styles.js +10 -0
  66. package/dist/lib/templates/advance-search-template.js +42 -0
  67. package/dist/lib/templates/callback-template.js +12 -0
  68. package/dist/lib/templates/code-samples-template.js +36 -0
  69. package/dist/lib/templates/components-template.js +27 -0
  70. package/dist/lib/templates/endpoint-template.js +111 -0
  71. package/dist/lib/templates/expanded-endpoint-template.js +48 -0
  72. package/dist/lib/templates/focused-endpoint-template.js +95 -0
  73. package/dist/lib/templates/navbar-template.js +54 -0
  74. package/dist/lib/templates/overview-template.js +16 -0
  75. package/dist/lib/templates/responsiveViewMainBodyTemplate.js +47 -0
  76. package/dist/lib/templates/security-scheme-template.js +342 -0
  77. package/dist/lib/templates/server-template.js +49 -0
  78. package/dist/lib/utils/color-utils.js +112 -0
  79. package/dist/lib/utils/common-utils.js +156 -0
  80. package/dist/lib/utils/schema-utils.js +743 -0
  81. package/dist/lib/utils/spec-parser.js +361 -0
  82. package/dist/lib/utils/theme.js +84 -0
  83. package/dist/lib/utils/xml/xml.js +239 -0
  84. package/package.json +19 -6
  85. package/dist/openapi-explorer.min.js.LICENSE.txt +0 -71
  86. package/dist/openapi-explorer.min.js.LICENSE.txt.gz +0 -0
  87. package/dist/openapi-explorer.min.js.gz +0 -0
  88. package/dist/openapi-explorer.min.js.map.gz +0 -0
  89. package/dist/report.html +0 -38
  90. package/src/components/api-request.js +0 -1244
  91. package/src/components/api-response.js +0 -340
  92. package/src/components/json-tree.js +0 -129
  93. package/src/components/schema-table.js +0 -250
  94. package/src/components/schema-tree.js +0 -280
  95. package/src/components/tag-input.js +0 -109
  96. package/src/styles/advanced-search-styles.js +0 -84
  97. package/src/styles/api-request-styles.js +0 -111
  98. package/src/styles/border-styles.js +0 -24
  99. package/src/styles/css/main.css +0 -24
  100. package/src/styles/endpoint-styles.js +0 -222
  101. package/src/styles/flex-styles.js +0 -15
  102. package/src/styles/font-styles.js +0 -266
  103. package/src/styles/info-styles.js +0 -20
  104. package/src/styles/input-styles.js +0 -236
  105. package/src/styles/nav-styles.js +0 -141
  106. package/src/styles/prism-styles.js +0 -107
  107. package/src/styles/schema-styles.js +0 -121
  108. package/src/styles/tab-styles.js +0 -44
  109. package/src/styles/table-styles.js +0 -48
  110. package/src/templates/advance-search-template.js +0 -81
  111. package/src/templates/callback-template.js +0 -63
  112. package/src/templates/code-samples-template.js +0 -35
  113. package/src/templates/components-template.js +0 -43
  114. package/src/templates/endpoint-template.js +0 -175
  115. package/src/templates/expanded-endpoint-template.js +0 -104
  116. package/src/templates/navbar-template.js +0 -175
  117. package/src/templates/overview-template.js +0 -58
  118. package/src/templates/responsiveViewMainBodyTemplate.js +0 -72
  119. package/src/templates/security-scheme-template.js +0 -487
  120. package/src/templates/server-template.js +0 -106
  121. package/src/utils/theme.js +0 -163
@@ -0,0 +1,936 @@
1
+ import { LitElement, html } from 'lit-element';
2
+ import { marked } from 'marked';
3
+ import Prism from 'prismjs';
4
+ import mimeTypeResolver from './mime-types';
5
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html';
6
+ import formatXml from 'xml-but-prettier';
7
+ import { copyToClipboard } from '../utils/common-utils';
8
+ import { schemaInObjectNotation, getTypeInfo, generateExample } from '../utils/schema-utils';
9
+ import './json-tree';
10
+ import './schema-tree';
11
+ import './tag-input';
12
+ const textFileRegex = RegExp('^font/|tar$|zip$|7z$|rtf$|msword$|excel$|/pdf$|/octet-stream$|^application/vnd.');
13
+ const mediaFileRegex = RegExp('^audio/|^image/|^video/');
14
+
15
+ const truncateString = (str, length) => str && str.length > length ? `${str.substring(0, length - 1)}…` : str;
16
+
17
+ export default class ApiRequest extends LitElement {
18
+ createRenderRoot() {
19
+ return this;
20
+ }
21
+
22
+ constructor() {
23
+ super();
24
+ this.responseMessage = '';
25
+ this.responseStatus = '';
26
+ this.responseHeaders = '';
27
+ this.responseText = '';
28
+ this.responseUrl = '';
29
+ this.responseElapsedMs = 0;
30
+ this.curlSyntax = '';
31
+ this.activeResponseTab = 'response'; // allowed values: response, headers, curl
32
+
33
+ this.selectedRequestBodyType = '';
34
+ this.selectedRequestBodyExample = '';
35
+ }
36
+
37
+ static get properties() {
38
+ return {
39
+ serverUrl: {
40
+ type: String,
41
+ attribute: 'server-url'
42
+ },
43
+ servers: {
44
+ type: Array
45
+ },
46
+ method: {
47
+ type: String
48
+ },
49
+ path: {
50
+ type: String
51
+ },
52
+ parameters: {
53
+ type: Array
54
+ },
55
+ request_body: {
56
+ type: Object
57
+ },
58
+ api_keys: {
59
+ type: Array
60
+ },
61
+ parser: {
62
+ type: Object
63
+ },
64
+ accept: {
65
+ type: String
66
+ },
67
+ callback: {
68
+ type: String
69
+ },
70
+ responseMessage: {
71
+ type: String,
72
+ attribute: false
73
+ },
74
+ responseText: {
75
+ type: String,
76
+ attribute: false
77
+ },
78
+ responseHeaders: {
79
+ type: String,
80
+ attribute: false
81
+ },
82
+ responseStatus: {
83
+ type: String,
84
+ attribute: false
85
+ },
86
+ responseUrl: {
87
+ type: String,
88
+ attribute: false
89
+ },
90
+ responseElapsedMs: {
91
+ type: Number,
92
+ attribute: false
93
+ },
94
+ fillRequestWithDefault: {
95
+ type: String,
96
+ attribute: 'fill-defaults'
97
+ },
98
+ allowTry: {
99
+ type: String,
100
+ attribute: 'enable-console'
101
+ },
102
+ renderStyle: {
103
+ type: String,
104
+ attribute: 'render-style'
105
+ },
106
+ schemaStyle: {
107
+ type: String,
108
+ attribute: 'schema-style'
109
+ },
110
+ activeSchemaTab: {
111
+ type: String,
112
+ attribute: 'active-schema-tab'
113
+ },
114
+ schemaExpandLevel: {
115
+ type: Number,
116
+ attribute: 'schema-expand-level'
117
+ },
118
+ schemaDescriptionExpanded: {
119
+ type: String,
120
+ attribute: 'schema-description-expanded'
121
+ },
122
+ schemaHideReadOnly: {
123
+ type: String,
124
+ attribute: 'schema-hide-read-only'
125
+ },
126
+ fetchCredentials: {
127
+ type: String,
128
+ attribute: 'fetch-credentials'
129
+ },
130
+ // properties for internal tracking
131
+ activeResponseTab: {
132
+ type: String
133
+ },
134
+ // internal tracking of response-tab not exposed as a attribute
135
+ selectedRequestBodyType: {
136
+ type: String,
137
+ attribute: 'selected-request-body-type'
138
+ },
139
+ // internal tracking of selected request-body type
140
+ selectedRequestBodyExample: {
141
+ type: String,
142
+ attribute: 'selected-request-body-example'
143
+ } // internal tracking of selected request-body example
144
+
145
+ };
146
+ }
147
+
148
+ render() {
149
+ return html` <div class="api-request col regular-font request-panel ${this.renderStyle === 'focused' || this.callback === 'true' ? 'focused-mode' : 'view-mode'}"> <div class="${this.callback === 'true' ? 'tiny-title' : 'req-res-title'}"> ${this.callback === 'true' ? 'CALLBACK REQUEST' : 'REQUEST'} </div> <div> ${this.inputParametersTemplate('path')} ${this.inputParametersTemplate('query')} ${this.requestBodyTemplate()} ${this.inputParametersTemplate('header')} ${this.inputParametersTemplate('cookie')} ${this.allowTry === 'false' ? '' : html`${this.apiCallTemplate()}`} </div> </div> `;
150
+ }
151
+
152
+ updated(changedProperties) {
153
+ // In focused mode after rendering the request component, update the text-areas(which contains examples) using the original values from hidden textareas.
154
+ // This is done coz, user may update the dom by editing the textarea's and once the DOM is updated externally change detection wont happen, therefore update the values manually
155
+ if (this.renderStyle !== 'focused') {
156
+ return;
157
+ } // dont update example as only tabs is switched
158
+
159
+
160
+ if (changedProperties.size === 1 && changedProperties.has('activeSchemaTab')) {
161
+ return;
162
+ }
163
+
164
+ const exampleTextAreaEls = [...this.querySelectorAll('textarea[data-ptype="form-data"]')];
165
+ exampleTextAreaEls.forEach(el => {
166
+ const origExampleEl = this.querySelector(`textarea[data-pname='hidden-${el.dataset.pname}']`);
167
+
168
+ if (origExampleEl) {
169
+ el.value = origExampleEl.value;
170
+ }
171
+ });
172
+ }
173
+ /* eslint-disable indent */
174
+
175
+
176
+ inputParametersTemplate(paramType) {
177
+ const filteredParams = this.parameters ? this.parameters.filter(param => param.in === paramType) : [];
178
+
179
+ if (filteredParams.length === 0) {
180
+ return '';
181
+ }
182
+
183
+ let title = '';
184
+
185
+ if (paramType === 'path') {
186
+ title = 'PATH PARAMETERS';
187
+ } else if (paramType === 'query') {
188
+ title = 'QUERY-STRING PARAMETERS';
189
+ } else if (paramType === 'header') {
190
+ title = 'REQUEST HEADERS';
191
+ } else if (paramType === 'cookie') {
192
+ title = 'COOKIES';
193
+ }
194
+
195
+ const tableRows = [];
196
+
197
+ for (const param of filteredParams) {
198
+ if (!param.schema) {
199
+ continue;
200
+ }
201
+
202
+ const paramSchema = getTypeInfo(param.schema);
203
+
204
+ if (!paramSchema) {
205
+ continue;
206
+ }
207
+
208
+ const defaultVal = Array.isArray(paramSchema.default) ? paramSchema.default : `${paramSchema.default}`;
209
+ let paramStyle = 'form';
210
+ let paramExplode = true;
211
+
212
+ if (paramType === 'query') {
213
+ if (param.style && 'form spaceDelimited pipeDelimited'.includes(param.style)) {
214
+ paramStyle = param.style;
215
+ }
216
+
217
+ if (typeof param.explode === 'boolean') {
218
+ paramExplode = param.explode;
219
+ }
220
+ }
221
+
222
+ tableRows.push(html` <tr> <td style="width:160px;min-width:50px"> <div class="param-name ${paramSchema.deprecated ? 'deprecated' : ''}"> ${param.name}${!paramSchema.deprecated && param.required ? html`<span style="color:var(--red)">*</span>` : ''} </div> <div class="param-type"> ${paramSchema.type === 'array' ? `${paramSchema.arrayType}` : `${paramSchema.format ? paramSchema.format : paramSchema.type}`}${!paramSchema.deprecated && param.required ? html`<span style="opacity:0">*</span>` : ''} </div> </td> ${this.allowTry === 'true' ? html` <td style="min-width:160px"> ${paramSchema.type === 'array' ? html` <tag-input class="request-param" style="width:100%" data-ptype="${paramType}" data-pname="${param.name}" data-default="${Array.isArray(defaultVal) ? defaultVal.join('~|~') : defaultVal}" data-param-serialize-style="${paramStyle}" data-param-serialize-explode="${paramExplode}" data-array="true" placeholder="${paramSchema.example || (Array.isArray(defaultVal) ? defaultVal[0] : defaultVal) || 'add-multiple &#x21a9;'}" .value="${Array.isArray(defaultVal) ? defaultVal : defaultVal.split(',')}"> </tag-input>` : paramSchema.type === 'object' ? html` <textarea class="textarea request-param" part="textarea textarea-param" data-ptype="${paramType}-object" data-pname="${param.name}" data-default="${defaultVal}" data-param-serialize-style="${paramStyle}" data-param-serialize-explode="${paramExplode}" spellcheck="false" placeholder="${paramSchema.example || defaultVal || ''}" style="resize:vertical;width:100%;height:${'read focused'.includes(this.renderStyle) ? '180px' : '120px'}">${this.fillRequestWithDefault === 'true' ? defaultVal : ''}</textarea>` : html` <input type="${paramSchema.format === 'password' ? 'password' : 'text'}" spellcheck="false" style="width:100%" placeholder="${paramSchema.example || defaultVal || ''}" class="request-param" part="textbox textbox-param" data-ptype="${paramType}" data-pname="${param.name}" data-default="${Array.isArray(defaultVal) ? defaultVal.join('~|~') : defaultVal}" data-array="false" @keyup="${this.requestParamFunction}" .value="${this.fillRequestWithDefault === 'true' ? defaultVal : ''}">`} </td>` : ''} ${this.renderStyle === 'focused' ? html` <td> ${paramSchema.default || paramSchema.constraint || paramSchema.allowedValues || paramSchema.pattern ? html` <div class="param-constraint"> ${paramSchema.constraint ? html`<span style="font-weight:700">Constraints: </span>${paramSchema.constraint}<br>` : ''} ${paramSchema.pattern ? html`<span style="font-weight:700">Pattern: </span>${truncateString(paramSchema.pattern, 60)}<br>` : ''} ${paramSchema.allowedValues && paramSchema.allowedValues.split('┃').map((v, i) => html` ${i > 0 ? '|' : html`<span style="font-weight:700">Allowed: </span>`} ${html` <a part="anchor anchor-param-constraint" class="${this.allowTry === 'true' ? '' : 'inactive-link'}" data-type="${paramSchema.type === 'array' ? paramSchema.type : 'string'}" data-enum="${v.trim()}" @click="${e => {
223
+ const inputEl = e.target.closest('table').querySelector(`[data-pname="${param.name}"]`);
224
+
225
+ if (inputEl) {
226
+ if (e.target.dataset.type === 'array') {
227
+ inputEl.value = [e.target.dataset.enum];
228
+ } else {
229
+ inputEl.value = e.target.dataset.enum;
230
+ }
231
+ }
232
+ }}"> ${v} </a>`}`)} </div>` : ''} </td> ` : ''} </tr>`);
233
+ }
234
+
235
+ return html` <div class="table-title top-gap">${title}${paramType === 'path' ? html`<span style="color:var(--red)">*</span>` : ''}</div> <div style="display:block;overflow-x:auto;max-width:100%"> <table role="presentation" class="m-table" style="width:100%;word-break:break-word"> ${tableRows} </table> </div>`;
236
+ }
237
+
238
+ resetRequestBodySelection() {
239
+ this.selectedRequestBodyType = '';
240
+ this.selectedRequestBodyExample = '';
241
+ this.clearResponseData();
242
+ } // Request-Body Event Handlers
243
+
244
+
245
+ onSelectExample(e) {
246
+ this.selectedRequestBodyExample = e.target.value;
247
+ const exampleDropdownEl = e.target;
248
+ window.setTimeout(selectEl => {
249
+ const exampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param');
250
+ const userInputExampleTextareaEl = selectEl.closest('.example-panel').querySelector('.request-body-param-user-input');
251
+ userInputExampleTextareaEl.value = exampleTextareaEl.value;
252
+ }, 0, exampleDropdownEl);
253
+ }
254
+
255
+ onMimeTypeChange(e) {
256
+ this.selectedRequestBodyType = e.target.value;
257
+ const mimeDropdownEl = e.target;
258
+ this.selectedRequestBodyExample = '';
259
+ window.setTimeout(selectEl => {
260
+ const exampleTextareaEl = selectEl.closest('.request-body-container').querySelector('.request-body-param');
261
+
262
+ if (exampleTextareaEl) {
263
+ const userInputExampleTextareaEl = selectEl.closest('.request-body-container').querySelector('.request-body-param-user-input');
264
+ userInputExampleTextareaEl.value = exampleTextareaEl.value;
265
+ }
266
+ }, 0, mimeDropdownEl);
267
+ }
268
+
269
+ requestBodyTemplate() {
270
+ if (!this.request_body) {
271
+ return '';
272
+ }
273
+
274
+ if (Object.keys(this.request_body).length === 0) {
275
+ return '';
276
+ } // Variable to store partial HTMLs
277
+
278
+
279
+ let reqBodyTypeSelectorHtml = '';
280
+ let reqBodyFileInputHtml = '';
281
+ let reqBodyFormHtml = '';
282
+ let reqBodySchemaHtml = '';
283
+ let reqBodyExampleHtml = '';
284
+ const requestBodyTypes = [];
285
+ const content = this.request_body.content;
286
+
287
+ for (const mimeType in content) {
288
+ requestBodyTypes.push({
289
+ mimeType,
290
+ schema: content[mimeType].schema,
291
+ example: content[mimeType].example,
292
+ examples: content[mimeType].examples
293
+ });
294
+
295
+ if (!this.selectedRequestBodyType) {
296
+ this.selectedRequestBodyType = mimeType;
297
+ }
298
+ } // MIME Type selector
299
+
300
+
301
+ reqBodyTypeSelectorHtml = requestBodyTypes.length === 1 ? '' : html` <select aria-label="mime type" style="min-width:100px;max-width:100%;margin-bottom:-1px" @change="${e => this.onMimeTypeChange(e)}"> ${requestBodyTypes.map(reqBody => html` <option value="${reqBody.mimeType}" ?selected="${reqBody.mimeType === this.selectedRequestBodyType}"> ${reqBody.mimeType} </option> `)} </select> `; // For Loop - Main
302
+
303
+ requestBodyTypes.forEach(reqBody => {
304
+ let reqBodyExamples = [];
305
+
306
+ if (this.selectedRequestBodyType.includes('json') || this.selectedRequestBodyType.includes('xml') || this.selectedRequestBodyType.includes('text')) {
307
+ // Generate Example
308
+ if (reqBody.mimeType === this.selectedRequestBodyType) {
309
+ reqBodyExamples = generateExample(reqBody.examples ? reqBody.examples : '', reqBody.example ? reqBody.example : '', reqBody.schema, reqBody.mimeType, false, true, 'text', true);
310
+
311
+ if (!this.selectedRequestBodyExample) {
312
+ this.selectedRequestBodyExample = reqBodyExamples.length > 0 ? reqBodyExamples[0].exampleId : '';
313
+ }
314
+
315
+ reqBodyExampleHtml = html` ${reqBodyExampleHtml} <div class="example-panel border-top pad-top-8"> ${reqBodyExamples.length === 1 ? '' : html` <select aria-label="request body example" style="min-width:100px;max-width:100%;margin-bottom:-1px" @change="${e => this.onSelectExample(e)}"> ${reqBodyExamples.map(v => html`<option value="${v.exampleId}" ?selected="${v.exampleId === this.selectedRequestBodyExample}"> ${v.exampleSummary.length > 80 ? v.exampleId : v.exampleSummary ? v.exampleSummary : v.exampleId} </option>`)} </select> `} ${reqBodyExamples.filter(v => v.exampleId === this.selectedRequestBodyExample).map(v => html` <div class="example ${v.exampleId === this.selectedRequestBodyExample ? 'example-selected' : ''}" data-default="${v.exampleId}"> ${v.exampleSummary && v.exampleSummary.length > 80 ? html`<div style="padding:4px 0"> ${v.exampleSummary} </div>` : ''} ${v.exampleDescription ? html`<div class="m-markdown-small" style="padding:4px 0"> ${unsafeHTML(marked(v.exampleDescription || ''))} </div>` : ''} <textarea class="textarea request-body-param-user-input" part="textarea textarea-param" spellcheck="false" data-ptype="${reqBody.mimeType}" data-default="${v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 8)}" data-default-format="${v.exampleFormat}" style="width:100%;resize:vertical">${this.fillRequestWithDefault === 'true' ? v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 8) : ''}</textarea> <textarea class="textarea is-hidden request-body-param ${reqBody.mimeType.substring(reqBody.mimeType.indexOf('/') + 1)}" spellcheck="false" data-ptype="${reqBody.mimeType}" style="width:100%;resize:vertical;display:none">${v.exampleFormat === 'text' ? v.exampleValue : JSON.stringify(v.exampleValue, null, 8)}</textarea> </div> `)} </div> `;
316
+ }
317
+ } else if (this.selectedRequestBodyType.includes('form-urlencoded') || this.selectedRequestBodyType.includes('form-data')) {
318
+ if (reqBody.mimeType === this.selectedRequestBodyType) {
319
+ const ex = generateExample(reqBody.examples ? reqBody.examples : '', reqBody.example ? reqBody.example : '', reqBody.schema, reqBody.mimeType, false, true, 'text', true);
320
+
321
+ if (reqBody.schema) {
322
+ reqBodyFormHtml = this.formDataTemplate(reqBody.schema, reqBody.mimeType, ex[0] ? ex[0].exampleValue : '');
323
+ }
324
+ }
325
+ } else if (mediaFileRegex.test(this.selectedRequestBodyType) || textFileRegex.test(this.selectedRequestBodyType)) {
326
+ if (reqBody.mimeType === this.selectedRequestBodyType) {
327
+ reqBodyFileInputHtml = html` <div class="small-font-size bold-text row"> <input type="file" part="file-input" style="max-width:100%" class="request-body-param-file" data-ptype="${reqBody.mimeType}" spellcheck="false"> </div> `;
328
+ }
329
+ } // Generate Schema
330
+
331
+
332
+ if (reqBody.mimeType.includes('json') || reqBody.mimeType.includes('xml') || reqBody.mimeType.includes('text')) {
333
+ const schemaAsObj = schemaInObjectNotation(reqBody.schema, {});
334
+
335
+ if (this.schemaStyle === 'table') {
336
+ reqBodySchemaHtml = html` ${reqBodySchemaHtml} <schema-table class="${reqBody.mimeType.substring(reqBody.mimeType.indexOf('/') + 1)} pad-top-8" style="display:${this.selectedRequestBodyType === reqBody.mimeType ? 'block' : 'none'}" .data="${schemaAsObj}" schema-expand-level="${this.schemaExpandLevel}" schema-description-expanded="${this.schemaDescriptionExpanded}" schema-hide-read-only="${this.schemaHideReadOnly.includes(this.method)}" schema-hide-write-only="false"> </schema-table> `;
337
+ } else if (this.schemaStyle === 'tree') {
338
+ reqBodySchemaHtml = html` ${reqBodySchemaHtml} <schema-tree class="${reqBody.mimeType.substring(reqBody.mimeType.indexOf('/') + 1)} pad-top-8" style="display:${this.selectedRequestBodyType === reqBody.mimeType ? 'block' : 'none'}" .data="${schemaAsObj}" schema-expand-level="${this.schemaExpandLevel}" schema-description-expanded="${this.schemaDescriptionExpanded}" schema-hide-read-only="${this.schemaHideReadOnly.includes(this.method)}" schema-hide-write-only="false"> </schema-tree> `;
339
+ }
340
+ }
341
+ });
342
+ return html` <div class="request-body-container" data-selected-request-body-type="${this.selectedRequestBodyType}"> <div class="table-title top-gap row"> REQUEST BODY ${this.request_body.required ? html`<span class="mono-font" style="color:var(--red)">*</span>` : ''} <span style="font-weight:400;margin-left:5px"> ${this.selectedRequestBodyType}</span> <span style="flex:1"></span> ${reqBodyTypeSelectorHtml} </div> ${this.request_body.description ? html`<div class="m-markdown" style="margin-bottom:12px">${unsafeHTML(marked(this.request_body.description))}</div>` : ''} ${this.selectedRequestBodyType.includes('json') || this.selectedRequestBodyType.includes('xml') || this.selectedRequestBodyType.includes('text') ? html` <div class="tab-panel col" style="border-width:0 0 1px 0"> <div class="tab-buttons row" @click="${e => {
343
+ if (e.target.tagName.toLowerCase() === 'button') {
344
+ this.activeSchemaTab = e.target.dataset.tab;
345
+ }
346
+ }}"> <button class="tab-btn ${this.activeSchemaTab === 'model' ? 'active' : ''}" data-tab="model">MODEL</button> <button class="tab-btn ${this.activeSchemaTab === 'body' ? 'active' : ''}" data-tab="body">BODY</button> </div> ${html`<div class="tab-content col" style="display:${this.activeSchemaTab === 'model' ? 'block' : 'none'}"> ${reqBodySchemaHtml}</div>`} ${html`<div class="tab-content col" style="display:${this.activeSchemaTab === 'model' ? 'none' : 'block'}"> ${reqBodyExampleHtml}</div>`} </div>` : html` ${reqBodyFileInputHtml} ${reqBodyFormHtml}`} </div> `;
347
+ }
348
+
349
+ formDataTemplate(schema, mimeType, exampleValue = '') {
350
+ const formDataTableRows = [];
351
+
352
+ if (schema.properties) {
353
+ for (const fieldName in schema.properties) {
354
+ const fieldSchema = schema.properties[fieldName];
355
+
356
+ if (fieldSchema.readOnly) {
357
+ continue;
358
+ }
359
+
360
+ const fieldType = fieldSchema.type;
361
+ const formdataPartSchema = schemaInObjectNotation(fieldSchema, {});
362
+ const paramSchema = getTypeInfo(fieldSchema);
363
+ const formdataPartExample = generateExample('', fieldSchema.example ? fieldSchema.example : '', fieldSchema, 'json', false, true, 'text', true);
364
+ formDataTableRows.push(html` <tr> <td style="width:160px;min-width:100px"> <div class="param-name ${fieldSchema.deprecated ? 'deprecated' : ''}"> ${fieldName}${!fieldSchema.deprecated && (schema.required && schema.required.includes(fieldName) || fieldSchema.required) ? html`<span style="color:var(--red)">*</span>` : ''} </div> <div class="param-type">${paramSchema.type}</div> </td> <td style="${fieldType === 'object' ? 'width:100%; padding:0;' : this.allowTry === 'true' ? '' : 'display:none;'} min-width:100px" colspan="${fieldType === 'object' ? 2 : 1}"> ${fieldType === 'array' ? fieldSchema.items && fieldSchema.items.format === 'binary' ? html` <div class="file-input-container col" style="align-items:flex-end" @click="${e => this.onAddRemoveFileInput(e, fieldName, mimeType)}"> <div class="input-set row"> <input type="file" part="file-input" style="width:100%" data-pname="${fieldName}" data-ptype="${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-array="false" data-file-array="true"> <button class="file-input-remove-btn"> &#x2715; </button> </div> <button class="m-btn primary file-input-add-btn" part="btn btn-fill" style="margin:2px 25px 0 0;padding:2px 6px">ADD</button> </div> ` : html` <tag-input style="width:100%" data-ptype="${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-pname="${fieldName}" data-default="${paramSchema.default || ''}" data-array="true" placeholder="${(Array.isArray(paramSchema.example) ? paramSchema.example[0] : paramSchema.example) || 'add-multiple &#x21a9;'}" .value="${paramSchema.default || ''}"> </tag-input> ` : html` ${fieldType === 'object' ? html` <div class="tab-panel row" style="min-height:220px;border-left:6px solid var(--light-border-color);align-items:stretch"> <div style="width:24px;background-color:var(--light-border-color)"> <div class="row" style="flex-direction:row-reverse;width:160px;height:24px;transform:rotate(270deg) translateX(-160px);transform-origin:top left;display:block" @click="${e => {
365
+ if (e.target.classList.contains('v-tab-btn')) {
366
+ const tab = e.target.dataset.tab;
367
+
368
+ if (tab) {
369
+ const tabPanelEl = e.target.closest('.tab-panel');
370
+ const selectedTabBtnEl = tabPanelEl.querySelector(`.v-tab-btn[data-tab="${tab}"]`);
371
+ const otherTabBtnEl = [...tabPanelEl.querySelectorAll(`.v-tab-btn:not([data-tab="${tab}"])`)];
372
+ const selectedTabContentEl = tabPanelEl.querySelector(`.tab-content[data-tab="${tab}"]`);
373
+ const otherTabContentEl = [...tabPanelEl.querySelectorAll(`.tab-content:not([data-tab="${tab}"])`)];
374
+ selectedTabBtnEl.classList.add('active');
375
+ selectedTabContentEl.style.display = 'block';
376
+ otherTabBtnEl.forEach(el => {
377
+ el.classList.remove('active');
378
+ });
379
+ otherTabContentEl.forEach(el => {
380
+ el.style.display = 'none';
381
+ });
382
+ }
383
+ }
384
+
385
+ if (e.target.tagName.toLowerCase() === 'button') {
386
+ this.activeSchemaTab = e.target.dataset.tab;
387
+ }
388
+ }}"> <button class="v-tab-btn ${this.activeSchemaTab === 'model' ? 'active' : ''}" data-tab="model">MODEL</button> <button class="v-tab-btn ${this.activeSchemaTab === 'body' ? 'active' : ''}" data-tab="body">REQUEST BODY</button> </div> </div> ${html` <div class="tab-content col" data-tab="model" style="display:${this.activeSchemaTab === 'model' ? 'block' : 'none'};padding-left:5px;width:100%"> <schema-tree .data="${formdataPartSchema}" schema-expand-level="${this.schemaExpandLevel}" schema-description-expanded="${this.schemaDescriptionExpanded}"> </schema-tree> </div>`} ${html` <div class="tab-content col" data-tab="example" style="display:${this.activeSchemaTab === 'body' ? 'block' : 'none'};padding-left:5px;width:100%"> <textarea class="textarea" placeholder="${formdataPartExample[0] && formdataPartExample[0].exampleValue || paramSchema.default || ''}" part="textarea textarea-param" style="width:100%;border:none;resize:vertical" data-array="false" data-ptype="${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-pname="${fieldName}" data-default="${paramSchema.default || ''}" spellcheck="false">${this.fillRequestWithDefault === 'true' ? paramSchema.default : ''}</textarea> <textarea data-pname="hidden-${fieldName}" data-ptype="${mimeType.includes('form-urlencode') ? 'hidden-form-urlencode' : 'hidden-form-data'}" class="is-hidden" style="display:none">${paramSchema.default}</textarea> </div>`} </div>` : html` ${this.allowTry === 'true' ? html`<input placeholder="${paramSchema.example || paramSchema.default || ''}" .value="${this.fillRequestWithDefault === 'true' ? paramSchema.default || '' : ''}" spellcheck="false" type="${fieldSchema.format === 'binary' ? 'file' : fieldSchema.format === 'password' ? 'password' : 'text'}" part="textbox textbox-param" style="width:100%" data-ptype="${mimeType.includes('form-urlencode') ? 'form-urlencode' : 'form-data'}" data-pname="${fieldName}" data-default="${paramSchema.default || ''}" data-array="false">` : ''} `}`} </td> ${fieldType === 'object' ? '' : html` <td> ${paramSchema.default || paramSchema.constraint || paramSchema.allowedValues || paramSchema.pattern ? html` <div class="param-constraint"> ${paramSchema.pattern ? html`<span style="font-weight:700">Pattern: </span>${paramSchema.pattern}<br>` : ''} ${paramSchema.constraint ? html`<span style="font-weight:700">Constraints: </span>${paramSchema.constraint}<br>` : ''} ${paramSchema.allowedValues && paramSchema.allowedValues.split('┃').map((v, i) => html` ${i > 0 ? '|' : html`<span style="font-weight:700">Allowed: </span>`} ${html` <a part="anchor anchor-param-constraint" class="${this.allowTry === 'true' ? '' : 'inactive-link'}" data-type="${paramSchema.type === 'array' ? paramSchema.type : 'string'}" data-enum="${v.trim()}" @click="${e => {
389
+ const inputEl = e.target.closest('table').querySelector(`[data-pname="${fieldName}"]`);
390
+
391
+ if (inputEl) {
392
+ if (e.target.dataset.type === 'array') {
393
+ inputEl.value = [e.target.dataset.enum];
394
+ } else {
395
+ inputEl.value = e.target.dataset.enum;
396
+ }
397
+ }
398
+ }}"> ${v} </a>`}`)} </div>` : ''} </td>`} </tr> ${fieldType === 'object' ? '' : html` <tr> <td style="border:none"> </td> <td colspan="2" style="border:none;margin-top:0;padding:0 5px 8px 5px"> <span class="m-markdown-small">${unsafeHTML(marked(fieldSchema.description || ''))}</span> ${paramSchema.example ? html` <span> <span style="font-weight:700"> Example: </span> ${paramSchema.type === 'array' ? '[ ' : ''} <a part="anchor anchor-param-example" class="${this.allowTry === 'true' ? '' : 'inactive-link'}" data-default-type="${paramSchema.type === 'array' ? paramSchema.type : 'string'}" data-default="${Array.isArray(paramSchema.example) && paramSchema.example.join('~|~') || paramSchema.example || ''}" @click="${e => {
399
+ const inputEl = e.target.closest('table').querySelector(`[data-pname="${fieldName}"]`);
400
+
401
+ if (inputEl) {
402
+ if (e.target.dataset.exampleType === 'array') {
403
+ inputEl.value = e.target.dataset.example.split('~|~');
404
+ } else {
405
+ inputEl.value = e.target.dataset.example;
406
+ }
407
+ }
408
+ }}"> ${paramSchema.type === 'array' ? paramSchema.example.join(', ') : paramSchema.example} </a> ${paramSchema.type === 'array' ? '] ' : ''} </span>` : ''} </td> </tr> `}`);
409
+ }
410
+
411
+ return html` <table role="presentation" style="width:100%" class="m-table"> ${formDataTableRows} </table> `;
412
+ }
413
+
414
+ return html` <textarea class="textarea dynamic-form-param ${mimeType}" part="textarea textarea-param" spellcheck="false" data-pname="dynamic-form" data-ptype="${mimeType}" style="width:100%">${exampleValue}</textarea> ${schema.description ? html`<span class="m-markdown-small">${unsafeHTML(marked(schema.description))}</span>` : ''} `;
415
+ }
416
+
417
+ apiResponseTabTemplate() {
418
+ const responseFormat = this.responseHeaders.includes('json') ? 'json' : this.responseHeaders.includes('html') || this.responseHeaders.includes('xml') ? 'html' : '';
419
+ return html` <div class="row" style="font-size:var(--font-size-small);margin:5px 0"> ${this.responseMessage ? html`<div class="response-message ${this.responseStatus}">Response Status: ${this.responseMessage} ${this.responseElapsedMs ? html`<span><br>Execution Time: ${this.responseElapsedMs}ms</span>` : ''} </div>` : ''} <div style="flex:1"></div> <button class="m-btn" part="btn btn-outline" @click="${this.clearResponseData}">CLEAR RESPONSE</button> </div> <div class="tab-panel col" style="border-width:0 0 1px 0"> <div id="tab_buttons" class="tab-buttons row" @click="${e => {
420
+ if (e.target.classList.contains('tab-btn') === false) {
421
+ return;
422
+ }
423
+
424
+ this.activeResponseTab = e.target.dataset.tab;
425
+ }}"> <button class="tab-btn ${this.activeResponseTab === 'response' ? 'active' : ''}" data-tab="response">RESPONSE</button> <button class="tab-btn ${this.activeResponseTab === 'headers' ? 'active' : ''}" data-tab="headers">RESPONSE HEADERS</button> <button class="tab-btn ${this.activeResponseTab === 'curl' ? 'active' : ''}" data-tab="curl">CURL</button> </div> ${this.responseIsBlob ? html` <div class="tab-content col" style="flex:1;display:${this.activeResponseTab === 'response' ? 'flex' : 'none'}"> <button class="m-btn thin-border mar-top-8" style="width:135px" @click="${this.downloadResponseBlob}" part="btn btn-outline">DOWNLOAD</button> ${this.responseBlobType === 'view' ? html`<button class="m-btn thin-border mar-top-8" style="width:135px" @click="${this.viewResponseBlob}" part="btn btn-outline">VIEW (NEW TAB)</button>` : ''} </div>` : html` <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'response' ? 'flex' : 'none'}"> <button class="toolbar-copy-btn" @click="${e => {
426
+ copyToClipboard(this.responseText, e);
427
+ }}" part="btn btn-fill">Copy</button> <pre>${responseFormat ? html`<code>${unsafeHTML(Prism.highlight(this.responseText, Prism.languages[responseFormat], responseFormat))}</code>` : `${this.responseText}`}
428
+ </pre> </div>`} <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'headers' ? 'flex' : 'none'}"> <button class="toolbar-copy-btn" @click="${e => {
429
+ copyToClipboard(this.responseHeaders, e);
430
+ }}" part="btn btn-fill">Copy</button> <pre><code>${unsafeHTML(Prism.highlight(this.responseHeaders, Prism.languages.css, 'css'))}</code></pre> </div> <div class="tab-content col m-markdown" style="flex:1;display:${this.activeResponseTab === 'curl' ? 'flex' : 'none'}"> <button class="toolbar-copy-btn" @click="${e => {
431
+ copyToClipboard(this.curlSyntax.replace(/\\$/, ''), e);
432
+ }}" part="btn btn-fill">Copy</button> <pre><code>${unsafeHTML(Prism.highlight(this.curlSyntax.trim().replace(/\\$/, ''), Prism.languages.shell, 'shell'))}</code></pre> </div> </div>`;
433
+ }
434
+
435
+ apiCallTemplate() {
436
+ return html` <div style="display:flex;align-items:flex-end;margin:16px 0;font-size:var(--font-size-small)"> ${this.parameters.length > 0 || this.request_body ? html` <button class="m-btn thin-border" part="btn btn-outline" style="margin-right:5px" @click="${this.onClearRequestData}"> CLEAR </button>` : ''} <button class="m-btn primary btn-execute thin-border" part="btn btn-fill btn-try" @click="${this.onTryClick}">EXECUTE</button> </div> ${this.responseMessage === '' ? '' : this.apiResponseTabTemplate()} `;
437
+ }
438
+ /* eslint-enable indent */
439
+
440
+
441
+ onClearRequestData(e) {
442
+ const requestPanelEl = e.target.closest('.request-panel');
443
+ const requestPanelInputEls = [...requestPanelEl.querySelectorAll('input, tag-input, textarea:not(.is-hidden)')];
444
+ requestPanelInputEls.forEach(el => {
445
+ el.value = '';
446
+ });
447
+ }
448
+
449
+ async onTryClick() {
450
+ const tryBtnEl = this.querySelectorAll('.btn-execute')[0];
451
+ let fetchUrl;
452
+ let curlUrl;
453
+ let curl = '';
454
+ let curlHeaders = '';
455
+ let curlData = '';
456
+ let curlForm = '';
457
+ const closestRespContainer = this.closest('.expanded-req-resp-container, .req-resp-container');
458
+ const respEl = closestRespContainer && closestRespContainer.getElementsByTagName('api-response')[0];
459
+ const acceptHeader = respEl && respEl.selectedMimeType;
460
+ const requestPanelEl = this.closest('.request-panel');
461
+ const pathParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='path']")];
462
+ const queryParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='query']")];
463
+ const queryParamObjTypeEls = [...requestPanelEl.querySelectorAll("[data-ptype='query-object']")];
464
+ const headerParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='header']")];
465
+ const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
466
+ fetchUrl = this.path;
467
+ const fetchOptions = {
468
+ method: this.method.toUpperCase(),
469
+ headers: new Headers()
470
+ }; // Generate URL using Path Params
471
+
472
+ pathParamEls.map(el => {
473
+ fetchUrl = fetchUrl.replace(`{${el.dataset.pname}}`, encodeURIComponent(el.value));
474
+ }); // Query Params
475
+
476
+ const urlQueryParam = new URLSearchParams();
477
+
478
+ if (queryParamEls.length > 0) {
479
+ queryParamEls.forEach(el => {
480
+ if (el.dataset.array === 'false') {
481
+ if (el.value !== '') {
482
+ urlQueryParam.append(el.dataset.pname, el.value);
483
+ }
484
+ } else {
485
+ const paramSerializeStyle = el.dataset.paramSerializeStyle;
486
+ const paramSerializeExplode = el.dataset.paramSerializeExplode;
487
+ let vals = el.value && Array.isArray(el.value) ? el.value : [];
488
+ vals = Array.isArray(vals) ? vals.filter(v => v !== '') : [];
489
+
490
+ if (vals.length > 0) {
491
+ if (paramSerializeStyle === 'spaceDelimited') {
492
+ urlQueryParam.append(el.dataset.pname, vals.join(' ').replace(/^\s|\s$/g, ''));
493
+ } else if (paramSerializeStyle === 'pipeDelimited') {
494
+ urlQueryParam.append(el.dataset.pname, vals.join('|').replace(/^\||\|$/g, ''));
495
+ } else {
496
+ if (paramSerializeExplode === 'true') {
497
+ // eslint-disable-line no-lonely-if
498
+ vals.forEach(v => {
499
+ urlQueryParam.append(el.dataset.pname, v);
500
+ });
501
+ } else {
502
+ urlQueryParam.append(el.dataset.pname, vals.join(',').replace(/^,|,$/g, ''));
503
+ }
504
+ }
505
+ }
506
+ }
507
+ });
508
+ } // Query Params (Dynamic - create from JSON)
509
+
510
+
511
+ if (queryParamObjTypeEls.length > 0) {
512
+ queryParamObjTypeEls.map(el => {
513
+ try {
514
+ let queryParamObj = {};
515
+ const paramSerializeStyle = el.dataset.paramSerializeStyle;
516
+ const paramSerializeExplode = el.dataset.paramSerializeExplode;
517
+ queryParamObj = Object.assign(queryParamObj, JSON.parse(el.value.replace(/\s+/g, ' ')));
518
+
519
+ for (const key in queryParamObj) {
520
+ if (typeof queryParamObj[key] === 'object') {
521
+ if (Array.isArray(queryParamObj[key])) {
522
+ if (paramSerializeStyle === 'spaceDelimited') {
523
+ urlQueryParam.append(key, queryParamObj[key].join(' '));
524
+ } else if (paramSerializeStyle === 'pipeDelimited') {
525
+ urlQueryParam.append(key, queryParamObj[key].join('|'));
526
+ } else {
527
+ if (paramSerializeExplode === 'true') {
528
+ // eslint-disable-line no-lonely-if
529
+ queryParamObj[key].forEach(v => {
530
+ urlQueryParam.append(key, v);
531
+ });
532
+ } else {
533
+ urlQueryParam.append(key, queryParamObj[key]);
534
+ }
535
+ }
536
+ }
537
+ } else {
538
+ urlQueryParam.append(key, queryParamObj[key]);
539
+ }
540
+ }
541
+ } catch (err) {
542
+ console.log('OpenAPI Explorer: unable to parse %s into object', el.value); // eslint-disable-line no-console
543
+ }
544
+ });
545
+ } // Add Authentication api keys if provided
546
+
547
+
548
+ this.api_keys.filter(v => v.finalKeyValue).forEach(v => {
549
+ if (v.in === 'query') {
550
+ urlQueryParam.append(v.name, v.finalKeyValue);
551
+ return;
552
+ } // Otherwise put it in the header
553
+
554
+
555
+ fetchOptions.headers.append(v.name, v.finalKeyValue);
556
+ curlHeaders += ` -H "${v.name}: ${v.finalKeyValue}" \\\n`;
557
+ });
558
+ fetchUrl = `${fetchUrl}${urlQueryParam.toString() ? '?' : ''}${urlQueryParam.toString()}`; // Final URL for API call
559
+
560
+ fetchUrl = `${this.serverUrl.replace(/\/$/, '')}${fetchUrl}`;
561
+
562
+ if (fetchUrl.startsWith('http') === false) {
563
+ const url = new URL(fetchUrl, window.location.href);
564
+ curlUrl = url.href;
565
+ } else {
566
+ curlUrl = fetchUrl;
567
+ }
568
+
569
+ curl = `curl -X ${this.method.toUpperCase()} "${curlUrl}" \\\n`;
570
+
571
+ if (acceptHeader) {
572
+ // Uses the acceptHeader from Response panel
573
+ fetchOptions.headers.append('Accept', acceptHeader);
574
+ curlHeaders += ` -H "Accept: ${acceptHeader}" \\\n`;
575
+ } else if (this.accept) {
576
+ fetchOptions.headers.append('Accept', this.accept);
577
+ curlHeaders += ` -H "Accept: ${this.accept}" \\\n`;
578
+ } // Add Header Params
579
+
580
+
581
+ headerParamEls.map(el => {
582
+ if (el.value) {
583
+ fetchOptions.headers.append(el.dataset.pname, el.value);
584
+ curlHeaders += ` -H "${el.dataset.pname}: ${el.value}" \\\n`;
585
+ }
586
+ }); // Request Body Params
587
+
588
+ if (requestBodyContainerEl) {
589
+ const requestBodyType = requestBodyContainerEl.dataset.selectedRequestBodyType;
590
+
591
+ if (requestBodyType.includes('form-urlencoded')) {
592
+ // url-encoded Form Params (dynamic) - Parse JSON and generate Params
593
+ const formUrlDynamicTextAreaEl = requestPanelEl.querySelector("[data-ptype='dynamic-form']");
594
+
595
+ if (formUrlDynamicTextAreaEl) {
596
+ const val = formUrlDynamicTextAreaEl.value;
597
+ const formUrlDynParams = new URLSearchParams();
598
+ let proceed = true;
599
+ let tmpObj;
600
+
601
+ if (val) {
602
+ try {
603
+ tmpObj = JSON.parse(val);
604
+ } catch (err) {
605
+ proceed = false;
606
+ console.warn('OpenAPI Explorer: Invalid JSON provided', err); // eslint-disable-line no-console
607
+ }
608
+ } else {
609
+ proceed = false;
610
+ }
611
+
612
+ if (proceed) {
613
+ for (const prop in tmpObj) {
614
+ formUrlDynParams.append(prop, JSON.stringify(tmpObj[prop]));
615
+ }
616
+
617
+ fetchOptions.body = formUrlDynParams;
618
+ curlData = ` -d ${formUrlDynParams.toString()} \\\n`;
619
+ }
620
+ } else {
621
+ // url-encoded Form Params (regular)
622
+ const formUrlEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-urlencode']")];
623
+ const formUrlParams = new URLSearchParams();
624
+ formUrlEls.filter(v => v.type !== 'file').forEach(el => {
625
+ if (el.dataset.array === 'false') {
626
+ if (el.value) {
627
+ formUrlParams.append(el.dataset.pname, el.value);
628
+ }
629
+ } else {
630
+ const vals = el.value && Array.isArray(el.value) ? el.value.join(',') : '';
631
+ formUrlParams.append(el.dataset.pname, vals);
632
+ }
633
+ });
634
+ fetchOptions.body = formUrlParams;
635
+ curlData = ` -d ${formUrlParams.toString()} \\\n`;
636
+ }
637
+ } else if (requestBodyType.includes('form-data')) {
638
+ const formDataParams = new FormData();
639
+ const formDataEls = [...requestPanelEl.querySelectorAll("[data-ptype='form-data']")];
640
+ formDataEls.forEach(el => {
641
+ if (el.dataset.array === 'false') {
642
+ if (el.type === 'file' && el.files[0]) {
643
+ formDataParams.append(el.dataset.pname, el.files[0], el.files[0].name);
644
+ curlForm += ` -F "${el.dataset.pname}=@${el.files[0].name}" \\\n`;
645
+ } else if (el.value) {
646
+ formDataParams.append(el.dataset.pname, el.value);
647
+ curlForm += ` -F "${el.dataset.pname}=${el.value}" \\\n`;
648
+ }
649
+ } else if (el.value && Array.isArray(el.value)) {
650
+ el.value.forEach(v => {
651
+ curlForm = `${curlForm} -F "${el.dataset.pname}[]=${v}" \\\n`;
652
+ });
653
+ formDataParams.append(el.dataset.pname, el.value.join(','));
654
+ }
655
+ });
656
+ fetchOptions.body = formDataParams;
657
+ } else if (mediaFileRegex.test(requestBodyType) || textFileRegex.test(requestBodyType)) {
658
+ const bodyParamFileEl = requestPanelEl.querySelector('.request-body-param-file');
659
+
660
+ if (bodyParamFileEl && bodyParamFileEl.files[0]) {
661
+ fetchOptions.body = bodyParamFileEl.files[0];
662
+ curlData = ` --data-binary @${bodyParamFileEl.files[0].name} \\\n`;
663
+ }
664
+ } else if (requestBodyType.includes('json') || requestBodyType.includes('xml') || requestBodyType.includes('text')) {
665
+ const exampleTextAreaEl = requestPanelEl.querySelector('.request-body-param-user-input');
666
+
667
+ if (exampleTextAreaEl && exampleTextAreaEl.value) {
668
+ fetchOptions.body = exampleTextAreaEl.value;
669
+
670
+ if (requestBodyType.includes('json')) {
671
+ try {
672
+ curlData = ` -d '${JSON.stringify(JSON.parse(exampleTextAreaEl.value))}' \\\n`;
673
+ } catch (err) {
674
+ /* Ignore unparseable JSON */
675
+ }
676
+ }
677
+
678
+ if (!curlData) {
679
+ // Save single quotes wrapped => 'text' => `"'"text"'"`
680
+ curlData = ` -d '${exampleTextAreaEl.value.replace(/'/g, '\'"\'"\'')}' \\\n`;
681
+ }
682
+ }
683
+ } // Common for all request-body
684
+
685
+
686
+ if (!requestBodyType.includes('form-data')) {
687
+ // For multipart/form-data don't set the content-type to allow creation of browser generated part boundaries
688
+ fetchOptions.headers.append('Content-Type', requestBodyType);
689
+ }
690
+
691
+ curlHeaders += ` -H "Content-Type: ${requestBodyType}" \\\n`;
692
+ }
693
+
694
+ this.curlSyntax = '';
695
+ this.responseIsBlob = false;
696
+ this.respContentDisposition = '';
697
+
698
+ if (this.responseBlobUrl) {
699
+ URL.revokeObjectURL(this.responseBlobUrl);
700
+ this.responseBlobUrl = '';
701
+ }
702
+
703
+ this.curlSyntax = `${curl}${curlHeaders}${curlData}${curlForm}`;
704
+
705
+ if (this.fetchCredentials) {
706
+ fetchOptions.credentials = this.fetchCredentials;
707
+ }
708
+
709
+ const fetchRequest = {
710
+ url: fetchUrl,
711
+ options: fetchOptions
712
+ };
713
+ const event = {
714
+ bubbles: true,
715
+ composed: true,
716
+ detail: {
717
+ request: fetchRequest
718
+ }
719
+ };
720
+ this.dispatchEvent(new CustomEvent('before-try', event));
721
+ this.dispatchEvent(new CustomEvent('request', event));
722
+ const fetchRequestObject = new Request(fetchUrl, fetchOptions);
723
+ let fetchResponse;
724
+
725
+ try {
726
+ let respBlob;
727
+ let respJson;
728
+ let respText;
729
+ tryBtnEl.disabled = true;
730
+ const fetchStart = new Date();
731
+ this.responseStatus = '';
732
+ this.responseMessage = '';
733
+ this.responseUrl = '';
734
+ this.responseHeaders = '';
735
+ this.responseText = '⌛';
736
+ this.requestUpdate();
737
+ const awaiter = new Promise(resolve => setTimeout(resolve, 200));
738
+ fetchResponse = await fetch(fetchRequestObject);
739
+ this.responseElapsedMs = new Date() - fetchStart;
740
+ await awaiter;
741
+ tryBtnEl.disabled = false;
742
+ this.responseStatus = fetchResponse.ok ? 'success' : 'error';
743
+ this.responseMessage = fetchResponse.statusText ? `${fetchResponse.statusText} (${fetchResponse.status})` : fetchResponse.status;
744
+ this.responseUrl = fetchResponse.url;
745
+ this.responseHeaders = '';
746
+ const headers = {};
747
+ fetchResponse.headers.forEach((hdrVal, hdr) => {
748
+ this.responseHeaders = `${this.responseHeaders}${hdr.trim()}: ${hdrVal}\n`;
749
+ headers[hdr.trim()] = hdrVal && hdrVal.trim();
750
+ });
751
+ const contentType = fetchResponse.headers.get('content-type');
752
+ const respEmpty = (await fetchResponse.clone().text()).length === 0;
753
+
754
+ if (respEmpty) {
755
+ this.responseText = '';
756
+ } else if (contentType) {
757
+ if (contentType.includes('json')) {
758
+ if (/charset=[^"']+/.test(contentType)) {
759
+ const encoding = contentType.split('charset=')[1];
760
+ const buffer = await fetchResponse.arrayBuffer();
761
+
762
+ try {
763
+ respText = new TextDecoder(encoding).decode(buffer);
764
+ } catch {
765
+ respText = new TextDecoder('utf-8').decode(buffer);
766
+ }
767
+
768
+ try {
769
+ this.responseText = JSON.stringify(JSON.parse(respText), null, 8);
770
+ } catch {
771
+ this.responseText = respText;
772
+ }
773
+ } else {
774
+ respJson = await fetchResponse.json();
775
+ this.responseText = JSON.stringify(respJson, null, 8);
776
+ }
777
+ } else if (textFileRegex.test(contentType)) {
778
+ this.responseIsBlob = true;
779
+ this.responseBlobType = 'download';
780
+ } else if (mediaFileRegex.test(contentType)) {
781
+ this.responseIsBlob = true;
782
+ this.responseBlobType = 'view';
783
+ } else {
784
+ respText = await fetchResponse.text();
785
+
786
+ if (contentType.includes('xml')) {
787
+ this.responseText = formatXml(respText, {
788
+ textNodesOnSameLine: true,
789
+ indentor: ' '
790
+ });
791
+ } else {
792
+ this.responseText = respText;
793
+ }
794
+ }
795
+
796
+ if (this.responseIsBlob) {
797
+ const contentDisposition = fetchResponse.headers.get('content-disposition');
798
+ const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
799
+ const filename = filenameRegex.exec(contentDisposition);
800
+ this.respContentDisposition = filename && filename[1] && filename[1].replace(/['"]/g, '') || `download.${mimeTypeResolver.extension(contentType) || 'file'}`;
801
+ respBlob = await fetchResponse.blob();
802
+ this.responseBlobUrl = URL.createObjectURL(respBlob);
803
+ }
804
+ } else {
805
+ respText = await fetchResponse.text();
806
+ this.responseText = respText;
807
+ }
808
+
809
+ const responseEvent = {
810
+ bubbles: true,
811
+ composed: true,
812
+ detail: {
813
+ request: fetchRequest,
814
+ response: {
815
+ headers,
816
+ body: respJson || respText || respBlob || fetchResponse.body,
817
+ status: fetchResponse.status
818
+ }
819
+ }
820
+ };
821
+ this.dispatchEvent(new CustomEvent('after-try', responseEvent));
822
+ this.dispatchEvent(new CustomEvent('response', responseEvent));
823
+ } catch (error) {
824
+ tryBtnEl.disabled = false;
825
+ this.responseMessage = `${error.message} (Check the browser network tab for more information.)`;
826
+ this.responseStatus = 'error';
827
+ const responseEvent = {
828
+ bubbles: true,
829
+ composed: true,
830
+ detail: {
831
+ error,
832
+ request: fetchRequest
833
+ }
834
+ };
835
+ document.dispatchEvent(new CustomEvent('after-try', responseEvent));
836
+ document.dispatchEvent(new CustomEvent('response', responseEvent));
837
+ }
838
+ }
839
+
840
+ onAddRemoveFileInput(e, pname, ptype) {
841
+ if (e.target.tagName.toLowerCase() !== 'button') {
842
+ return;
843
+ }
844
+
845
+ if (e.target.classList.contains('file-input-remove-btn')) {
846
+ // Remove File Input Set
847
+ const el = e.target.closest('.input-set');
848
+ el.remove();
849
+ return;
850
+ }
851
+
852
+ const el = e.target.closest('.file-input-container'); // Add File Input Set
853
+ // Container
854
+
855
+ const newInputContainerEl = document.createElement('div');
856
+ newInputContainerEl.setAttribute('class', 'input-set row'); // File Input
857
+
858
+ const newInputEl = document.createElement('input');
859
+ newInputEl.type = 'file';
860
+ newInputEl.style = 'width:200px; margin-top:2px;';
861
+ newInputEl.setAttribute('data-pname', pname);
862
+ newInputEl.setAttribute('data-ptype', ptype.includes('form-urlencode') ? 'form-urlencode' : 'form-data');
863
+ newInputEl.setAttribute('data-array', 'false');
864
+ newInputEl.setAttribute('data-file-array', 'true'); // Remover Button
865
+
866
+ const newRemoveBtnEl = document.createElement('button');
867
+ newRemoveBtnEl.setAttribute('class', 'file-input-remove-btn');
868
+ newRemoveBtnEl.innerHTML = '&#x2715;';
869
+ newInputContainerEl.appendChild(newInputEl);
870
+ newInputContainerEl.appendChild(newRemoveBtnEl);
871
+ el.insertBefore(newInputContainerEl, e.target); // el.appendChild(newInputContainerEl);
872
+ }
873
+
874
+ downloadResponseBlob() {
875
+ if (this.responseBlobUrl) {
876
+ const a = document.createElement('a');
877
+ document.body.appendChild(a);
878
+ a.style = 'display: none';
879
+ a.href = this.responseBlobUrl;
880
+ a.download = this.respContentDisposition;
881
+ a.click();
882
+ a.remove();
883
+ }
884
+ }
885
+
886
+ viewResponseBlob() {
887
+ if (this.responseBlobUrl) {
888
+ const a = document.createElement('a');
889
+ document.body.appendChild(a);
890
+ a.style = 'display: none';
891
+ a.href = this.responseBlobUrl;
892
+ a.target = '_blank';
893
+ a.click();
894
+ a.remove();
895
+ }
896
+ }
897
+
898
+ clearResponseData() {
899
+ this.responseUrl = '';
900
+ this.responseHeaders = '';
901
+ this.responseText = '';
902
+ this.responseStatus = '';
903
+ this.responseMessage = '';
904
+ this.responseElapsedMs = 0;
905
+ this.responseIsBlob = false;
906
+ this.responseBlobType = '';
907
+ this.respContentDisposition = '';
908
+
909
+ if (this.responseBlobUrl) {
910
+ URL.revokeObjectURL(this.responseBlobUrl);
911
+ this.responseBlobUrl = '';
912
+ }
913
+ }
914
+
915
+ requestParamFunction(event) {
916
+ if (event.key === 'Enter') {
917
+ this.onTryClick();
918
+ event.preventDefault();
919
+ }
920
+ }
921
+
922
+ disconnectedCallback() {
923
+ // Cleanup ObjectURL forthe blob data if this component created one
924
+ if (this.responseBlobUrl) {
925
+ URL.revokeObjectURL(this.responseBlobUrl);
926
+ this.responseBlobUrl = '';
927
+ }
928
+
929
+ super.disconnectedCallback();
930
+ }
931
+
932
+ } // Register the element with the browser
933
+
934
+ if (!customElements.get('openapi-explorer')) {
935
+ customElements.define('api-request', ApiRequest);
936
+ }