templatags: rendering of $id/$ref in jsonschema (#81643)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Benjamin Dauvergne 2023-09-26 17:14:47 +02:00
parent 8266740b52
commit a9f2956db7
2 changed files with 51 additions and 4 deletions

View File

@ -110,7 +110,10 @@ def censor(string):
return re.sub(r'://([^/]*):([^/]*?)@', r'://\1:***@', string)
def render_json_schema(schema):
def render_json_schema(schema, anchor_map=None):
if anchor_map is None:
anchor_map = {}
if not isinstance(schema, dict):
if schema is True:
return mark_safe('<em>%s</em>') % _('always valid')
@ -133,6 +136,11 @@ def render_json_schema(schema):
def html_type(s):
return '<span class="type">%s</span>' % s
def to_id(ref):
_ref = ref.lstrip('#')
_id = id(anchor_map.get(_ref))
return f'schema-object-{_ref}-{_id}'
if 'anyOf' in schema:
return many_of('anyOf', schema['anyOf'])
@ -145,10 +153,17 @@ def render_json_schema(schema):
original_schema = schema
schema = schema.copy()
schema.pop('$schema', None)
schema.pop('$id', None)
_anchor = schema.pop('$anchor', None)
if _anchor:
anchor_map.setdefault(_anchor, original_schema)
title = schema.pop('title', None)
description = schema.pop('description', None)
typ = schema.pop('type', None)
_ref = schema.pop('$ref', None)
if _ref and _ref.startswith('#'):
target_schema = anchor_map.get(_ref[1:], {})
target_title = target_schema.get('title') or target_schema.get('description') or 'referenced schema'
return format_html('<a href="#{}">{}</a>', to_id(_ref), target_title)
if typ == 'null':
return mark_safe(html_type('null'))
if typ == 'string':
@ -181,10 +196,12 @@ def render_json_schema(schema):
if typ == 'array':
s = html_type('array') + ' '
if 'items' in schema:
s += render_json_schema(schema['items'])
s += render_json_schema(schema['items'], anchor_map)
return mark_safe(s)
if typ == 'object':
s = html_type('object')
if _anchor:
s += f'<a id="{to_id(_anchor)}"></a>'
unflatten = schema.pop('unflatten', False)
merge_extra = schema.pop('merge_extra', False)
properties = schema.pop('properties', {})
@ -211,6 +228,9 @@ def render_json_schema(schema):
def render_property_schema(key, html, sub):
nonlocal s
_anchor = sub.get('$anchor', None)
if _anchor:
anchor_map.setdefault(_anchor, sub.copy())
required = key in required_keys
sub_description = sub.pop('description', '')
sub_title = sub.pop('title', '')
@ -226,7 +246,7 @@ def render_json_schema(schema):
if sub_title or '\n' in sub_description:
s += format_html('\n<p class="description">{}</p>', sub_description)
if sub:
s += format_html('\n{0}', render_json_schema(sub))
s += format_html('\n{0}', render_json_schema(sub, anchor_map))
s += '</li>'
if properties or pattern_properties:

View File

@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import inspect
import re
from django.apps import apps
from django.utils import translation
@ -99,3 +100,29 @@ def test_render_oneof_property_required():
],
}
assert "<b>oneOf</b> [ <em>required 'a'</em> | <em>required 'b'</em> ]" in render_json_schema(schema)
def test_render_json_schema_anchor():
SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'required': ['foo'],
'additionalProperties': False,
'properties': {
'foo': {
'type': 'object',
'$anchor': 'foo',
'title': 'foo object',
'properties': {'a': {'type': 'string'}},
},
'zorglub': {
'$ref': '#foo',
},
},
}
# Check that no unicode crash occurs
with translation.override('fr'):
fragment = render_json_schema(SCHEMA)
match = re.search(r'id="(schema-object-foo-[^"]+)"', fragment)
assert match
assert f'href="#{match.group(1)}">foo object</a>' in fragment