/* 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 "") ) }} {{ 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 }} {{ 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 }} /* 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 }} /* 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 }} /* 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 }} {{ 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 }} /* 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.oneOf[%d]" $name $idx) ) }} {{ $all_objects = $res.objects }} {{ end }} {{ end }} {{ return (dict "objects" (uniq $all_objects) "schema" $this_object ) }} {{ end }} /* 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 := .name }} {{ $all_objects := .all_objects }} {{ if not (reflect.IsMap .this_object) }} {{ 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 schemas, so we have to call it again. */ {{ $this_object := partial "json-schema/resolve-allof" .this_object }} {{ $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 (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. */ {{ define "partials/clean-object" }} {{ return (dict "title" .title "properties" .properties "required" .required "enum" .enum "anchor" .anchor) }} {{ end }}