From 4959d93c26f16274dbfca80aeda526dff6dc629f Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Fri, 13 Feb 2026 00:07:18 -0600 Subject: [PATCH] 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. --- assets/styles/layouts/_api-hugo-native.scss | 598 ++++++++++++++++++ assets/styles/styles-default.scss | 3 +- layouts/api/list.html | 16 +- .../partials/api/hugo-native/operation.html | 61 ++ .../api/hugo-native/parameter-row.html | 65 ++ .../partials/api/hugo-native/parameters.html | 85 +++ .../api/hugo-native/request-body.html | 60 ++ .../partials/api/hugo-native/responses.html | 79 +++ layouts/partials/api/hugo-native/schema.html | 117 ++++ .../api/hugo-native/tag-renderer.html | 83 +++ 10 files changed, 1163 insertions(+), 4 deletions(-) create mode 100644 assets/styles/layouts/_api-hugo-native.scss create mode 100644 layouts/partials/api/hugo-native/operation.html create mode 100644 layouts/partials/api/hugo-native/parameter-row.html create mode 100644 layouts/partials/api/hugo-native/parameters.html create mode 100644 layouts/partials/api/hugo-native/request-body.html create mode 100644 layouts/partials/api/hugo-native/responses.html create mode 100644 layouts/partials/api/hugo-native/schema.html create mode 100644 layouts/partials/api/hugo-native/tag-renderer.html diff --git a/assets/styles/layouts/_api-hugo-native.scss b/assets/styles/layouts/_api-hugo-native.scss new file mode 100644 index 000000000..2b26bf16a --- /dev/null +++ b/assets/styles/layouts/_api-hugo-native.scss @@ -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; + } +} diff --git a/assets/styles/styles-default.scss b/assets/styles/styles-default.scss index ce318f720..589160164 100644 --- a/assets/styles/styles-default.scss +++ b/assets/styles/styles-default.scss @@ -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", diff --git a/layouts/api/list.html b/layouts/api/list.html index f36746f6b..b12dbc965 100644 --- a/layouts/api/list.html +++ b/layouts/api/list.html @@ -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 }}
{{ . }}
- {{ 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 }}
- {{ 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 }}
{{ end }} {{ end }} {{ end }} {{ partial "article/related.html" . }} diff --git a/layouts/partials/api/hugo-native/operation.html b/layouts/partials/api/hugo-native/operation.html new file mode 100644 index 000000000..72ce380ce --- /dev/null +++ b/layouts/partials/api/hugo-native/operation.html @@ -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 }} + +
+ {{/* Operation Header */}} +
+
+ {{ upper $method }} + {{ $path }} +
+

{{ $operation.summary }}

+
+ + {{/* Operation Description */}} + {{ with $opDef.description }} +
+ {{ . | markdownify }} +
+ {{ 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 }} +
diff --git a/layouts/partials/api/hugo-native/parameter-row.html b/layouts/partials/api/hugo-native/parameter-row.html new file mode 100644 index 000000000..5f34125a9 --- /dev/null +++ b/layouts/partials/api/hugo-native/parameter-row.html @@ -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 }} + +
+
+
+ {{ $name }} + {{ if $required }} + required + {{ end }} + {{ $typeDisplay }} +
+ + {{ if $description }} +
+ {{ $description | markdownify }} +
+ {{ end }} + + {{/* Show enum values if present */}} + {{ if gt (len $enum) 0 }} +
+ Allowed values: + {{ range $i, $val := $enum }} + {{ if $i }}, {{ end }}{{ $val }} + {{ end }} +
+ {{ end }} + + {{/* Show default value if present */}} + {{ if $default }} +
+ Default: + {{ $default }} +
+ {{ end }} +
+
diff --git a/layouts/partials/api/hugo-native/parameters.html b/layouts/partials/api/hugo-native/parameters.html new file mode 100644 index 000000000..43ad2efff --- /dev/null +++ b/layouts/partials/api/hugo-native/parameters.html @@ -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 }} + +
+

Parameters

+ + {{/* Path Parameters */}} + {{ if gt (len $pathParams) 0 }} +
+
Path parameters
+
+ {{ range $pathParams }} + {{ partial "api/hugo-native/parameter-row.html" (dict "param" . "spec" $spec) }} + {{ end }} +
+
+ {{ end }} + + {{/* Query Parameters */}} + {{ if gt (len $queryParams) 0 }} +
+
Query parameters
+
+ {{ range $queryParams }} + {{ partial "api/hugo-native/parameter-row.html" (dict "param" . "spec" $spec) }} + {{ end }} +
+
+ {{ end }} + + {{/* Header Parameters */}} + {{ if gt (len $headerParams) 0 }} +
+
Header parameters
+
+ {{ range $headerParams }} + {{ partial "api/hugo-native/parameter-row.html" (dict "param" . "spec" $spec) }} + {{ end }} +
+
+ {{ end }} +
diff --git a/layouts/partials/api/hugo-native/request-body.html b/layouts/partials/api/hugo-native/request-body.html new file mode 100644 index 000000000..f90df93f1 --- /dev/null +++ b/layouts/partials/api/hugo-native/request-body.html @@ -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 }} + +
+

+ Request body + {{ if $required }} + required + {{ end }} +

+ + {{ if $description }} +
+ {{ $description | markdownify }} +
+ {{ end }} + + {{/* Content type indicator */}} +
+ Content-Type: + application/json +
+ + {{/* Render schema properties */}} + {{ with $resolvedSchema }} + {{ partial "api/hugo-native/schema.html" (dict "schema" . "spec" $spec "level" 0) }} + {{ end }} +
diff --git a/layouts/partials/api/hugo-native/responses.html b/layouts/partials/api/hugo-native/responses.html new file mode 100644 index 000000000..8f66a7cd8 --- /dev/null +++ b/layouts/partials/api/hugo-native/responses.html @@ -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 }} + +
+

Responses

+ +
+ {{ 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 }} + +
+
+ {{ $statusCode }} + {{ $description }} +
+ + {{/* 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 }} + +
+ {{ partial "api/hugo-native/schema.html" (dict "schema" $resolvedSchema "spec" $spec "level" 0) }} +
+ {{ end }} + {{ end }} +
+ {{ end }} +
+
diff --git a/layouts/partials/api/hugo-native/schema.html b/layouts/partials/api/hugo-native/schema.html new file mode 100644 index 000000000..6d2f2c06d --- /dev/null +++ b/layouts/partials/api/hugo-native/schema.html @@ -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 }} + +
+ {{ if gt (len $properties) 0 }} +
+ {{ 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 }} + +
+
+ {{ $propName }} + {{ if $isRequired }} + required + {{ end }} + {{ $typeDisplay }} +
+ + {{ if $propDescription }} +
+ {{ $propDescription | markdownify }} +
+ {{ end }} + + {{/* Enum values */}} + {{ if gt (len $propEnum) 0 }} +
+ Allowed: + {{ range $i, $val := $propEnum }} + {{ if $i }}, {{ end }}{{ $val }} + {{ end }} +
+ {{ end }} + + {{/* Default value */}} + {{ if $propDefault }} +
+ Default: + {{ $propDefault }} +
+ {{ end }} + + {{/* Example value */}} + {{ if $propExample }} +
+ Example: + {{ jsonify $propExample }} +
+ {{ 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 }} +
+ {{ end }} +
+ {{ end }} + + {{/* Show example at schema level */}} + {{ if and $example (eq $level 0) }} +
+
Example
+
{{ jsonify (dict "indent" "  ") $example }}
+
+ {{ end }} +
diff --git a/layouts/partials/api/hugo-native/tag-renderer.html b/layouts/partials/api/hugo-native/tag-renderer.html new file mode 100644 index 000000000..ebd4a94e2 --- /dev/null +++ b/layouts/partials/api/hugo-native/tag-renderer.html @@ -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 }} + +
+ {{/* Tag Overview/Description */}} + {{ if $tagDescription }} +
+
+ {{ $tagDescription | markdownify }} +
+
+ {{ end }} + + {{/* Operations List */}} +
+ {{ range $operations }} + {{ partial "api/hugo-native/operation.html" (dict + "operation" . + "spec" $spec + "context" $page + ) }} + {{ end }} +
+ + {{/* Related Guides - displayed at bottom like standard page template */}} + {{ if gt (len $tagRelated) 0 }} + + {{ end }} +