diff --git a/exampleSite/assets/tictactoe.json b/exampleSite/assets/tictactoe.json new file mode 100644 index 0000000..79f34cb --- /dev/null +++ b/exampleSite/assets/tictactoe.json @@ -0,0 +1,261 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Tic Tac Toe", + "description": "This API allows writing down marks on a Tic Tac Toe board\nand requesting the state of the board or of individual squares.\n", + "version": "1.0.0" + }, + "tags": [ + { + "name": "Gameplay" + } + ], + "paths": { + "/board": { + "get": { + "summary": "Get the whole board", + "description": "Retrieves the current state of the board and the winner.", + "tags": ["Gameplay"], + "operationId": "get-board", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/status" + } + } + } + } + }, + "security": [ + { + "defaultApiKey": [] + }, + { + "app2AppOauth": ["board:read"] + } + ] + } + }, + "/board/{row}/{column}": { + "parameters": [ + { + "$ref": "#/components/parameters/rowParam" + }, + { + "$ref": "#/components/parameters/columnParam" + } + ], + "get": { + "summary": "Get a single board square", + "description": "Retrieves the requested square.", + "tags": ["Gameplay"], + "operationId": "get-square", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/mark" + } + } + } + }, + "400": { + "description": "The provided parameters are incorrect", + "content": { + "text/html": { + "schema": { + "$ref": "#/components/schemas/errorMessage" + }, + "example": "Illegal coordinates" + } + } + } + }, + "security": [ + { + "bearerHttpAuthentication": [] + }, + { + "user2AppOauth": ["board:read"] + } + ] + }, + "put": { + "summary": "Set a single board square", + "description": "Places a mark on the board and retrieves the whole board and the winner (if any).", + "tags": ["Gameplay"], + "operationId": "put-square", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/mark" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/status" + } + } + } + }, + "400": { + "description": "The provided parameters are incorrect", + "content": { + "text/html": { + "schema": { + "$ref": "#/components/schemas/errorMessage" + }, + "examples": { + "illegalCoordinates": { + "value": "Illegal coordinates." + }, + "notEmpty": { + "value": "Square is not empty." + }, + "invalidMark": { + "value": "Invalid Mark (X or O)." + } + } + } + } + } + }, + "security": [ + { + "bearerHttpAuthentication": [] + }, + { + "user2AppOauth": ["board:write"] + } + ] + } + } + }, + "components": { + "parameters": { + "rowParam": { + "description": "Board row (vertical coordinate)", + "name": "row", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/coordinate" + } + }, + "columnParam": { + "description": "Board column (horizontal coordinate)", + "name": "column", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/coordinate" + } + } + }, + "schemas": { + "errorMessage": { + "type": "string", + "maxLength": 256, + "description": "A text message describing an error" + }, + "coordinate": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "example": 1 + }, + "mark": { + "type": "string", + "enum": [".", "X", "O"], + "description": "Possible values for a board square. `.` means empty square.", + "example": "." + }, + "board": { + "type": "array", + "maxItems": 3, + "minItems": 3, + "items": { + "type": "array", + "maxItems": 3, + "minItems": 3, + "items": { + "$ref": "#/components/schemas/mark" + } + } + }, + "winner": { + "type": "string", + "enum": [".", "X", "O"], + "description": "Winner of the game. `.` means nobody has won yet.", + "example": "." + }, + "status": { + "type": "object", + "properties": { + "winner": { + "$ref": "#/components/schemas/winner" + }, + "board": { + "$ref": "#/components/schemas/board" + } + } + } + }, + "securitySchemes": { + "defaultApiKey": { + "description": "API key provided in console", + "type": "apiKey", + "name": "api-key", + "in": "header" + }, + "basicHttpAuthentication": { + "description": "Basic HTTP Authentication", + "type": "http", + "scheme": "Basic" + }, + "bearerHttpAuthentication": { + "description": "Bearer token using a JWT", + "type": "http", + "scheme": "Bearer", + "bearerFormat": "JWT" + }, + "app2AppOauth": { + "type": "oauth2", + "flows": { + "clientCredentials": { + "tokenUrl": "https://learn.openapis.org/oauth/2.0/token", + "scopes": { + "board:read": "Read the board" + } + } + } + }, + "user2AppOauth": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://learn.openapis.org/oauth/2.0/auth", + "tokenUrl": "https://learn.openapis.org/oauth/2.0/token", + "scopes": { + "board:read": "Read the board", + "board:write": "Write to the board" + } + } + } + } + } + } +} diff --git a/exampleSite/content.en/_index.md b/exampleSite/content.en/_index.md index caba096..342ea6a 100644 --- a/exampleSite/content.en/_index.md +++ b/exampleSite/content.en/_index.md @@ -5,62 +5,42 @@ layout: landing
# Hugo Book {anchor=false} -{{% columns %}} -- Documentation theme for [Hugo](https://gohugo.io) as simple as plain book. - Hugo-Book theme is designed to be easy to read, write, navigate and maintain. - It is an attempt to create a sustainable web project. +Documentation theme for [Hugo](https://gohugo.io) as simple as plain book. - {{}} +Hugo-Book theme is designed to be easy to read, write, navigate and maintain. +It is an attempt to create a sustainable web project. -- ```shell - # TL;DR - git clone https://github.com/alex-shpak/hugo-book-starter documentation - cd documentation - git submodule update --init --remote - hugo server --minify - ``` -{{% /columns %}} +{{}} +

{{% columns %}} -- {{< card >}} - ### Probably fast {anchor=false} +- ### Probably fast {anchor=false} Built on Hugo static site generator. "The world’s fastest framework for building websites". - {{< /card >}} -- {{< card >}} - ### 50% JS free {anchor=false} +- ### 50% JS free {anchor=false} All important features are working even with JavaScript disabled in the browser, including interactive shortcodes. - {{< /card >}} -- {{< card >}} - ### Minimalistic {anchor=false} +- ### Minimalistic {anchor=false} For real, it is very minimalistic, black on white. No extra tools are needed to build the site, only Hugo. No heavy JS or CSS frameworks included. - {{< /card >}} {{% /columns %}} {{% columns %}} -- {{< card >}} - ### Shortcodes {anchor=false} +- ### Shortcodes {anchor=false} Pretty good shortcodes are included to enhance content: [Columns](/docs/content/shortcodes/columns/), - [Cards](/docs/content/shortcodes/cards/), + [Cards](/docs/content/shortcodes/experimental/cards/), [Tabs](/docs/content/shortcodes/tabs/), - [Images](/docs/content/shortcodes/images/), + [Images](/docs/content/shortcodes/experimental/images/), [Asciinema](/docs/content/shortcodes/asciinema/), [KaTex](/docs/content/shortcodes/katex/), [Mermaid](/docs/content/shortcodes/mermaid/) and others. - {{< /card >}} -- {{< card >}} - ### Even more {anchor=false} +- ### Even more {anchor=false} Do people actually read these? I thought it was just a visual filler. But there is full-text search and multi-language support. - {{< /card >}} -- {{< card >}} - ### Made to be extendable {anchor=false} +- ### Made to be extendable {anchor=false} There are multiple points to inject own styles and templates to make it your own. - {{< /card >}} {{% /columns %}} diff --git a/exampleSite/content.en/docs/content/shortcodes/experimental/_index.md b/exampleSite/content.en/docs/content/shortcodes/experimental/_index.md new file mode 100644 index 0000000..62729a9 --- /dev/null +++ b/exampleSite/content.en/docs/content/shortcodes/experimental/_index.md @@ -0,0 +1,3 @@ +--- +bookCollapseSection: true +--- diff --git a/exampleSite/content.en/docs/content/shortcodes/badges.md b/exampleSite/content.en/docs/content/shortcodes/experimental/badges.md similarity index 100% rename from exampleSite/content.en/docs/content/shortcodes/badges.md rename to exampleSite/content.en/docs/content/shortcodes/experimental/badges.md diff --git a/exampleSite/content.en/docs/content/shortcodes/cards.md b/exampleSite/content.en/docs/content/shortcodes/experimental/cards.md similarity index 87% rename from exampleSite/content.en/docs/content/shortcodes/cards.md rename to exampleSite/content.en/docs/content/shortcodes/experimental/cards.md index a4fc03b..7853b36 100644 --- a/exampleSite/content.en/docs/content/shortcodes/cards.md +++ b/exampleSite/content.en/docs/content/shortcodes/experimental/cards.md @@ -1,6 +1,6 @@ # Cards -Content blocks with optional images and links. Often used with [Columns](/docs/content/shortcodes/columns) for grid layouts. +Content blocks with optional images and links. Often used with [Columns](/docs/content/shortcodes/columns/) for grid layouts. ## Syntax @@ -18,7 +18,7 @@ Markdown content Cards can display an image above the content. {{< /card >}} -- {{< card href="/docs/content/shortcodes/cards" >}} +- {{< card href="/docs/content/shortcodes/experimental/cards/" >}} ### With Link {anchor=false} When `href` is set, the entire card becomes clickable. {{< /card >}} diff --git a/exampleSite/content.en/docs/content/shortcodes/images.md b/exampleSite/content.en/docs/content/shortcodes/experimental/images.md similarity index 100% rename from exampleSite/content.en/docs/content/shortcodes/images.md rename to exampleSite/content.en/docs/content/shortcodes/experimental/images.md diff --git a/exampleSite/content.en/docs/content/shortcodes/experimental/openapi.md b/exampleSite/content.en/docs/content/shortcodes/experimental/openapi.md new file mode 100644 index 0000000..1573cd0 --- /dev/null +++ b/exampleSite/content.en/docs/content/shortcodes/experimental/openapi.md @@ -0,0 +1,19 @@ +--- +title: OpenAPI +--- + +# OpenAPI + +Renders an OpenAPI 3.x specification as a documentation page. Operations are grouped by their first tag. + +## Syntax + +```tpl +{{%/* openapi src="spec.json" */%}} +``` + +The spec file is loaded via `resources.Get`, so the path is relative to the site's `assets/` directory. JSON and YAML are both supported. + +## Example + +{{% openapi src="tictactoe.json" %}} diff --git a/layouts/_shortcodes/openapi.html b/layouts/_shortcodes/openapi.html new file mode 100644 index 0000000..ceecc9e --- /dev/null +++ b/layouts/_shortcodes/openapi.html @@ -0,0 +1,385 @@ +{{- $source := or (.Get "src") (.Get 0) -}} +{{- with resources.Get $source -}} +{{- $raw := . -}} +{{- if or (strings.HasSuffix $source ".yaml") (strings.HasSuffix $source ".yml") -}} + {{- $raw = resources.FromString (printf "%s.json" $source) (. | transform.Unmarshal | jsonify) -}} +{{- end -}} +{{- $spec := openapi3.Unmarshal $raw -}} + +{{ with $spec.Info }}{{ template "openapi-info" (dict "info" . "externalDocs" $spec.ExternalDocs) }}{{ end }} +{{ with $spec.Servers }}{{ template "openapi-servers" (dict "servers" .) }}{{ end }} +{{ with $spec.Components }}{{ with .SecuritySchemes }}{{ template "openapi-security" (dict "schemes" .) }}{{ end }}{{ end }} +{{ template "openapi-operations" (dict "spec" $spec) }} +{{ with $spec.Components }}{{ with .Schemas }}{{ template "openapi-models" (dict "schemas" .) }}{{ end }}{{ end }} + +{{- else -}} +{{- errorf "openapi shortcode: resource %q not found" $source -}} +{{- end -}} + + +{{- /* ========== Info ========== */ -}} +{{- define "openapi-info" -}} +{{- $info := .info -}} +{{- $externalDocs := .externalDocs }} +{{ with $info.Description }}{{ . }}{{ end }} + +{{ $meta := slice -}} +{{- with $info.Version }}{{ $meta = $meta | append (printf "`v%s`" .) }}{{ end -}} +{{- with $info.License }}{{ if .URL }}{{ $meta = $meta | append (printf "[%s](%s)" .Name .URL) }}{{ else }}{{ $meta = $meta | append .Name }}{{ end }}{{ end -}} +{{- with $info.Contact }}{{ with .Email }}{{ $meta = $meta | append (printf "[%s](mailto:%s)" . .) }}{{ end }}{{ end -}} +{{- with $info.TermsOfService }}{{ $meta = $meta | append (printf "[Terms of service](%s)" .) }}{{ end -}} +{{- with $externalDocs }}{{ $meta = $meta | append (printf "[%s](%s)" (or .Description .URL) .URL) }}{{ end }} + +{{ delimit $meta " \n" | safeHTML }} +{{- end -}} + + +{{- /* ========== Servers ========== */ -}} +{{- define "openapi-servers" }} + +## Servers + +{{ range .servers }} +{{ with .Description }}{{ . | chomp | replaceRE "\n" " " }}{{ else }}Server{{ end }} +: [`{{ .URL }}`]({{ .URL }}) +{{- range $name, $var := .Variables }} +: `{{ $name }}`: default `{{ $var.Default }}`{{ with $var.Enum }}, enum: {{ delimit . ", " }}{{ end }}{{ with $var.Description }}, {{ . | chomp | replaceRE "\n" " " }}{{ end }} +{{- end }} +{{ end -}} +{{- end -}} + + +{{- /* ========== Security ========== */ -}} +{{- define "openapi-security" -}} +{{- $schemes := .schemes }} + +## Security + +{{ range $name, $schemeRef := $schemes }} + {{- with $schemeRef.Value }} +{{ with .Description }}{{ . | chomp | replaceRE "\n" " " }} +: `{{ $name }}` +{{ else }}`{{ $name }}` +{{ end -}} +: `{{ .Type }}`{{ with .Scheme }} `{{ . }}`{{ end }}{{ with .In }} in `{{ . }}`{{ end }}{{ with .Name }} (`{{ . }}`){{ end }}{{ with .BearerFormat }} `{{ . }}`{{ end }}{{ with .OpenIdConnectUrl }}, discovery: [{{ . }}]({{ . }}){{ end }} + {{- end }} +{{ end -}} +{{- end -}} + + +{{- /* ========== Operations (grouped by tag) ========== */ -}} +{{- define "openapi-operations" -}} +{{- $spec := .spec -}} +{{- $methods := slice "GET" "POST" "PUT" "PATCH" "DELETE" "HEAD" "OPTIONS" "TRACE" -}} +{{- $locations := slice + (dict "in" "path" "label" "Path parameters") + (dict "in" "query" "label" "Query parameters") + (dict "in" "header" "label" "Header parameters") + (dict "in" "cookie" "label" "Cookie parameters") -}} + +{{- /* Bucket each operation under every tag it declares */ -}} +{{- $byTag := dict -}} +{{- range $path, $pathItem := $spec.Paths.Map -}} + {{- range $method := $methods -}} + {{- with index $pathItem.Operations $method -}} + {{- $tags := .Tags -}} + {{- if not $tags }}{{ $tags = slice "API" }}{{ end -}} + {{- $entry := dict "path" $path "method" $method "operation" . "pathItem" $pathItem -}} + {{- range $tag := $tags -}} + {{- $bucket := index $byTag $tag | default slice -}} + {{- $byTag = merge $byTag (dict $tag ($bucket | append $entry)) -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end }} + +{{ range $tag, $entries := $byTag }} + +## {{ $tag | title }} + +{{ range $entry := $entries }} +{{ template "openapi-operation" (dict "spec" $spec "path" $entry.path "method" $entry.method "operation" $entry.operation "pathItem" $entry.pathItem "locations" $locations) }} +{{- end -}} +{{- end }} +{{- end -}} + + +{{- /* ========== Operation ========== */ -}} +{{- define "openapi-operation" -}} +{{- $spec := .spec -}} +{{- $path := .path -}} +{{- $method := .method -}} +{{- $operation := .operation -}} +{{- $pathItem := .pathItem -}} +{{- $locations := .locations }} + +{{- $title := or $operation.Summary (printf "`%s %s`" (upper $method) $path) -}} +{{- if $operation.Deprecated }}{{ $title = printf "~~%s~~" $title }}{{ end }} + +### {{ $title }}{{ with $operation.OperationID }} {id="op-{{ anchorize . }}"}{{ end }} + +{{ with $operation.Description }}{{ . }} + +{{ end -}} +{{- with $operation.ExternalDocs }}[{{ with .Description }}{{ . | chomp | replaceRE "\n" " " }}{{ else }}{{ .URL }}{{ end }}]({{ .URL }}) + +{{ end -}} + +{{- $security := $operation.Security -}} +{{- if not $security }}{{ $security = $spec.Security }}{{ end }} + +{{ $requirementStrings := slice -}} +{{- range $requirement := $security -}} + {{- $schemeLinks := slice -}} + {{- range $schemeName, $_ := $requirement -}} + {{- $schemeLinks = $schemeLinks | append (printf "[%s](#security)" $schemeName) -}} + {{- end -}} + {{- if $schemeLinks }}{{ $requirementStrings = $requirementStrings | append (delimit $schemeLinks " and ") }}{{ end -}} +{{- end }} + +#### Request{{ if $requirementStrings }} (requires {{ delimit $requirementStrings ", " }}){{ end }} + +```tpl +{{ upper $method }} {{ $path }} +``` + + +{{- /* Dedup: operation-level overrides path-level by name+in */ -}} +{{- $opKeys := dict -}} +{{- range $operation.Parameters }}{{ with .Value }}{{ $opKeys = merge $opKeys (dict (printf "%s:%s" .In .Name) true) }}{{ end }}{{ end -}} +{{- $allParams := slice -}} +{{- range $pp := $pathItem.Parameters }}{{ with $pp.Value }}{{ if not (isset $opKeys (printf "%s:%s" .In .Name)) }}{{ $allParams = $allParams | append $pp }}{{ end }}{{ end }}{{ end -}} +{{- $allParams = append $allParams $operation.Parameters -}} +{{ template "openapi-parameters" (dict "allParams" $allParams "locations" $locations) }} +{{- with $operation.RequestBody }}{{ with .Value -}} + {{ template "openapi-request-body" (dict "requestBody" .) }} +{{- end }}{{ end }} +{{- with $operation.Responses -}} + {{ template "openapi-responses" (dict "responses" .) }} +{{- end -}} +{{- end -}} + + +{{- /* ========== Parameters ========== */ -}} +{{- define "openapi-parameters" -}} + {{- $allParams := .allParams -}} + {{- range .locations -}} + {{- $location := .in -}} + {{- $params := slice -}} + {{- range $allParams -}} + {{- with .Value }}{{ if eq .In $location }}{{ $params = $params | append . }}{{ end }}{{ end -}} + {{- end }} + {{- if $params }} + +#### {{ .label }} + +| Parameter name | Value | Description | Additional | +|----------------|-------|-------------|------------| +{{- range $params }} +| `{{ .Name }}` | {{ with .Schema }}{{ template "openapi-type" . }}{{ end }} | {{ with .Description }}{{ . | chomp | replaceRE "\n" " " }}{{ end }} | {{ template "openapi-param-extras" . }} | +{{- end }} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "openapi-param-extras" -}} + {{- $bits := slice -}} + {{- if .Required }}{{ $bits = $bits | append "Required" }}{{ end -}} + {{- if .Deprecated }}{{ $bits = $bits | append "Deprecated" }}{{ end -}} + {{- $example := .Example -}} + {{- $default := "" -}} + {{- with .Schema }}{{ with .Value -}} + {{- with .Default }}{{ $default = . }}{{ end -}} + {{- if not $example }}{{ with .Example }}{{ $example = . }}{{ end }}{{ end -}} + {{- end }}{{ end -}} + {{- with $default }}{{ $bits = $bits | append (printf "Default: `%v`" .) }}{{ end -}} + {{- with $example }}{{ $bits = $bits | append (printf "Example: `%v`" .) }}{{ end -}} + {{- delimit $bits ", " -}} +{{- end -}} + + +{{- /* ========== Request body ========== */ -}} +{{- define "openapi-request-body" -}} + {{- with .requestBody -}} + {{- if or .Description .Content }} + +#### Request body{{ if .Required }} (required){{ end }} + +{{ with .Description }}{{ . }}{{ end }} +{{ range $mediaType, $obj := .Content }} +{{ template "openapi-media-examples" (dict "mediaType" $mediaType "examples" $obj.Examples "example" $obj.Example) }} +{{- end -}} +{{ template "openapi-schema-summary" (dict "content" .Content) }} + {{- end -}} + {{- end -}} +{{- end -}} + + +{{- /* ========== Responses ========== */ -}} +{{- define "openapi-responses" -}} + {{- $responses := .responses -}} + {{- with $responses }} + +#### Responses + +{{ range $code, $codeRef := .Map -}} + {{- with $codeRef.Value }} +`{{ $code }}`{{ with .Description }}: {{ . | chomp | replaceRE "\n" " " }}{{ end }} +{{ with .Content }}{{ range $mediaType, $obj := . }} +{{ template "openapi-media-examples" (dict "mediaType" $mediaType "examples" $obj.Examples "example" $obj.Example) }} +{{- end -}} +{{ template "openapi-schema-summary" (dict "content" .) }} +{{ end -}} +{{ template "openapi-headers-detail" (dict "code" $code "headers" .Headers) }} + {{- end }} +{{ end -}} + {{- end -}} +{{- end -}} + +{{- define "openapi-headers-detail" -}} + {{- $code := .code -}} + {{- with .headers }} + +`{{ $code }}` response headers: + +| Name | Type | Description | +|------|------|-------------| + {{- range $name, $hdrRef := . }} + {{- with $hdrRef.Value }} +| `{{ $name }}` | {{ with .Schema }}{{ template "openapi-type" . }}{{ end }} | {{ with .Description }}{{ . | chomp | replaceRE "\n" " " }}{{ end }} | + {{- end -}} + {{- end }} + + {{- end -}} +{{- end -}} + + +{{- /* ========== Media examples (author-provided only) ========== */ -}} +{{- define "openapi-media-examples" -}} + {{- $mediaType := .mediaType -}} + {{- $examples := .examples -}} + {{- $example := .example -}} + {{- if $examples -}} + {{- range $name, $exRef := $examples }} + {{- with $exRef.Value }} + +{{ with .Summary }}{{ . }}{{ else }}{{ $name }}{{ end }}{{ with .Description }} - {{ . | chomp | replaceRE "\n" " " }}{{ end }} +```jsonc {filename="{{ $mediaType }}"} +{{ jsonify (dict "indent" " ") .Value | safeHTML }} +``` + {{- end -}} + {{- end -}} + {{- else if $example }} +```jsonc {filename="{{ $mediaType }}"} +{{ jsonify (dict "indent" " ") $example | safeHTML }} +``` + {{- end -}} +{{- end -}} + + +{{- /* ========== Schema summary (type caption + embedded properties table) ========== */ -}} +{{- define "openapi-schema-summary" -}} + {{- $schemaRef := "" -}} + {{- range $mt, $obj := .content -}} + {{- if not $schemaRef }}{{ with $obj.Schema }}{{ $schemaRef = . }}{{ end }}{{ end -}} + {{- end -}} + {{- with $schemaRef }} +Schema: {{ template "openapi-type" . }} +{{- /* Unwrap arrays so `array of Pet` embeds the Pet table */ -}} +{{- $value := $schemaRef.Value -}} +{{- $itemRef := $schemaRef -}} +{{- if and $value (not $value.Properties) $value.Items -}} + {{- $itemRef = $value.Items -}} +{{- end -}} +{{- with $itemRef.Value -}} + {{- if or .Properties .AllOf }} + +{{ template "openapi-properties-table" (dict "schema" $itemRef) }} + {{- end -}} +{{- end -}} + {{- end -}} +{{- end -}} + + +{{- /* ========== Models ========== */ -}} +{{- define "openapi-models" -}} +{{- $schemas := .schemas }} + +## Models + +{{ range $name, $schemaRef := $schemas -}} + {{- with $schemaRef.Value }} + +### {{ $name }} {id="model-{{ $name | anchorize }}"} + +{{ with .Description }}{{ . | chomp }} + +{{ end -}} +{{- $parents := slice -}} +{{- range .AllOf }}{{ if .Ref }}{{ $parents = $parents | append . }}{{ end }}{{ end -}} +{{- with $parents }}Inherits from {{ range $i, $p := . }}{{ if $i }}, {{ end }}{{ template "openapi-schema-link" $p }}{{ end }}. + +{{ end -}} +{{- if or .Properties .AllOf -}} +{{ template "openapi-properties-table" (dict "schema" $schemaRef) }} +{{- else -}} +Type: {{ template "openapi-type" $schemaRef }}{{ with .Enum }} (enum: {{ range $i, $v := . }}{{ if $i }}, {{ end }}`{{ $v }}`{{ end }}){{ end }} +{{- end }} + {{- end }} +{{ end -}} +{{- end -}} + + +{{- /* ========== Properties table ========== */ -}} +{{- define "openapi-properties-table" -}} + {{- $schemaRef := .schema -}} + {{- with $schemaRef.Value -}} + {{- $props := dict -}} + {{- $required := slice -}} + {{- with .Properties }}{{ $props = merge $props . }}{{ end -}} + {{- with .Required }}{{ $required = $required | append . }}{{ end -}} + {{- range .AllOf -}} + {{- if not .Ref -}} + {{- with .Value -}} + {{- with .Properties }}{{ $props = merge $props . }}{{ end -}} + {{- with .Required }}{{ $required = $required | append . }}{{ end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- if $props }} + +| Property | Type | Required | Description | +|----------|------|----------|-------------| + {{- range $name, $prop := $props -}} + {{- $isRequired := in $required $name }} +| `{{ $name }}` | {{ template "openapi-type" $prop }} | {{ if $isRequired }}yes{{ else }}no{{ end }} | {{ with $prop.Value.Description }}{{ . | chomp | replaceRE "\n" " " }}{{ end }} | + {{- end }} + {{- end -}} + {{- end -}} +{{- end -}} + + +{{- /* ========== Type cell (handles ref, array, primitive) ========== */ -}} +{{- define "openapi-type" -}} + {{- if .Ref -}} + {{- template "openapi-schema-link" . -}} + {{- else -}} + {{- with .Value -}} + {{- $t := "" -}}{{- with .Type }}{{ with .Slice }}{{ $t = index . 0 }}{{ end }}{{ end -}} + {{- if eq $t "array" -}} + array of {{ template "openapi-type" .Items }} + {{- else if $t -}} + {{ $t }}{{ with .Format }} ({{ . }}){{ end }} + {{- else if .Enum -}} + enum + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* ========== Schema link (#model-name) ========== */ -}} +{{- define "openapi-schema-link" -}} + {{- $parts := split .Ref "/" -}} + {{- $name := index $parts (sub (len $parts) 1) -}} +[{{ $name }}](#model-{{ $name | anchorize }}) +{{- end -}}