feat(api): add Hugo-native API rendering POC
Add Hugo-native rendering as alternative to RapiDoc for API documentation. Renders operations, parameters, request bodies, schemas, and responses using Hugo templates styled after docusaurus-openapi aesthetic. New partials: - tag-renderer.html: Main renderer that loads OpenAPI spec - operation.html: Operation block with header and sections - parameters.html: Groups query/path/header parameters - parameter-row.html: Individual parameter rendering - request-body.html: Request body with schema - schema.html: Recursive JSON schema rendering - responses.html: Response status codes with schemas Features: - Parses OpenAPI YAML at build time (no JS required) - Resolves $ref references for parameters and schemas - Displays JSON examples from spec - Color-coded status badges (2xx green, 4xx red) - x-influxdata-related links rendered at page bottom - Dark mode support - Responsive design Enable with `useHugoNative: true` in page frontmatter.claude/api-code-samples-plan-MEkQO
parent
17301e0833
commit
4959d93c26
|
|
@ -0,0 +1,598 @@
|
|||
// Hugo-Native API Documentation Styles
|
||||
// Styled after docusaurus-openapi aesthetic with clean, readable layouts
|
||||
|
||||
// Variables
|
||||
$api-border-radius: 6px;
|
||||
$api-spacing-sm: 0.5rem;
|
||||
$api-spacing-md: 1rem;
|
||||
$api-spacing-lg: 1.5rem;
|
||||
$api-spacing-xl: 2rem;
|
||||
|
||||
// Method colors (matching existing theme)
|
||||
$method-get: #00A3FF;
|
||||
$method-post: #34BB55;
|
||||
$method-put: #FFB94A;
|
||||
$method-delete: #F95F53;
|
||||
$method-patch: #9b2aff;
|
||||
|
||||
// Status code colors
|
||||
$status-success: #34BB55;
|
||||
$status-redirect: #FFB94A;
|
||||
$status-client-error: #F95F53;
|
||||
$status-server-error: #9b2aff;
|
||||
|
||||
// ============================================
|
||||
// Operation Block
|
||||
// ============================================
|
||||
|
||||
.api-hugo-native {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.api-operation {
|
||||
margin-bottom: $api-spacing-xl;
|
||||
padding: $api-spacing-lg;
|
||||
background: var(--article-bg, #fff);
|
||||
border: 1px solid var(--article-border, #e8e8f0);
|
||||
border-radius: $api-border-radius;
|
||||
|
||||
&:target {
|
||||
animation: highlight-operation 1.5s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes highlight-operation {
|
||||
0% {
|
||||
box-shadow: 0 0 0 4px rgba($method-get, 0.3);
|
||||
}
|
||||
100% {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Operation Header
|
||||
.api-operation-header {
|
||||
margin-bottom: $api-spacing-md;
|
||||
}
|
||||
|
||||
.api-operation-endpoint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $api-spacing-sm;
|
||||
margin-bottom: $api-spacing-sm;
|
||||
}
|
||||
|
||||
.api-method {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
|
||||
&--get { background-color: $method-get; }
|
||||
&--post { background-color: $method-post; }
|
||||
&--put { background-color: $method-put; }
|
||||
&--delete { background-color: $method-delete; }
|
||||
&--patch { background-color: $method-patch; }
|
||||
}
|
||||
|
||||
.api-path {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
font-size: 0.95rem;
|
||||
color: var(--article-text, #2b2b2b);
|
||||
background: var(--code-bg, #f5f5f5);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.api-operation-summary {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--article-heading, #2b2b2b);
|
||||
}
|
||||
|
||||
.api-operation-description {
|
||||
margin: $api-spacing-md 0;
|
||||
color: var(--article-text, #545667);
|
||||
line-height: 1.6;
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Section Titles
|
||||
// ============================================
|
||||
|
||||
.api-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $api-spacing-sm;
|
||||
margin: $api-spacing-lg 0 $api-spacing-md;
|
||||
padding-bottom: $api-spacing-sm;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--article-heading, #2b2b2b);
|
||||
border-bottom: 1px solid var(--article-border, #e8e8f0);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Parameters Section
|
||||
// ============================================
|
||||
|
||||
.api-parameters {
|
||||
margin: $api-spacing-lg 0;
|
||||
}
|
||||
|
||||
.api-param-group {
|
||||
margin-bottom: $api-spacing-md;
|
||||
}
|
||||
|
||||
.api-param-group-title {
|
||||
margin: $api-spacing-sm 0;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--article-text-muted, #777);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.api-param-list {
|
||||
border: 1px solid var(--article-border, #e8e8f0);
|
||||
border-radius: $api-border-radius;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-param-row {
|
||||
padding: $api-spacing-md;
|
||||
border-bottom: 1px solid var(--article-border, #e8e8f0);
|
||||
background: var(--article-bg, #fff);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--article-bg-hover, #f9f9fb);
|
||||
}
|
||||
|
||||
&--required {
|
||||
background: rgba($method-post, 0.03);
|
||||
|
||||
&:hover {
|
||||
background: rgba($method-post, 0.06);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-param-name-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $api-spacing-sm;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.api-param-name {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--article-heading, #2b2b2b);
|
||||
}
|
||||
|
||||
.api-param-type {
|
||||
font-size: 0.8rem;
|
||||
color: var(--article-text-muted, #777);
|
||||
}
|
||||
|
||||
.api-param-description {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--article-text, #545667);
|
||||
line-height: 1.5;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.api-param-enum,
|
||||
.api-param-default {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.api-param-enum-label,
|
||||
.api-param-default-label {
|
||||
color: var(--article-text-muted, #777);
|
||||
}
|
||||
|
||||
.api-param-enum-value,
|
||||
.api-param-default-value {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
font-size: 0.8rem;
|
||||
background: var(--code-bg, #f5f5f5);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Badges
|
||||
// ============================================
|
||||
|
||||
.api-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.125rem 0.375rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
border-radius: 3px;
|
||||
|
||||
&--required {
|
||||
background: rgba($method-delete, 0.1);
|
||||
color: $method-delete;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Request Body Section
|
||||
// ============================================
|
||||
|
||||
.api-request-body {
|
||||
margin: $api-spacing-lg 0;
|
||||
}
|
||||
|
||||
.api-request-body-description {
|
||||
margin: $api-spacing-sm 0;
|
||||
color: var(--article-text, #545667);
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.api-content-type {
|
||||
margin: $api-spacing-sm 0;
|
||||
font-size: 0.85rem;
|
||||
|
||||
code {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
background: var(--code-bg, #f5f5f5);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.api-content-type-label {
|
||||
color: var(--article-text-muted, #777);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Schema Section
|
||||
// ============================================
|
||||
|
||||
.api-schema {
|
||||
margin: $api-spacing-md 0;
|
||||
|
||||
&--nested {
|
||||
margin-left: $api-spacing-lg;
|
||||
padding-left: $api-spacing-md;
|
||||
border-left: 2px solid var(--article-border, #e8e8f0);
|
||||
}
|
||||
}
|
||||
|
||||
.api-schema-properties {
|
||||
border: 1px solid var(--article-border, #e8e8f0);
|
||||
border-radius: $api-border-radius;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-schema-property {
|
||||
padding: $api-spacing-md;
|
||||
border-bottom: 1px solid var(--article-border, #e8e8f0);
|
||||
background: var(--article-bg, #fff);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--article-bg-hover, #f9f9fb);
|
||||
}
|
||||
|
||||
&--required {
|
||||
.api-schema-property-name {
|
||||
&::after {
|
||||
content: '*';
|
||||
color: $method-delete;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-schema-property-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $api-spacing-sm;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.api-schema-property-name {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--article-heading, #2b2b2b);
|
||||
}
|
||||
|
||||
.api-schema-property-type {
|
||||
font-size: 0.8rem;
|
||||
color: var(--article-text-muted, #777);
|
||||
}
|
||||
|
||||
.api-schema-property-description {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--article-text, #545667);
|
||||
line-height: 1.5;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.api-schema-property-enum,
|
||||
.api-schema-property-default,
|
||||
.api-schema-property-example {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.api-enum-label,
|
||||
.api-default-label,
|
||||
.api-example-label {
|
||||
color: var(--article-text-muted, #777);
|
||||
}
|
||||
|
||||
.api-enum-value,
|
||||
.api-default-value,
|
||||
.api-example-value {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
font-size: 0.8rem;
|
||||
background: var(--code-bg, #f5f5f5);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
// Schema Example Block
|
||||
.api-schema-example {
|
||||
margin-top: $api-spacing-md;
|
||||
padding: $api-spacing-md;
|
||||
background: var(--code-bg, #f5f5f5);
|
||||
border-radius: $api-border-radius;
|
||||
}
|
||||
|
||||
.api-schema-example-title {
|
||||
margin: 0 0 $api-spacing-sm;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--article-text-muted, #777);
|
||||
}
|
||||
|
||||
.api-schema-example-code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
overflow-x: auto;
|
||||
|
||||
code {
|
||||
font-family: var(--mono-font, 'IBM Plex Mono', monospace);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Responses Section
|
||||
// ============================================
|
||||
|
||||
.api-responses {
|
||||
margin: $api-spacing-lg 0;
|
||||
}
|
||||
|
||||
.api-response-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $api-spacing-sm;
|
||||
}
|
||||
|
||||
.api-response {
|
||||
border: 1px solid var(--article-border, #e8e8f0);
|
||||
border-radius: $api-border-radius;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-response-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $api-spacing-sm;
|
||||
padding: $api-spacing-sm $api-spacing-md;
|
||||
background: var(--article-bg-alt, #f9f9fb);
|
||||
}
|
||||
|
||||
.api-response-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 3rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
|
||||
&--success { background-color: $status-success; }
|
||||
&--redirect { background-color: $status-redirect; }
|
||||
&--client-error { background-color: $status-client-error; }
|
||||
&--server-error { background-color: $status-server-error; }
|
||||
&--info { background-color: var(--article-text-muted, #777); }
|
||||
}
|
||||
|
||||
.api-response-description {
|
||||
font-size: 0.9rem;
|
||||
color: var(--article-text, #545667);
|
||||
}
|
||||
|
||||
.api-response-body {
|
||||
padding: $api-spacing-md;
|
||||
border-top: 1px solid var(--article-border, #e8e8f0);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Tag Overview
|
||||
// ============================================
|
||||
|
||||
.api-tag-overview {
|
||||
margin-bottom: $api-spacing-xl;
|
||||
padding-bottom: $api-spacing-lg;
|
||||
border-bottom: 1px solid var(--article-border, #e8e8f0);
|
||||
}
|
||||
|
||||
.api-tag-description {
|
||||
color: var(--article-text, #545667);
|
||||
line-height: 1.7;
|
||||
|
||||
h4, h5 {
|
||||
margin-top: $api-spacing-lg;
|
||||
margin-bottom: $api-spacing-sm;
|
||||
color: var(--article-heading, #2b2b2b);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: $api-spacing-lg;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color, #00A3FF);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Related Guides Section
|
||||
// ============================================
|
||||
|
||||
.api-related-guides {
|
||||
margin-top: $api-spacing-xl;
|
||||
padding-top: $api-spacing-lg;
|
||||
border-top: 1px solid var(--article-border, #e8e8f0);
|
||||
}
|
||||
|
||||
.api-related-title {
|
||||
margin: 0 0 $api-spacing-md;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--article-heading, #2b2b2b);
|
||||
}
|
||||
|
||||
.api-related-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-bottom: $api-spacing-sm;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color, #00A3FF);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Dark Mode Overrides
|
||||
// ============================================
|
||||
|
||||
[data-theme='dark'],
|
||||
html:has(link[title='dark-theme']:not([disabled])) {
|
||||
.api-operation {
|
||||
background: var(--article-bg, #1a1a2a);
|
||||
border-color: var(--article-border, #2a2a3a);
|
||||
}
|
||||
|
||||
.api-related-guides {
|
||||
border-color: var(--article-border, #2a2a3a);
|
||||
}
|
||||
|
||||
.api-related-title {
|
||||
color: var(--article-heading, #e0e0e0);
|
||||
}
|
||||
|
||||
.api-path {
|
||||
background: var(--code-bg, #252535);
|
||||
color: var(--article-text, #d4d7dd);
|
||||
}
|
||||
|
||||
.api-param-row,
|
||||
.api-schema-property {
|
||||
background: var(--article-bg, #1a1a2a);
|
||||
|
||||
&:hover {
|
||||
background: var(--article-bg-hover, #252535);
|
||||
}
|
||||
}
|
||||
|
||||
.api-param-enum-value,
|
||||
.api-param-default-value,
|
||||
.api-enum-value,
|
||||
.api-default-value,
|
||||
.api-example-value {
|
||||
background: var(--code-bg, #252535);
|
||||
}
|
||||
|
||||
.api-schema-example {
|
||||
background: var(--code-bg, #252535);
|
||||
}
|
||||
|
||||
.api-response-header {
|
||||
background: var(--article-bg-alt, #252535);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Responsive Adjustments
|
||||
// ============================================
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.api-operation {
|
||||
padding: $api-spacing-md;
|
||||
}
|
||||
|
||||
.api-operation-endpoint {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.api-path {
|
||||
font-size: 0.85rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.api-param-name-line,
|
||||
.api-schema-property-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,8 @@
|
|||
"layouts/code-controls",
|
||||
"layouts/v3-wayfinding",
|
||||
"layouts/api-layout",
|
||||
"layouts/api-security-schemes";
|
||||
"layouts/api-security-schemes",
|
||||
"layouts/api-hugo-native";
|
||||
|
||||
// Import Components
|
||||
@import "components/influxdb-version-detector",
|
||||
|
|
|
|||
|
|
@ -124,10 +124,20 @@ param) - shows RapiDoc with all operations for the tag For conceptual pages
|
|||
{{/* Hugo page content if any (for custom intro content) */}}
|
||||
{{ with .Content }}
|
||||
<section class="api-content-body">{{ . }}</section>
|
||||
{{ end }} {{/* RapiDoc renders all operations for this tag */}} {{ with
|
||||
.Params.staticFilePath }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Choose rendering mode: Hugo-native (POC) or RapiDoc (default) */}}
|
||||
{{ $useHugoNative := .Params.useHugoNative | default false }}
|
||||
|
||||
{{ with .Params.staticFilePath }}
|
||||
<section class="api-operations-section">
|
||||
{{ partial "api/rapidoc-tag.html" $ }}
|
||||
{{ if $useHugoNative }}
|
||||
{{/* Hugo-Native rendering (docusaurus-openapi style) */}}
|
||||
{{ partial "api/hugo-native/tag-renderer.html" $ }}
|
||||
{{ else }}
|
||||
{{/* RapiDoc rendering (current default) */}}
|
||||
{{ partial "api/rapidoc-tag.html" $ }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }} {{ end }} {{ end }} {{ partial "article/related.html" . }}
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
{{/*
|
||||
Hugo-Native Operation Renderer
|
||||
|
||||
Renders a single API operation with parameters, request body, and responses.
|
||||
Styled to match docusaurus-openapi aesthetic.
|
||||
|
||||
Params:
|
||||
- operation: Map with method, path, summary, operationId
|
||||
- spec: The full OpenAPI spec object
|
||||
- context: The page context for URL generation
|
||||
*/}}
|
||||
|
||||
{{ $operation := .operation }}
|
||||
{{ $spec := .spec }}
|
||||
{{ $method := lower $operation.method }}
|
||||
{{ $path := $operation.path }}
|
||||
{{ $operationId := $operation.operationId }}
|
||||
|
||||
{{/* Find the operation definition in the spec */}}
|
||||
{{ $pathDef := index $spec.paths $path }}
|
||||
{{ $opDef := dict }}
|
||||
{{ if $pathDef }}
|
||||
{{ $opDef = index $pathDef $method | default dict }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Generate anchor ID matching existing TOC format */}}
|
||||
{{ $anchorId := printf "%s-%s" $method $path }}
|
||||
|
||||
<div class="api-operation" id="{{ $anchorId }}" data-method="{{ $method }}" data-operation-id="{{ $operationId }}">
|
||||
{{/* Operation Header */}}
|
||||
<div class="api-operation-header">
|
||||
<div class="api-operation-endpoint">
|
||||
<span class="api-method api-method--{{ $method }}">{{ upper $method }}</span>
|
||||
<code class="api-path">{{ $path }}</code>
|
||||
</div>
|
||||
<h3 class="api-operation-summary">{{ $operation.summary }}</h3>
|
||||
</div>
|
||||
|
||||
{{/* Operation Description */}}
|
||||
{{ with $opDef.description }}
|
||||
<div class="api-operation-description">
|
||||
{{ . | markdownify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Parameters Section */}}
|
||||
{{ $params := $opDef.parameters | default slice }}
|
||||
{{ if gt (len $params) 0 }}
|
||||
{{ partial "api/hugo-native/parameters.html" (dict "parameters" $params "spec" $spec) }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Request Body Section */}}
|
||||
{{ with $opDef.requestBody }}
|
||||
{{ partial "api/hugo-native/request-body.html" (dict "requestBody" . "spec" $spec) }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Responses Section */}}
|
||||
{{ with $opDef.responses }}
|
||||
{{ partial "api/hugo-native/responses.html" (dict "responses" . "spec" $spec) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
{{/*
|
||||
Hugo-Native Parameter Row Renderer
|
||||
|
||||
Renders a single parameter with name, type, required badge, and description.
|
||||
|
||||
Params:
|
||||
- param: Parameter object with name, schema, required, description
|
||||
- spec: The full OpenAPI spec object for resolving schema $ref
|
||||
*/}}
|
||||
|
||||
{{ $param := .param }}
|
||||
{{ $spec := .spec }}
|
||||
|
||||
{{ $name := $param.name }}
|
||||
{{ $required := $param.required | default false }}
|
||||
{{ $description := $param.description | default "" }}
|
||||
|
||||
{{/* Resolve schema type */}}
|
||||
{{ $schema := $param.schema | default dict }}
|
||||
{{ $type := $schema.type | default "string" }}
|
||||
{{ $format := $schema.format | default "" }}
|
||||
{{ $enum := $schema.enum | default slice }}
|
||||
{{ $default := $schema.default }}
|
||||
|
||||
{{/* Build type display string */}}
|
||||
{{ $typeDisplay := $type }}
|
||||
{{ if $format }}
|
||||
{{ $typeDisplay = printf "%s <%s>" $type $format }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-param-row{{ if $required }} api-param-row--required{{ end }}">
|
||||
<div class="api-param-info">
|
||||
<div class="api-param-name-line">
|
||||
<code class="api-param-name">{{ $name }}</code>
|
||||
{{ if $required }}
|
||||
<span class="api-badge api-badge--required">required</span>
|
||||
{{ end }}
|
||||
<span class="api-param-type">{{ $typeDisplay }}</span>
|
||||
</div>
|
||||
|
||||
{{ if $description }}
|
||||
<div class="api-param-description">
|
||||
{{ $description | markdownify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Show enum values if present */}}
|
||||
{{ if gt (len $enum) 0 }}
|
||||
<div class="api-param-enum">
|
||||
<span class="api-param-enum-label">Allowed values:</span>
|
||||
{{ range $i, $val := $enum }}
|
||||
{{ if $i }}, {{ end }}<code class="api-param-enum-value">{{ $val }}</code>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Show default value if present */}}
|
||||
{{ if $default }}
|
||||
<div class="api-param-default">
|
||||
<span class="api-param-default-label">Default:</span>
|
||||
<code class="api-param-default-value">{{ $default }}</code>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
{{/*
|
||||
Hugo-Native Parameters Renderer
|
||||
|
||||
Renders a table of API operation parameters (query, path, header).
|
||||
Resolves $ref references to component parameters.
|
||||
|
||||
Params:
|
||||
- parameters: Array of parameter objects
|
||||
- spec: The full OpenAPI spec object for resolving $ref
|
||||
*/}}
|
||||
|
||||
{{ $parameters := .parameters }}
|
||||
{{ $spec := .spec }}
|
||||
|
||||
{{/* Resolve $ref parameters and group by location */}}
|
||||
{{ $queryParams := slice }}
|
||||
{{ $pathParams := slice }}
|
||||
{{ $headerParams := slice }}
|
||||
|
||||
{{ range $parameters }}
|
||||
{{ $param := . }}
|
||||
|
||||
{{/* Resolve $ref if present */}}
|
||||
{{ if isset . "$ref" }}
|
||||
{{ $refPath := index . "$ref" }}
|
||||
{{/* Parse ref like "#/components/parameters/db" */}}
|
||||
{{ $refParts := split $refPath "/" }}
|
||||
{{ if ge (len $refParts) 4 }}
|
||||
{{ $paramName := index $refParts 3 }}
|
||||
{{ with index $spec.components.parameters $paramName }}
|
||||
{{ $param = . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Group by 'in' location */}}
|
||||
{{ $location := $param.in | default "query" }}
|
||||
{{ if eq $location "query" }}
|
||||
{{ $queryParams = $queryParams | append $param }}
|
||||
{{ else if eq $location "path" }}
|
||||
{{ $pathParams = $pathParams | append $param }}
|
||||
{{ else if eq $location "header" }}
|
||||
{{ $headerParams = $headerParams | append $param }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-parameters">
|
||||
<h4 class="api-section-title">Parameters</h4>
|
||||
|
||||
{{/* Path Parameters */}}
|
||||
{{ if gt (len $pathParams) 0 }}
|
||||
<div class="api-param-group">
|
||||
<h5 class="api-param-group-title">Path parameters</h5>
|
||||
<div class="api-param-list">
|
||||
{{ range $pathParams }}
|
||||
{{ partial "api/hugo-native/parameter-row.html" (dict "param" . "spec" $spec) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Query Parameters */}}
|
||||
{{ if gt (len $queryParams) 0 }}
|
||||
<div class="api-param-group">
|
||||
<h5 class="api-param-group-title">Query parameters</h5>
|
||||
<div class="api-param-list">
|
||||
{{ range $queryParams }}
|
||||
{{ partial "api/hugo-native/parameter-row.html" (dict "param" . "spec" $spec) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Header Parameters */}}
|
||||
{{ if gt (len $headerParams) 0 }}
|
||||
<div class="api-param-group">
|
||||
<h5 class="api-param-group-title">Header parameters</h5>
|
||||
<div class="api-param-list">
|
||||
{{ range $headerParams }}
|
||||
{{ partial "api/hugo-native/parameter-row.html" (dict "param" . "spec" $spec) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
{{/*
|
||||
Hugo-Native Request Body Renderer
|
||||
|
||||
Renders the request body section including schema properties.
|
||||
|
||||
Params:
|
||||
- requestBody: OpenAPI requestBody object
|
||||
- spec: The full OpenAPI spec object for resolving $ref
|
||||
*/}}
|
||||
|
||||
{{ $requestBody := .requestBody }}
|
||||
{{ $spec := .spec }}
|
||||
|
||||
{{ $required := $requestBody.required | default false }}
|
||||
{{ $description := $requestBody.description | default "" }}
|
||||
|
||||
{{/* Get content schema - typically application/json */}}
|
||||
{{ $content := $requestBody.content | default dict }}
|
||||
{{ $jsonContent := index $content "application/json" | default dict }}
|
||||
{{ $schema := $jsonContent.schema | default dict }}
|
||||
|
||||
{{/* Resolve $ref if present */}}
|
||||
{{ $resolvedSchema := $schema }}
|
||||
{{ if isset $schema "$ref" }}
|
||||
{{ $refPath := index $schema "$ref" }}
|
||||
{{/* Parse ref like "#/components/schemas/DistinctCacheCreateRequest" */}}
|
||||
{{ $refParts := split $refPath "/" }}
|
||||
{{ if ge (len $refParts) 4 }}
|
||||
{{ $schemaName := index $refParts 3 }}
|
||||
{{ with index $spec.components.schemas $schemaName }}
|
||||
{{ $resolvedSchema = . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-request-body">
|
||||
<h4 class="api-section-title">
|
||||
Request body
|
||||
{{ if $required }}
|
||||
<span class="api-badge api-badge--required">required</span>
|
||||
{{ end }}
|
||||
</h4>
|
||||
|
||||
{{ if $description }}
|
||||
<div class="api-request-body-description">
|
||||
{{ $description | markdownify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Content type indicator */}}
|
||||
<div class="api-content-type">
|
||||
<span class="api-content-type-label">Content-Type:</span>
|
||||
<code>application/json</code>
|
||||
</div>
|
||||
|
||||
{{/* Render schema properties */}}
|
||||
{{ with $resolvedSchema }}
|
||||
{{ partial "api/hugo-native/schema.html" (dict "schema" . "spec" $spec "level" 0) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
{{/*
|
||||
Hugo-Native Responses Renderer
|
||||
|
||||
Renders the responses section for an API operation.
|
||||
Shows status codes, descriptions, and response schemas.
|
||||
|
||||
Params:
|
||||
- responses: Map of status codes to response objects
|
||||
- spec: The full OpenAPI spec object for resolving $ref
|
||||
*/}}
|
||||
|
||||
{{ $responses := .responses }}
|
||||
{{ $spec := .spec }}
|
||||
|
||||
<div class="api-responses">
|
||||
<h4 class="api-section-title">Responses</h4>
|
||||
|
||||
<div class="api-response-list">
|
||||
{{ range $statusCode, $response := $responses }}
|
||||
{{/* Resolve $ref if present */}}
|
||||
{{ $resolvedResponse := $response }}
|
||||
{{ if isset $response "$ref" }}
|
||||
{{ $refPath := index $response "$ref" }}
|
||||
{{ $refParts := split $refPath "/" }}
|
||||
{{ if ge (len $refParts) 4 }}
|
||||
{{ $responseName := index $refParts 3 }}
|
||||
{{ with index $spec.components.responses $responseName }}
|
||||
{{ $resolvedResponse = . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ $description := $resolvedResponse.description | default "" }}
|
||||
{{ $content := $resolvedResponse.content | default dict }}
|
||||
|
||||
{{/* Determine status category for styling */}}
|
||||
{{ $statusCategory := "info" }}
|
||||
{{ if hasPrefix $statusCode "2" }}
|
||||
{{ $statusCategory = "success" }}
|
||||
{{ else if hasPrefix $statusCode "3" }}
|
||||
{{ $statusCategory = "redirect" }}
|
||||
{{ else if hasPrefix $statusCode "4" }}
|
||||
{{ $statusCategory = "client-error" }}
|
||||
{{ else if hasPrefix $statusCode "5" }}
|
||||
{{ $statusCategory = "server-error" }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-response api-response--{{ $statusCategory }}">
|
||||
<div class="api-response-header">
|
||||
<span class="api-response-status api-response-status--{{ $statusCategory }}">{{ $statusCode }}</span>
|
||||
<span class="api-response-description">{{ $description }}</span>
|
||||
</div>
|
||||
|
||||
{{/* Response body schema if present */}}
|
||||
{{ with $content }}
|
||||
{{ $jsonContent := index . "application/json" | default dict }}
|
||||
{{ with $jsonContent.schema }}
|
||||
{{/* Resolve schema $ref if present */}}
|
||||
{{ $resolvedSchema := . }}
|
||||
{{ if isset . "$ref" }}
|
||||
{{ $refPath := index . "$ref" }}
|
||||
{{ $refParts := split $refPath "/" }}
|
||||
{{ if ge (len $refParts) 4 }}
|
||||
{{ $schemaName := index $refParts 3 }}
|
||||
{{ with index $spec.components.schemas $schemaName }}
|
||||
{{ $resolvedSchema = . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-response-body">
|
||||
{{ partial "api/hugo-native/schema.html" (dict "schema" $resolvedSchema "spec" $spec "level" 0) }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
{{/*
|
||||
Hugo-Native Schema Renderer
|
||||
|
||||
Renders a JSON schema as a property table with nested object support.
|
||||
Similar to docusaurus-openapi's schema tables.
|
||||
|
||||
Params:
|
||||
- schema: OpenAPI schema object
|
||||
- spec: The full OpenAPI spec object for resolving $ref
|
||||
- level: Nesting level (0 = root)
|
||||
*/}}
|
||||
|
||||
{{ $schema := .schema }}
|
||||
{{ $spec := .spec }}
|
||||
{{ $level := .level | default 0 }}
|
||||
|
||||
{{ $type := $schema.type | default "object" }}
|
||||
{{ $properties := $schema.properties | default dict }}
|
||||
{{ $required := $schema.required | default slice }}
|
||||
{{ $example := $schema.example }}
|
||||
|
||||
{{/* Convert required slice to map for easy lookup */}}
|
||||
{{ $requiredMap := dict }}
|
||||
{{ range $required }}
|
||||
{{ $requiredMap = merge $requiredMap (dict . true) }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-schema{{ if gt $level 0 }} api-schema--nested{{ end }}" data-level="{{ $level }}">
|
||||
{{ if gt (len $properties) 0 }}
|
||||
<div class="api-schema-properties">
|
||||
{{ range $propName, $propSchema := $properties }}
|
||||
{{ $isRequired := index $requiredMap $propName | default false }}
|
||||
{{ $propType := $propSchema.type | default "string" }}
|
||||
{{ $propDescription := $propSchema.description | default "" }}
|
||||
{{ $propFormat := $propSchema.format | default "" }}
|
||||
{{ $propEnum := $propSchema.enum | default slice }}
|
||||
{{ $propDefault := $propSchema.default }}
|
||||
{{ $propExample := $propSchema.example }}
|
||||
|
||||
{{/* Build type display */}}
|
||||
{{ $typeDisplay := $propType }}
|
||||
{{ if eq $propType "array" }}
|
||||
{{ $itemsType := "object" }}
|
||||
{{ with $propSchema.items }}
|
||||
{{ $itemsType = .type | default "object" }}
|
||||
{{ end }}
|
||||
{{ $typeDisplay = printf "%s[]" $itemsType }}
|
||||
{{ else if $propFormat }}
|
||||
{{ $typeDisplay = printf "%s <%s>" $propType $propFormat }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-schema-property{{ if $isRequired }} api-schema-property--required{{ end }}">
|
||||
<div class="api-schema-property-header">
|
||||
<code class="api-schema-property-name">{{ $propName }}</code>
|
||||
{{ if $isRequired }}
|
||||
<span class="api-badge api-badge--required">required</span>
|
||||
{{ end }}
|
||||
<span class="api-schema-property-type">{{ $typeDisplay }}</span>
|
||||
</div>
|
||||
|
||||
{{ if $propDescription }}
|
||||
<div class="api-schema-property-description">
|
||||
{{ $propDescription | markdownify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Enum values */}}
|
||||
{{ if gt (len $propEnum) 0 }}
|
||||
<div class="api-schema-property-enum">
|
||||
<span class="api-enum-label">Allowed:</span>
|
||||
{{ range $i, $val := $propEnum }}
|
||||
{{ if $i }}, {{ end }}<code class="api-enum-value">{{ $val }}</code>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Default value */}}
|
||||
{{ if $propDefault }}
|
||||
<div class="api-schema-property-default">
|
||||
<span class="api-default-label">Default:</span>
|
||||
<code class="api-default-value">{{ $propDefault }}</code>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Example value */}}
|
||||
{{ if $propExample }}
|
||||
<div class="api-schema-property-example">
|
||||
<span class="api-example-label">Example:</span>
|
||||
<code class="api-example-value">{{ jsonify $propExample }}</code>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Nested object/array rendering (limit depth to prevent infinite loops) */}}
|
||||
{{ if and (eq $propType "object") (lt $level 2) }}
|
||||
{{ with $propSchema.properties }}
|
||||
{{ partial "api/hugo-native/schema.html" (dict "schema" $propSchema "spec" $spec "level" (add $level 1)) }}
|
||||
{{ end }}
|
||||
{{ else if and (eq $propType "array") (lt $level 2) }}
|
||||
{{ with $propSchema.items }}
|
||||
{{ if isset . "properties" }}
|
||||
{{ partial "api/hugo-native/schema.html" (dict "schema" . "spec" $spec "level" (add $level 1)) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Show example at schema level */}}
|
||||
{{ if and $example (eq $level 0) }}
|
||||
<div class="api-schema-example">
|
||||
<h5 class="api-schema-example-title">Example</h5>
|
||||
<pre class="api-schema-example-code"><code class="language-json">{{ jsonify (dict "indent" " ") $example }}</code></pre>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
{{/*
|
||||
Hugo-Native Tag Page Renderer
|
||||
|
||||
Renders all operations for a tag page using Hugo templates instead of RapiDoc.
|
||||
Parses the OpenAPI spec file and renders each operation natively.
|
||||
|
||||
Required page params:
|
||||
- staticFilePath: Path to the OpenAPI specification file (YAML)
|
||||
- operations: Array of operation metadata from frontmatter
|
||||
|
||||
Usage:
|
||||
{{ partial "api/hugo-native/tag-renderer.html" . }}
|
||||
*/}}
|
||||
|
||||
{{ $page := . }}
|
||||
{{ $specPath := .Params.staticFilePath }}
|
||||
{{ $operations := .Params.operations | default slice }}
|
||||
|
||||
{{/* Load and parse the OpenAPI spec from static/ directory */}}
|
||||
{{ $spec := dict }}
|
||||
{{ if $specPath }}
|
||||
{{/* Build path to static file (staticFilePath has leading slash, e.g. /openapi/...) */}}
|
||||
{{ $staticFile := printf "static%s" $specPath }}
|
||||
|
||||
{{/* Use os.ReadFile (Hugo 0.121+) to read from static directory */}}
|
||||
{{ with os.ReadFile $staticFile }}
|
||||
{{ $spec = . | transform.Unmarshal }}
|
||||
{{ else }}
|
||||
{{/* Fallback: try unmounted resources (for assets mount configuration) */}}
|
||||
{{ $cleanPath := strings.TrimPrefix "/" $specPath }}
|
||||
{{ with resources.Get $cleanPath }}
|
||||
{{ $spec = .Content | transform.Unmarshal }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Tag description and related links from spec */}}
|
||||
{{ $tagDescription := "" }}
|
||||
{{ $tagRelated := slice }}
|
||||
{{ $tagName := .Params.tag | default "" }}
|
||||
{{ range $spec.tags }}
|
||||
{{ if eq .name $tagName }}
|
||||
{{ $tagDescription = .description | default "" }}
|
||||
{{/* Extract x-influxdata-related from the tag */}}
|
||||
{{ with index . "x-influxdata-related" }}
|
||||
{{ $tagRelated = . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div class="api-hugo-native" data-component="api-hugo-native">
|
||||
{{/* Tag Overview/Description */}}
|
||||
{{ if $tagDescription }}
|
||||
<section class="api-tag-overview">
|
||||
<div class="api-tag-description">
|
||||
{{ $tagDescription | markdownify }}
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{/* Operations List */}}
|
||||
<section class="api-operations">
|
||||
{{ range $operations }}
|
||||
{{ partial "api/hugo-native/operation.html" (dict
|
||||
"operation" .
|
||||
"spec" $spec
|
||||
"context" $page
|
||||
) }}
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
{{/* Related Guides - displayed at bottom like standard page template */}}
|
||||
{{ if gt (len $tagRelated) 0 }}
|
||||
<section class="api-related-guides">
|
||||
<h4 class="api-related-title">Related guides</h4>
|
||||
<ul class="api-related-list">
|
||||
{{ range $tagRelated }}
|
||||
<li><a href="{{ .href }}">{{ .title }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</section>
|
||||
{{ end }}
|
||||
</div>
|
||||
Loading…
Reference in New Issue