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

This commit is contained in:
Richard van der Hoff 2024-02-28 10:39:08 +00:00 committed by GitHub
parent afda8b8f74
commit 976ebdca2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 208 additions and 126 deletions

View file

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

View file

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

View file

@ -81,7 +81,7 @@
Computes the type to display for a property, given: Computes the type to display for a property, given:
* `type`: string or array of strings for the type(s) of the property * `type`: string or array of strings for the type(s) of the property
* `title`: optional string for the title of the property * `title`: optional string for the title of the property
* `oneOf`: optional array of dictionaries describing the different formats * `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 * `items`: if the type is an array, array of dictionaries describing the
format of the array's items 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" }} {{ define "partials/property-type" }}
{{ $type := .type }} {{ $type := .type }}
@ -123,7 +126,7 @@
Computes the type to display for a property's schema, given: Computes the type to display for a property's schema, given:
* `type`: string or array of strings for the type(s) of the property * `type`: string or array of strings for the type(s) of the property
* `title`: optional string for the title of the property * `title`: optional string for the title of the property
* `oneOf`: optional array of dictionaries describing the different formats * `oneOf`: optional array of dictionaries describing the different formats
@ -134,7 +137,9 @@
* `patternProperties`: optional dictionary for properties with names * `patternProperties`: optional dictionary for properties with names
adhering to a regex pattern 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. The title has a higher priority than anything else.
*/}} */}}
{{ define "partials/type-or-title" }} {{ define "partials/type-or-title" }}
@ -145,6 +150,9 @@
This means we can write things like `EventFilter` rather than `object`. This means we can write things like `EventFilter` rather than `object`.
*/}} */}}
{{ $type = .title }} {{ $type = .title }}
{{ if .anchor }}
{{ $type = printf "<a href=\"#%s\">%s</a>" (htmlEscape .anchor) (htmlEscape $type) | safeHTML }}
{{ end }}
{{ else if reflect.IsMap .additionalProperties }} {{ else if reflect.IsMap .additionalProperties }}
{{/* {{/*
If the property uses `additionalProperties` to describe its If the property uses `additionalProperties` to describe its
@ -205,9 +213,9 @@
* `property`: dictionary describing the property's data, with these fields: * `property`: dictionary describing the property's data, with these fields:
* `description`: string describing the property * `description`: string describing the property
* `enum`: optional array indicating the accepted values for the property * `enum`: optional array indicating the accepted values for the property
* `x-addedInMatrixVersion`: optional string indicating in which Matrix * `x-addedInMatrixVersion`: optional string indicating in which Matrix
spec version this property was added. spec version this property was added.
@ -220,4 +228,4 @@
{{ if .property.enum }}<p>One of: <code>[{{ delimit .property.enum ", " }}]</code>.</p>{{ end -}} {{ 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-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 -}} {{ if (index .property "x-changedInMatrixVersion") }}{{ partial "changed-in" (dict "changes_dict" (index .property "x-changedInMatrixVersion")) }}{{ end -}}
{{ end }} {{ end }}