Skip to content

Commit

Permalink
Include information about additionalProperties in object tables (#1798)
Browse files Browse the repository at this point in the history
Currently, if we have an object which has additionalProperties in addition to properties, that information gets lost. This PR seeks to address that.
  • Loading branch information
richvdh authored May 2, 2024
1 parent eea3dfa commit 48f4c49
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 20 deletions.
1 change: 1 addition & 0 deletions changelogs/internal/newsfragments/1798.clarification
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show information about "Additional Properties" in object tables.
1 change: 0 additions & 1 deletion data/api/identity/v2_terms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ paths:
might be and could be "alpha", semantically versioned, or arbitrary.
required:
- version
# TODO: TravisR - Make this render
additionalProperties:
type: object
title: Internationalised Policy
Expand Down
45 changes: 31 additions & 14 deletions layouts/partials/json-schema/resolve-additional-types.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
*
* * title
* * properties
* * additionalProperties
* * patternProperties
* * required
* * enum
* * anchor
Expand All @@ -45,27 +47,31 @@
"schema" .schema
"anchor_base" .anchor_base
"name" (.name | default .schema.title | default "<untitled object>")
"top_level" true
) }}
{{ 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.
* Takes the same inputs as resolve-additional-types itself, except:
* * `name` is mandatory.
* * `top_level`, if set, indicates that this is the top-level object.
*
* 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`.
* * `schema`: An updated copy of the `schema` input (eg, an `anchor` may be added).
* If the input `schema` was itself an object that we should create a table for,
* 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 }}
{{ $top_level := .top_level }}

{{ $all_objects := slice }}

{{ if eq $this_object.type "object" }}
Expand Down Expand Up @@ -126,12 +132,24 @@
{{ $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 }}
/* We'll want to create a table for `$this_object` itself if either:
*
* - The object has some regular properties (not just patternProperties or additionalProperties), or:
* - It is the top-level object. (We use a special format of table for top-level objects that
* only have patternProperties or additionalProperties)
*
* In those cases, prepend the updated schema for the top-level object onto the $all_objects array.
*
* We think about this last so that we can take advantage of any updates to the schema that happened
* above (in particular, addition of `anchor` attributes).
*/
{{ if or $this_object.properties $top_level }}
{{ $tmp := slice $this_object }}
{{ if $all_objects }}
{{ $tmp = $tmp | append $all_objects }}
{{ end }}
{{ $all_objects = $tmp }}
{{ end }}
{{ $all_objects = $tmp }}
{{ end }}

{{ if eq $this_object.type "array" }}
Expand Down Expand Up @@ -198,8 +216,7 @@
*
* 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).
* * `schema`: An updated copy of the `schema` input (eg, an `anchor` may be added).
*/
{{ define "partials/get-additional-objects" }}
/* .name is the name of the object for logging purposes */
Expand All @@ -211,7 +228,7 @@
{{ errorf "Invalid call to partials/get-additional-objects: %s is not a map" $name .this_object }}
{{ end }}

{{ $res := partial "resolve-additional-types-inner" (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 "top_level" false) }}
{{ range $res.objects }}
{{ $all_objects = $all_objects | append (partial "clean-object" .) }}
{{ end }}
Expand All @@ -226,5 +243,5 @@
* 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) }}
{{ return (dict "title" .title "properties" .properties "additionalProperties" .additionalProperties "patternProperties" .patternProperties "required" .required "enum" .enum "anchor" .anchor) }}
{{ end }}
40 changes: 35 additions & 5 deletions layouts/partials/openapi/render-object-table.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
* `properties`: optional dictionary of the properties to list, each given as:
`property_name` : `property_data`

* `additionalProperties`: optional dictionary for properties with undefined
names, in the same format as `property_data`
* `additionalProperties`: a JSON Schema for additional properties on the
object.

* `patternProperties`: optional dictionary for properties with names adhering
to a regex pattern, in the same format as `property_data`
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
Expand All @@ -26,7 +26,6 @@
{{ $required := .required}}

{{ if $properties }}

<table{{ if .anchor }} id="{{ .anchor }}"{{ end }} class="object-table">
{{ with $title }}
<caption>{{ . }}</caption>
Expand All @@ -52,9 +51,40 @@

{{ 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;</code></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>
Expand Down Expand Up @@ -116,7 +146,7 @@
{{ 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.
Expand Down

0 comments on commit 48f4c49

Please sign in to comment.