You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
matrix-spec/layouts/partials/openapi/render-object-table.html

323 lines
11 KiB
HTML

{{/*
Render a table listing the properties of an object, given:
* `title`: optional caption for the table
* `anchor`: optional HTML element id for the table
* `properties`: optional dictionary of the properties to list, each given as:
`property_name` : `property_data`
* `additionalProperties`: a JSON Schema for additional properties on the
object.
* `patternProperties`: optional dictionary for properties with names adhering
to a regex pattern. A map from regex pattern to JSON Schema.
* `required`: optional array containing the names of required properties.
In some cases (such as response body specifications) this isn't used, and
instead properties have a `required` boolean attribute. We support this too.
*/}}
{{ $title := .title }}
{{ $properties := .properties}}
{{ $required := .required}}
{{ if $properties }}
<table{{ if .anchor }} id="{{ .anchor }}"{{ end }} class="object-table">
{{ with $title }}
<caption>{{ . }}</caption>
{{ end }}
<thead>
<tr>
<th class="col-name">Name</th>
<th class="col-type">Type</th>
<th class="col-description">Description</th>
</tr>
</thead>
{{ range $property_name, $property := $properties }}
{{/*
Handle two ways of indicating "required", one for simple parameters,
the other for request and response body objects.
*/}}
{{ $required := cond (or (in $required $property_name) ( eq $property.required true )) true false }}
<tr>
<td><code>{{ $property_name }}</code></td>
<td><code>{{ partial "partials/property-type" $property | safeHTML }}</code></td>
<td>{{ partial "partials/property-description" (dict "property" $property "required" $required) }}</td>
</tr>
{{ end }}
{{/*
If the object has additional properties *as well as* regular properties, we add a special row to the table.
Note that, per https://json-schema.org/draft/2020-12/json-schema-core#name-boolean-json-schemas, JSON schemas
can be a simple "true" or "false" as well as the more normal object.
`additionalProperties: true` is pretty much the default for Matrix (it means: "you're allowed to include random
unspecced properties in your object"), so nothing to do here.
`additionalProperties: false` means "you're not allowed to include any unspecced properties in your object". We
may want to consider how to display that; for now we just ignore it.
TODO: support `patternProperties` here.
*/}}
{{ if reflect.IsMap .additionalProperties }}
<tr>
<td>&lt;Other properties&gt;</td>
<td><code>{{ partial "partials/property-type" .additionalProperties | safeHTML }}</code></td>
<td>{{ partial "partials/property-description" (dict "property" .additionalProperties) }}</td>
</tr>
{{ end }}
</table>
{{ else if (or .additionalProperties .patternProperties) }}
{{/*
A special format of table for objects which only have additionalProperties or patternProperties.
This is only ever used for top-level objects. Nested objects in this situation are just shown
as rows within their parent object, and don't get their own table. (They are filtered out in
resolve-additional-types.)
*/}}
<table{{ if .anchor }} id="{{ .anchor }}"{{ end }} class="object-table">
{{ with $title }}
<caption>{{ . }}</caption>
{{ end }}
<thead>
<tr>
<th class="col-type">Type</th>
<th class="col-description">Description</th>
</tr>
</thead>
{{ $property := . }}
<tr>
<td><code>{{ partial "partials/property-type" $property | safeHTML }}</code></td>
<td>{{ partial "partials/property-description" (dict "property" $property) }}</td>
</tr>
</table>
{{ end }}
{{/*
Computes the type to display for a property's schema, given:
* `type`: optional string or array of strings for the type(s) of the property
* `title`: optional string for the title of the property
* `oneOf`: optional array of dictionaries describing the different formats
that the property can have
* `properties`: if the type is an object, optional dictionary for
well-defined properties, each given as: `property_name` : `property_data`
* `additionalProperties`: if the type is an object, optional dictionary for
properties with undefined names
* `patternProperties`: if the type is an object, optional dictionary for
properties with names adhering to a regex pattern
* `items`: if the type is an array, array of dictionaries describing the
format of the array's items
* `anchor`: optional HTML element id for the target type, which will be used to link to it.
* `format`: optional string for the format of the type, used for strings.
*/}}
{{ define "partials/property-type" }}
{{ $type := "" }}
{{ if eq .type "object" }}
{{/* Resolve the type or title of the object */}}
{{ $type = partial "object-type-or-title" . }}
{{ else if eq .type "array"}}
{{/*
If the property is an array, indicate this with square brackets,
like `[type]`.
*/}}
{{ $items := .items }}
{{ $inner_type := partial "property-type" $items }}
{{ $type = delimit (slice "[" $inner_type "]") "" }}
{{ else if eq .type "string" }}
{{ $type = "string" }}
{{/* If the string uses a known format, use it. */}}
{{ with .format }}
{{ with partial "custom-format" . }}
{{ $type = . }}
{{ end }}
{{ end }}
{{ else if or (reflect.IsSlice .type) .oneOf }}
{{/*
It's legal to specify an array of types.
There are two ways to do that:
- Use an array of strings.
- Use oneOf, with items having a schema.
Join them together in that case, like `type|other_type`.
*/}}
{{ $types := slice }}
{{ if .oneOf }}
{{ range .oneOf }}
{{ $types = $types | append (partial "property-type" .) }}
{{ end }}
{{ else }}
{{ range .type }}
{{ $types = $types | append (htmlEscape .) }}
{{ end }}
{{ end }}
{{ $type = delimit $types "|" }}
{{ else }}
{{/* A simple type like integer or boolean */}}
{{ $type = (htmlEscape .type) }}
{{ end }}
{{ return $type }}
{{ end }}
{{/*
Computes the type to display for an object property's schema, given:
* `type`: string equal to "object"
* `title`: optional string for the title of the object property
* `properties`: optional dictionary for well-defined properties, each given
as: `property_name` : `property_data`
* `additionalProperties`: optional dictionary for properties with undefined
names
* `patternProperties`: optional dictionary for properties with names
adhering to a regex pattern
* `anchor`: optional HTML element id for the target type, which will be used to link to it.
*/}}
{{ define "partials/object-type-or-title" }}
{{ $type := "object" }}
{{ if .properties }}
{{/*
The object has its own (regular) properties, so we will make a
separate table for it. Refer to it using its title, if it has one.
*/}}
{{ if .title }}
{{ $type = .title | htmlEscape }}
{{ if .anchor }}
{{ $type = printf "<a href=\"#%s\">%s</a>" (htmlEscape .anchor) $type }}
{{ end }}
{{ end }}
{{ else if reflect.IsMap .additionalProperties }}
{{/*
If the property uses `additionalProperties` to describe its
internal structure, handle this with a bit of recursion
*/}}
{{ $type = delimit (slice "{string: " (partial "property-type" .additionalProperties) "}" ) "" }}
{{ else if reflect.IsMap .patternProperties }}
{{/*
If the property uses `patternProperties` to describe its
internal structure, handle this with a bit of recursion.
Types are grouped by pattern format. Note that we ignore
patterns without a format as the current definitions
always have a single pattern, but we might need to handle
them later to differentiate schemas according to patterns.
*/}}
{{/*
Construct a map from format ID to the type string of the format.
*/}}
{{ $formatMap := newScratch }}
{{ range $pattern, $schema := .patternProperties }}
{{ $formatId := or (index $schema "x-pattern-format") "string" }}
{{ if $formatMap.Get $formatId }}
{{ errorf "'%s' pattern format is defined more than once for the same property" $formatId }}
{{ end }}
{{ $formatMap.Set $formatId (partial "property-type" $schema) }}
{{ end }}
{{/* First generate the type string for each format. */}}
{{ $types := slice }}
{{ range $formatId, $formatType := $formatMap.Values }}
{{ $formatKey := "string" }}
{{ if ne $formatId "string" }}
{{ with partial "custom-format" $formatId }}
{{ $formatKey = . }}
{{ else }}
{{ errorf "Unsupported value for `x-pattern-format`: %s" $formatId }}
{{ end }}
{{ end }}
{{ $formatString := printf "{%s: %s}" $formatKey $formatType }}
{{ $types = $types | append $formatString }}
{{ end }}
{{/* Then join all the formats. */}}
{{ $type = delimit $types "|" }}
{{ else if .title }}
{{/*
No properties, so there won't be a separate table. We use the title
anyway, because showing the title (like `EventFilter`) is better
than showing `object`.
*/}}
{{ $type = .title | htmlEscape }}
{{ end }}
{{ return $type }}
{{ end }}
{{/*
Computes the description to display for a property, given:
* `required`: boolean indicating whether this property is required.
* `property`: dictionary describing the property's data, with these fields:
* `description`: string describing the property
* `enum`: optional array indicating the accepted values for the property
* `x-addedInMatrixVersion`: optional string indicating in which Matrix
spec version this property was added.
* `x-changedInMatrixVersion`: optional string indicating in which Matrix
spec version this property was last changed.
*/}}
{{ define "partials/property-description" }}
{{ if .required }}<strong>Required: </strong>{{end -}}
{{ .property.description | markdownify -}}
{{ if .property.enum }}<p>One of: <code>[{{ delimit .property.enum ", " }}]</code>.</p>{{ end -}}
{{ if (index .property "x-addedInMatrixVersion") }}{{ partial "added-in" (dict "v" (index .property "x-addedInMatrixVersion")) }}{{ end -}}
{{ if (index .property "x-changedInMatrixVersion") }}{{ partial "changed-in" (dict "changes_dict" (index .property "x-changedInMatrixVersion")) }}{{ end -}}
{{ end }}
{{/*
Computes the type to display for a string format, given the identifier of
the format as a string.
*/}}
{{ define "partials/custom-format" }}
{{ $customFormat := "" }}
{{ with index site.Data "custom-formats" . }}
{{ $customFormat = printf "<a href=\"%s\">%s</a>" (htmlEscape .url) (htmlEscape .title) }}
{{ end }}
{{ return $customFormat }}
{{ end }}