@publier/openapi 0.3.83 → 0.4.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/dist/generate.d.mts +1 -0
- package/dist/index.d.mts +3 -0
- package/dist/parser.d.mts +1 -0
- package/dist/server.d.mts +1 -0
- package/package.json +2 -2
- package/src/components/api-playground-ce-element.ts +94 -52
- package/src/components/api-playground.astro +2 -2
- package/src/components/props.ts +2 -0
package/dist/generate.d.mts
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -11,6 +11,7 @@ interface OpenApiSpec {
|
|
|
11
11
|
*/
|
|
12
12
|
webhooks?: Record<string, OpenApiPathItem>;
|
|
13
13
|
components?: OpenApiComponents;
|
|
14
|
+
security?: Record<string, string[]>[];
|
|
14
15
|
tags?: OpenApiTag[];
|
|
15
16
|
}
|
|
16
17
|
interface OpenApiInfo {
|
|
@@ -241,6 +242,8 @@ interface MethodBadgeProps {
|
|
|
241
242
|
method: HttpMethod;
|
|
242
243
|
}
|
|
243
244
|
interface APIPlaygroundProps {
|
|
245
|
+
/** Full parsed specification supplies inherited security and server defaults. */
|
|
246
|
+
spec?: OpenApiSpec;
|
|
244
247
|
method: HttpMethod;
|
|
245
248
|
path: string;
|
|
246
249
|
operation: OpenApiOperation;
|
package/dist/parser.d.mts
CHANGED
package/dist/server.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@publier/openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@publier/native": "
|
|
48
|
+
"@publier/native": "8.0.0",
|
|
49
49
|
"@readme/httpsnippet": "^11.1.0",
|
|
50
50
|
"openapi-sampler": "^1.7.3",
|
|
51
51
|
"yaml": "^2.9.0"
|
|
@@ -1,46 +1,34 @@
|
|
|
1
1
|
export class PublierApiPlayground extends HTMLElement {
|
|
2
|
+
#listeners?: AbortController;
|
|
3
|
+
|
|
2
4
|
connectedCallback() {
|
|
3
|
-
this
|
|
5
|
+
this.#listeners?.abort();
|
|
6
|
+
this.#listeners = new AbortController();
|
|
7
|
+
const form = this.querySelector('form');
|
|
8
|
+
form?.addEventListener('submit', this.#send, { signal: this.#listeners.signal });
|
|
9
|
+
form?.addEventListener('change', this.#selectSecurity, { signal: this.#listeners.signal });
|
|
10
|
+
this.#selectSecurity();
|
|
4
11
|
}
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const data = new FormData(form);
|
|
10
|
-
|
|
11
|
-
const method = (form.dataset.method ?? 'get').toUpperCase();
|
|
12
|
-
let path = form.dataset.path ?? '/';
|
|
13
|
-
|
|
14
|
-
for (const [key, val] of data.entries()) {
|
|
15
|
-
if (key.startsWith('path:')) {
|
|
16
|
-
const paramName = key.slice(5);
|
|
17
|
-
path = path.replace(`{${paramName}}`, encodeURIComponent((val as string) || paramName));
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const qs = new URLSearchParams();
|
|
22
|
-
for (const [key, val] of data.entries()) {
|
|
23
|
-
if (key.startsWith('query:') && val) qs.set(key.slice(6), val as string);
|
|
24
|
-
}
|
|
25
|
-
const server = (data.get('server') as string) ?? '';
|
|
26
|
-
const fullUrl = `${server}${path}${qs.size > 0 ? `?${qs}` : ''}`;
|
|
13
|
+
disconnectedCallback() {
|
|
14
|
+
this.#listeners?.abort();
|
|
15
|
+
}
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for (const [key, val] of data.entries()) {
|
|
34
|
-
if (key.startsWith('header:') && val) headers[key.slice(7)] = val as string;
|
|
17
|
+
#selectSecurity = () => {
|
|
18
|
+
const selected = this.querySelector<HTMLSelectElement>('[name="security"]')?.value ?? '0';
|
|
19
|
+
for (const group of this.querySelectorAll<HTMLFieldSetElement>('[data-pg-auth-group]')) {
|
|
20
|
+
group.hidden = group.dataset.pgAuthGroup !== selected;
|
|
21
|
+
group.disabled = group.hidden;
|
|
35
22
|
}
|
|
23
|
+
};
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
#send = async (e: SubmitEvent) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const form = e.target as HTMLFormElement;
|
|
40
28
|
const sendBtn = this.querySelector<HTMLButtonElement>('[data-pg-send]');
|
|
41
29
|
const responseEl = this.querySelector('[data-pg-response]');
|
|
42
30
|
const errorEl = this.querySelector('[data-pg-error]');
|
|
43
|
-
|
|
31
|
+
if (sendBtn?.disabled) return;
|
|
44
32
|
if (sendBtn) {
|
|
45
33
|
sendBtn.disabled = true;
|
|
46
34
|
sendBtn.textContent = 'Sending…';
|
|
@@ -49,26 +37,84 @@ export class PublierApiPlayground extends HTMLElement {
|
|
|
49
37
|
if (responseEl) responseEl.innerHTML = '';
|
|
50
38
|
|
|
51
39
|
try {
|
|
52
|
-
const
|
|
40
|
+
const data = new FormData(form);
|
|
41
|
+
const method = (form.dataset.method ?? 'get').toUpperCase();
|
|
42
|
+
let path = form.dataset.path ?? '/';
|
|
43
|
+
for (const [key, val] of data) {
|
|
44
|
+
if (key.startsWith('path:')) {
|
|
45
|
+
path = path.replaceAll(`{${key.slice(5)}}`, encodeURIComponent(String(val)));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const server = String(data.get('server') ?? '').replace(/\/$/, '');
|
|
49
|
+
const url = new URL(`${server}${path}`, document.baseURI);
|
|
50
|
+
if (!['https:', 'http:'].includes(url.protocol))
|
|
51
|
+
throw new Error('Use an HTTP or HTTPS server.');
|
|
52
|
+
const headers = new Headers();
|
|
53
|
+
for (const [key, val] of data) {
|
|
54
|
+
if (key.startsWith('query:') && val) url.searchParams.set(key.slice(6), String(val));
|
|
55
|
+
if (key.startsWith('header:') && val) headers.set(key.slice(7), String(val));
|
|
56
|
+
}
|
|
57
|
+
let credentials: RequestCredentials = 'same-origin';
|
|
58
|
+
const unsupported = form.querySelector(
|
|
59
|
+
'[data-pg-auth-group]:not([hidden]) [data-pg-unsupported]',
|
|
60
|
+
);
|
|
61
|
+
if (unsupported) throw new Error(unsupported.textContent ?? 'Unsupported authentication.');
|
|
62
|
+
for (const input of form.querySelectorAll<HTMLInputElement>('[data-pg-auth-kind]')) {
|
|
63
|
+
const value = data.get(input.name);
|
|
64
|
+
if (!value) continue;
|
|
65
|
+
const kind = input.dataset.pgAuthKind;
|
|
66
|
+
if (kind === 'apiKey') {
|
|
67
|
+
const name = input.dataset.pgAuthName ?? '';
|
|
68
|
+
if (input.dataset.pgAuthIn === 'query') url.searchParams.set(name, String(value));
|
|
69
|
+
else headers.set(name, String(value));
|
|
70
|
+
} else if (kind === 'bearer') {
|
|
71
|
+
headers.set('Authorization', `Bearer ${value}`);
|
|
72
|
+
} else if (kind === 'basic') {
|
|
73
|
+
const bytes = new TextEncoder().encode(String(value));
|
|
74
|
+
headers.set(
|
|
75
|
+
'Authorization',
|
|
76
|
+
`Basic ${btoa(Array.from(bytes, (b) => String.fromCharCode(b)).join(''))}`,
|
|
77
|
+
);
|
|
78
|
+
} else if (kind === 'cookie') credentials = 'include';
|
|
79
|
+
}
|
|
80
|
+
let body: BodyInit | undefined;
|
|
81
|
+
const rawBody = String(data.get('body') ?? '');
|
|
82
|
+
if (!['GET', 'HEAD'].includes(method) && rawBody) {
|
|
83
|
+
const media = String(data.get('content-type') ?? 'application/json');
|
|
84
|
+
if (media === 'multipart/form-data' || media === 'application/x-www-form-urlencoded') {
|
|
85
|
+
const fields: unknown = JSON.parse(rawBody);
|
|
86
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields)) {
|
|
87
|
+
throw new Error('Form bodies must be a JSON object of scalar fields.');
|
|
88
|
+
}
|
|
89
|
+
const encoded = media === 'multipart/form-data' ? new FormData() : new URLSearchParams();
|
|
90
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
91
|
+
if (value !== null && typeof value === 'object')
|
|
92
|
+
throw new Error('Form fields must be scalar values.');
|
|
93
|
+
encoded.append(key, value === null ? '' : String(value));
|
|
94
|
+
}
|
|
95
|
+
body = encoded;
|
|
96
|
+
|
|
97
|
+
if (media === 'multipart/form-data') headers.delete('Content-Type');
|
|
98
|
+
else headers.set('Content-Type', media);
|
|
99
|
+
} else {
|
|
100
|
+
body = rawBody;
|
|
101
|
+
headers.set('Content-Type', media);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const res = await fetch(url.href, { method, headers, body, credentials });
|
|
53
105
|
const bodyText = await res.text();
|
|
54
|
-
|
|
55
106
|
let formatted = bodyText;
|
|
56
107
|
try {
|
|
57
108
|
formatted = JSON.stringify(JSON.parse(bodyText), null, 2);
|
|
58
109
|
} catch {}
|
|
59
|
-
|
|
60
|
-
const isSuccess = res.status >= 200 && res.status < 300;
|
|
61
|
-
const statusColor = isSuccess ? 'var(--color-green-400)' : 'var(--color-red-400)';
|
|
62
|
-
|
|
63
110
|
if (responseEl) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`;
|
|
111
|
+
const status = document.createElement('div');
|
|
112
|
+
status.className = 'px-4 py-3 font-mono text-sm';
|
|
113
|
+
status.textContent = `${res.status} ${res.statusText}`;
|
|
114
|
+
const pre = document.createElement('pre');
|
|
115
|
+
pre.className = 'px-4 py-3 overflow-auto font-mono text-sm';
|
|
116
|
+
pre.textContent = formatted;
|
|
117
|
+
responseEl.replaceChildren(status, pre);
|
|
72
118
|
}
|
|
73
119
|
} catch (err) {
|
|
74
120
|
if (errorEl) errorEl.textContent = err instanceof Error ? err.message : 'Request failed';
|
|
@@ -78,11 +124,7 @@ export class PublierApiPlayground extends HTMLElement {
|
|
|
78
124
|
sendBtn.textContent = 'Send Request';
|
|
79
125
|
}
|
|
80
126
|
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#escHtml(str: string): string {
|
|
84
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
85
|
-
}
|
|
127
|
+
};
|
|
86
128
|
}
|
|
87
129
|
|
|
88
130
|
if (typeof customElements !== 'undefined' && !customElements.get('publier-api-playground')) {
|
|
@@ -3,8 +3,8 @@ import { openapiRenderApiPlayground } from '@publier/native';
|
|
|
3
3
|
import type { APIPlaygroundProps } from './props.ts';
|
|
4
4
|
type Props = APIPlaygroundProps;
|
|
5
5
|
|
|
6
|
-
const { method, path, operation, servers = [] } = Astro.props;
|
|
7
|
-
const html = openapiRenderApiPlayground(JSON.stringify({ method, path, operation, servers }));
|
|
6
|
+
const { method, path, operation, spec, servers = spec?.servers ?? [] } = Astro.props;
|
|
7
|
+
const html = openapiRenderApiPlayground(JSON.stringify({ method, path, operation, servers, spec }));
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
<Fragment set:html={html} />
|