*/
const inline = (s: string) => esc(s).replace(/`([^`]+)`/g, '$1')
/** 概述条目:`代码`、**加粗**,$BASE 替换为服务地址 */
const marked = (s: string, base: string) => inline(s.replaceAll('$BASE', base)).replace(/\*\*([^*]+)\*\*/g, '$1')
/** "409 session_busy:说明" → 状态码加粗、错误码为代码样式 */
const errorItem = (x: string) => {
const m = /^(\d{3}) ([a-z_]+)(.*)$/.exec(x)
return m ? `${m[1]} ${m[2]}${inline(m[3]!)} ` : `${inline(x)} `
}
const anchor = (e: Endpoint) => `${e.method}-${e.path}`.toLowerCase().replace(/[^a-z0-9]+/g, '-')
const slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-')
function fieldTable(fields: Field[], withRequired = true) {
return `字段 类型 ${withRequired ? '必填 ' : ''}说明 ${
fields.map(f => `${esc(f.name)}${esc(f.type)} ${withRequired ? `${f.required ? '是' : ''} ` : ''}${inline(f.desc)} `).join('')
}
`
}
function endpoint(e: Endpoint, base: string) {
return `
${e.method}${esc(e.path)}${esc(e.summary)}
${e.desc ? `${inline(e.desc)}
` : ''}
${e.query?.length ? `查询参数
${fieldTable(e.query)}` : ''}
${e.body?.length ? `请求体(JSON)
${fieldTable(e.body)}` : ''}
${e.bodyNote ? `请求体
${inline(e.bodyNote)}
` : ''}
响应
${e.response.status} ${esc(e.response.type)}${e.response.desc ? ` — ${inline(e.response.desc)}` : ''}
${e.errors?.length ? `常见错误
${e.errors.map(errorItem).join('')}
` : ''}
${e.example ? `示例
${esc(e.example.replaceAll('$BASE', base))}` : ''}
`
}
/** 一部分的章节:概述、各接口分组、事件类型、数据结构、错误码(编号为"部分.节") */
function sections(spec: ApiSpec) {
return [
['overview', '概述'],
...spec.groups.map(g => [g.id, g.title]),
...(spec.events.length ? [['event-types', '事件类型']] : []),
['types', '数据结构'],
['errors', '错误码'],
].map(([id, title], k) => ({ id: `${spec.id}-${id}`, title: title!, no: k + 1 }))
}
function specHtml(spec: ApiSpec, part: number, base: string) {
const sec = sections(spec)
const no = (id: string) => `${part}.${sec.find(x => x.id === `${spec.id}-${id}`)!.no}`
return `
第 ${part} 部分 · ${esc(spec.title)}
${inline(spec.summary)}
${no('overview')} 概述
${spec.overview.points.map(x => `- ${marked(x, base)}
`).join('')}
典型流程
${spec.overview.flow.map(x => `- ${marked(x, base)}
`).join('')}
${spec.groups.map(g => `${no(g.id)} ${esc(g.title)}
${g.desc ? `${inline(g.desc)}
` : ''}
${g.endpoints.map(e => endpoint(e, base)).join('\n')}`).join('\n')}
${spec.events.length ? `${no('event-types')} 事件类型
事件信封见"数据结构 › ${esc(spec.eventEnvelope)}"。"落库"为否的事件只在实时事件流中推送,不出现在历史事件中。
type payload 说明 落库
${spec.events.map(e => `${esc(e.type)}${esc(e.payload)}${inline(e.desc)} ${e.persisted ? '是' : '否'} `).join('\n')}
` : ''}
${no('types')} 数据结构
${spec.types.map(t => `${esc(t.name)}
${t.desc ? `${inline(t.desc)}
` : ''}${fieldTable(t.fields, false)}`).join('\n')}
${no('errors')} 错误码
HTTP code 说明
${spec.errors.map(e => `${e.status} ${esc(e.code)}${inline(e.desc)} `).join('\n')}
`
}
export function renderApiReferenceHtml(o: RenderOptions): string {
const base = o.baseUrl.replace(/\/+$/, '')
const date = o.generatedAt.toISOString().slice(0, 10)
return `
AgentServer API 协议 ${esc(o.version)}
AgentServer 对外 API 协议
目录
${API_SPECS.map((spec, p) => `第 ${p + 1} 部分 · ${esc(spec.title)}
${sections(spec).map(x => `- ${esc(x.title)}
`).join('')}
`).join('\n')}
${API_SPECS.map((spec, p) => specHtml(spec, p + 1, base)).join('\n')}
`
}