async function loadSpec() { const response = await fetch('/openapi.json'); const spec = await response.json(); if (!response.ok) { throw new Error(spec.error || 'Failed to load OpenAPI spec'); } return spec; } function methodPayload(operation) { const body = operation.requestBody?.content?.['application/json']?.schema; if (!body) return ''; return JSON.stringify(body.example || body.properties || body, null, 2); } function renderSpec(spec) { const root = document.getElementById('api-docs'); const sectionTemplate = document.getElementById('api-section-template'); const methodTemplate = document.getElementById('api-method-template'); root.innerHTML = ''; const paths = Object.entries(spec.paths || {}); if (!paths.length) { const section = document.createElement('section'); section.className = 'panel'; const header = document.createElement('div'); header.className = 'panel-header'; const title = document.createElement('h2'); title.textContent = 'No endpoints'; header.appendChild(title); const body = document.createElement('div'); body.className = 'panel-body'; const empty = document.createElement('div'); empty.className = 'empty-state'; const text = document.createElement('p'); text.className = 'muted'; text.textContent = 'The OpenAPI spec contains no paths.'; empty.appendChild(text); body.appendChild(empty); section.append(header, body); root.appendChild(section); return; } paths.forEach(([path, methods]) => { const section = sectionTemplate.content.firstElementChild.cloneNode(true); section.querySelector('.api-path').textContent = path; const methodsRoot = section.querySelector('.api-methods'); Object.entries(methods).forEach(([method, operation]) => { const node = methodTemplate.content.firstElementChild.cloneNode(true); node.querySelector('.api-verb').textContent = method.toUpperCase(); node.querySelector('.api-summary').textContent = operation.summary || ''; node.querySelector('.api-description').textContent = operation.description || ''; const body = methodPayload(operation); const bodyNode = node.querySelector('.api-body'); if (body) { bodyNode.textContent = body; } else { bodyNode.remove(); } methodsRoot.appendChild(node); }); root.appendChild(section); }); } loadSpec() .then(renderSpec) .catch((error) => { console.error(error); const docsEl = document.getElementById('api-docs'); docsEl.textContent = ''; const section = document.createElement('section'); section.className = 'panel'; const header = document.createElement('div'); header.className = 'panel-header'; const title = document.createElement('h2'); title.textContent = 'Failed to load API spec'; header.appendChild(title); const body = document.createElement('div'); body.className = 'panel-body'; const empty = document.createElement('div'); empty.className = 'empty-state'; const text = document.createElement('p'); text.className = 'muted'; text.textContent = error.message; const actions = document.createElement('div'); actions.className = 'empty-actions'; const retry = document.createElement('button'); retry.className = 'button'; retry.type = 'button'; retry.textContent = 'Retry'; retry.addEventListener('click', () => window.location.reload()); actions.appendChild(retry); empty.append(text, actions); body.appendChild(empty); section.append(header, body); docsEl.appendChild(section); });