Update HTML templates to link to object definitions (#1724)

pull/1740/head
Richard van der Hoff 2 months ago committed by GitHub
parent afda8b8f74
commit 976ebdca2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1 @@
Update HTML templates to include links to object schema definitions.

@ -1,133 +1,207 @@
{{/*
Finds and returns all objects, including nested ones, given a dict containing:
* `schema`: a JSON schema object
* `anchor_base`: a prefix to add to the HTML anchors generated for each object. If nil, no anchors are generated.
* `name`: optionally, a name to use for this object in error/warning messages. If left unset,
the object's `title` property is used (if present).
This template finds all nested objects inside `schema`.
Assumes that "resolve-refs" and "resolve-allof" has already been called on the
input schema.
Returns an array of all the objects found. The first object keeps all its properties. For all other objects, the following properties are returned:
* title
* properties
* required
* enum
* anchor: a string suitable for using as an html anchor for this object (if `anchor_base` was set, and the object has a title)
/* Finds and returns all schema definitions for objects, including subschemas,
* in a JSON schema.
*
* The input should be a dict containing:
*
* * `schema`: a dict containing a JSON schema.
* * `anchor_base`: a prefix to add to the HTML anchors generated for each
* object. If nil, no anchors are generated.
* * `name`: optionally, a name to use for this schema in error/warning
* messages. If left unset, the schema's `title` property is used (if
* present).
*
* Assumes that "resolve-refs" and "resolve-allof" has already been called on
* the input schema.
*
* Returns an array of all the JSON schema definitions with `type: object` found
* by recursing through `schema` and inspecting any subschemas, and including
* `schema` itself.
*
* The returned entries are based on the JSON schema definitions found by
* recursing through the input `schema`, with the following differences:
*
* * `allOf` references are expanded. (Although this partial requires that
* `resolve-allof` is called on the top-level `schema` beforehand,
* `resolve-allof` doesn't recurse down to subschemas).
*
* * If `anchor_base` is set, each object with a `title` and `properties`
* is given an `anchor`, which is a string suitable for using as an html
* anchor for that object schema.
*
* * With the *exception* of the top-level `schema` (if it is an object),
* properties outside the following list are removed:
*
* * title
* * properties
* * required
* * enum
* * anchor
*
* In particular, this means that examples are removed (typically examples
* are not helpful for subschemas), which means that duplicate schemas can
* be successfully eliminated.
*
* The returned array contains only unique objects (ie, if the same object is
* referenced twice within `schema`, it will only be returned once).
*/
{{ $res := partial "resolve-additional-types-inner" (dict
"schema" .schema
"anchor_base" .anchor_base
"name" (.name | default .schema.title | default "<untitled object>")
) }}
{{ return $res.objects }}
/*
* A helper for the resolve-additional-types partial.
*
* Takes the same inputs as resolve-additional-types itself, except that `name` is mandatory.
*
* Returns a dict containing:
*
* * `objects`: The array of object schema definitions.
*
* * `schema`: An updated copy of the `schema` input (eg, nested `allOf`
* entries are expanded, and an `anchor` may be added). If the input
* `schema` was itself an object, this will be the same as the first entry
* in `objects`.
*/
{{ define "partials/resolve-additional-types-inner" }}
{{ $this_object := .schema }}
{{ $anchor_base := .anchor_base }}
{{ $name := .name }}
{{ $all_objects := slice }}
Note that the returned array contains only unique objects.
{{ if eq $this_object.type "object" }}
/* Give this object an anchor, if it has a name and properties */
{{ if (and $anchor_base $this_object.title $this_object.properties) }}
{{ $this_object = merge $this_object (dict "anchor" (printf "%s_%s" $anchor_base (anchorize $this_object.title))) }}
{{ end }}
*/}}
/* Add any nested objects referenced in this object's `additionalProperties` */
{{ if $this_object.additionalProperties }}
{{ if reflect.IsMap $this_object.additionalProperties }}
{{ $res := partial "get-additional-objects" (dict
"this_object" $this_object.additionalProperties
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.additional" $name)
) }}
{{ $all_objects = $res.objects }}
/* Update the top-level schema with the updated subschemas for the additionalProperties */
{{ $this_object = merge $this_object (dict "additionalProperties" $res.schema) }}
{{ end }}
{{ end }}
{{ $this_object := .schema }}
{{ $anchor_base := .anchor_base }}
{{ $all_objects := slice }}
{{ $name := .name | default $this_object.title | default "<untitled object>" }}
/* Add any nested objects referenced in this object's `patternProperties` */
{{ if $this_object.patternProperties }}
{{ $updated_pattern_properties := dict }}
{{ range $pattern, $object := $this_object.patternProperties}}
{{ $res := partial "get-additional-objects" (dict
"this_object" $object
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.pattern.%s" $name $pattern)
) }}
{{ $all_objects = $res.objects }}
{{ $updated_pattern_properties = merge $updated_pattern_properties (dict $pattern $res.schema) }}
{{ end }}
/* Update the top-level schema with the updated subschemas for the patternProperties */
{{ $this_object = merge $this_object (dict "patternProperties" $updated_pattern_properties) }}
{{ end }}
{{ if eq $this_object.type "object" }}
{{/* give this object an anchor, if it has a name */}}
{{ if (and $anchor_base $this_object.title) }}
{{ $this_object = merge $this_object (dict "anchor" (printf "%s_%s" $anchor_base (anchorize $this_object.title))) }}
{{ end }}
/* Add any nested objects referenced in this object's `properties` */
{{ if $this_object.properties }}
{{ $updated_properties := dict }}
{{ range $key, $property := $this_object.properties}}
{{ $res := partial "get-additional-objects" (dict
"this_object" $property
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.%s" $name $key)
) }}
{{ $all_objects = $res.objects }}
{{ $updated_properties = merge $updated_properties (dict $key $res.schema) }}
{{ end }}
/* Update the top-level schema with the updated subschemas for the regular properties */
{{ $this_object = merge $this_object (dict "properties" $updated_properties) }}
{{ end }}
{{/*
Add the object we were passed into the $all_objects array
*/}}
{{ $all_objects = $all_objects | append $this_object }}
{{/*
Add any nested objects referenced in this object's `additionalProperties`
*/}}
{{ if $this_object.additionalProperties }}
{{ if reflect.IsMap $this_object.additionalProperties }}
{{ $all_objects = partial "get-additional-objects" (dict
"this_object" $this_object.additionalProperties
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.additional" $name)
) }}
/* Finally, prepend the updated schema for the top-level object onto the $all_objects array */
{{ $tmp := slice $this_object }}
{{ if $all_objects }}
{{ $tmp = $tmp | append $all_objects }}
{{ end }}
{{ $all_objects = $tmp }}
{{ end }}
{{/*
Add any nested objects referenced in this object's `patternProperties`
*/}}
{{ if $this_object.patternProperties }}
{{ range $pattern, $object := $this_object.patternProperties}}
{{ $all_objects = partial "get-additional-objects" (dict
"this_object" $object
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.pattern.%s" $name $pattern)
{{ if eq $this_object.type "array" }}
/* Add any nested objects referenced in this object's `items` */
{{ if $this_object.items.anyOf }}
{{ range $idx, $item := $this_object.items.anyOf }}
{{ $res := partial "get-additional-objects" (dict
"this_object" $item
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.items[%d]" $name $idx)
) }}
{{ $all_objects = $res.objects }}
{{ end }}
{{ else if reflect.IsMap $this_object.items}}
{{ $res := partial "get-additional-objects" (dict
"this_object" $this_object.items
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.items" $name)
) }}
{{ $all_objects = $res.objects }}
/* Update the top-level schema with the updated subschema for the items */
{{ $this_object = merge $this_object (dict "items" $res.schema) }}
{{ else }}
{{ errorf "%s is defined as an 'array' but lacks a valid 'items'" $name }}
{{ end }}
{{ end }}
{{/*
Add any nested objects referenced in this object's `properties`
*/}}
{{ range $key, $property := $this_object.properties}}
{{ $all_objects = partial "get-additional-objects" (dict
"this_object" $property
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.%s" $name $key)
) }}
{{ end }}
{{ end }}
{{ if eq $this_object.type "array" }}
{{/*
Add any nested objects referenced in this object's `items`
*/}}
{{ if $this_object.items.anyOf }}
{{ range $idx, $item := $this_object.items.anyOf }}
{{ $all_objects = partial "get-additional-objects" (dict
/* Handle object schemas using the `oneOf` keyword
* (https://json-schema.org/understanding-json-schema/reference/combining.html#oneof)
*/
{{ if $this_object.oneOf }}
{{ range $idx, $item := $this_object.oneOf }}
{{ $res := partial "get-additional-objects" (dict
"this_object" $item
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.items[%d]" $name $idx)
"name" (printf "%s.oneOf[%d]" $name $idx)
) }}
{{ $all_objects = $res.objects }}
{{ end }}
{{ else if reflect.IsMap $this_object.items}}
{{ $all_objects = partial "get-additional-objects" (dict
"this_object" $this_object.items
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.items" $name)
) }}
{{ else }}
{{ errorf "%s is defined as an 'array' but lacks a valid 'items'" $name }}
{{ end }}
{{ end }}
{{/*
Handle object schemas using the `oneOf` keyword
(https://json-schema.org/understanding-json-schema/reference/combining.html#oneof)
*/}}
{{ if $this_object.oneOf }}
{{ range $idx, $item := $this_object.oneOf }}
{{ $all_objects = partial "get-additional-objects" (dict
"this_object" $item
"all_objects" $all_objects
"anchor_base" $anchor_base
"name" (printf "%s.oneOf[%d]" $name $idx)
) }}
{{ end }}
{{ return (dict
"objects" (uniq $all_objects)
"schema" $this_object
) }}
{{ end }}
{{ return uniq $all_objects }}
{{/*
This actually makes the recursive call and adds the returned objects to the array
*/}}
/* This actually makes the recursive call and adds the returned object schema definitions to the array.
*
* Input is a dict containing:
* * `this_object`: a JSON schema object.
* * `name`: a name to use for this object in error/warning messages.
* * `anchor_base`: a prefix to add to the HTML anchors generated for each
* object. If nil, no anchors are generated.
* * `all_objects`: the array of object schema definitions so far.
*
* Returns a dict containing:
* * `objects`: The array of object schema definitions.
* * `schema`: An updated copy of the `schema` input (eg, nested `allOf`
* entries are expanded, and an `anchor` may be added).
*/
{{ define "partials/get-additional-objects" }}
{{/* .name is the name of the object for logging purposes */}}
/* .name is the name of the object for logging purposes */
{{ $name := .name }}
{{ $all_objects := .all_objects }}
@ -136,26 +210,25 @@
{{ errorf "Invalid call to partials/get-additional-objects: %s is not a map" $name .this_object }}
{{ end }}
/* although we expect resolve-allof to be called on the input, resolve-allof does not recurse into
* nested objects, so we have to call it again.
/* Although we expect resolve-allof to be called on the input, resolve-allof does not recurse into
* nested schemas, so we have to call it again.
*/
{{ $this_object := partial "json-schema/resolve-allof" .this_object }}
{{ $more_objects := partial "json-schema/resolve-additional-types" (dict "schema" $this_object "anchor_base" .anchor_base "name" $name) }}
{{/*
As far as I know we don't have something like Array.concat(), so add them one at a time
*/}}
{{ range $more_objects}}
{{ $res := partial "resolve-additional-types-inner" (dict "schema" $this_object "anchor_base" .anchor_base "name" $name) }}
{{ range $res.objects }}
{{ $all_objects = $all_objects | append (partial "clean-object" .) }}
{{ end }}
{{ return $all_objects }}
{{ return (dict
"objects" $all_objects
"schema" $res.schema
) }}
{{ end }}
{{/*
Only copy the bits of the object that we actually care about.
This is needed for uniqify to work - otherwise objects that are the same
but with (for example) different examples will be considered different.
*/}}
/* Only copy the bits of the object that we actually care about.
* This is needed for uniqify to work - otherwise objects that are the same
* but with (for example) different examples will be considered different.
*/
{{ define "partials/clean-object" }}
{{ return (dict "title" .title "properties" .properties "required" .required "enum" .enum "anchor" .anchor) }}
{{ end }}

@ -81,7 +81,7 @@
Computes the type to display for a property, given:
* `type`: 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
@ -95,6 +95,9 @@
* `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.
*/}}
{{ define "partials/property-type" }}
{{ $type := .type }}
@ -123,7 +126,7 @@
Computes the type to display for a property's schema, given:
* `type`: 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
@ -134,7 +137,9 @@
* `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.
The title has a higher priority than anything else.
*/}}
{{ define "partials/type-or-title" }}
@ -145,6 +150,9 @@
This means we can write things like `EventFilter` rather than `object`.
*/}}
{{ $type = .title }}
{{ if .anchor }}
{{ $type = printf "<a href=\"#%s\">%s</a>" (htmlEscape .anchor) (htmlEscape $type) | safeHTML }}
{{ end }}
{{ else if reflect.IsMap .additionalProperties }}
{{/*
If the property uses `additionalProperties` to describe its
@ -205,9 +213,9 @@
* `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.
@ -220,4 +228,4 @@
{{ 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 }}
{{ end }}

Loading…
Cancel
Save