From 93ad047329d22d1927fc05fd86b4c9eaac83c9e6 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 5 Nov 2019 14:53:15 +0100 Subject: [PATCH 03/11] misc: render request body's JSON schema (#35818) --- passerelle/base/templatetags/passerelle.py | 145 ++++++++++++++++++ .../passerelle/manage/service_view.html | 28 +++- passerelle/utils/api.py | 11 ++ 3 files changed, 176 insertions(+), 8 deletions(-) diff --git a/passerelle/base/templatetags/passerelle.py b/passerelle/base/templatetags/passerelle.py index d0c228b2..3ab25b16 100644 --- a/passerelle/base/templatetags/passerelle.py +++ b/passerelle/base/templatetags/passerelle.py @@ -1,8 +1,11 @@ from __future__ import absolute_import +import collections import re from django import template +from django.utils.html import mark_safe, format_html, conditional_escape, escape +from django.utils.translation import ugettext as _ from django.contrib.contenttypes.models import ContentType from django.contrib.auth import get_permission_codename from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger @@ -84,3 +87,145 @@ def can_delete(obj, user): @stringfilter def censor(string): return re.sub(r'://([^/]*):([^/]*?)@', r'://\1:***@', string) + + +def render_json_schema(schema): + if not isinstance(schema, dict): + if schema is True: + return mark_safe('ALWAYS VALID') + if schema is False: + return mark_safe('ALWAYS INVALID') + return format_html('{!r}', schema) + + def many_of(name, schemas): + s = format_html('{}', name) + parts = [render_json_schema(schema) for schema in schemas] + if any('\n' in part for part in parts): + s += '' + else: + s += ' [ ' + ' | '.join(parts) + ' ]' + return mark_safe(s) + + if 'anyOf' in schema: + return many_of('anyOf', schema['anyOf']) + + if 'oneOf' in schema: + return many_of('oneOf', schema['oneOf']) + + if 'allOf' in schema: + return many_of('allOf', schema['allOf']) + + original_schema = schema + schema = schema.copy() + schema.pop('$schema', None) + schema.pop('$id', None) + title = schema.pop('title', None) + description = schema.pop('description', None) + typ = schema.pop('type', None) + if typ == 'null': + return mark_safe('null') + if typ == 'string': + enum = schema.pop('enum', []) + min_length = schema.pop('minLength', '') + max_length = schema.pop('maxLength', '') + pattern = schema.pop('pattern', '') + if enum: + enum = mark_safe(' | '.join( + [format_html('"{}"', el) for el in enum])) + s = 'string' + if max_length or min_length: + s += format_html('[{0}:{1}]', min_length, max_length) + s += '' + if enum: + s += ' %s' % enum + if pattern: + s += format_html(' /{}/', pattern) + if schema: + s += format_html('\n{!r}', schema) + return mark_safe(s) + if typ == 'integer': + if not schema: + return mark_safe('integer') + if typ == 'number': + if not schema: + return mark_safe('number') + if typ == 'array': + s = 'array ' + if 'items' in schema: + s += render_json_schema(schema['items']) + return mark_safe(s) + if typ == 'object': + s = 'object' + unflatten = schema.pop('unflatten', False) + merge_extra = schema.pop('merge_extra', False) + properties = schema.pop('properties', {}) + required_keys = schema.pop('required', []) + additional_properties = schema.pop('additionalProperties', True) + if unflatten: + s += ', unflatten' + if merge_extra: + s += ', merge_extra' + if not additional_properties: + s += ', no additional properties' + if title: + s += format_html(', {}', title) + if schema: + s += format_html('{!r}', schema) + if description: + s += format_html('\n

{}

', description) + s += ' ' + if properties: + s += '\n' + return mark_safe(s) + if typ == 'boolean': + if not schema: + return mark_safe('bool') + return format_html('unknown {!r}', original_schema) + + +@register.simple_tag(takes_context=False) +def render_body_schemas(body_schemas): + if not body_schemas: + return '' + + s = mark_safe('