Project

General

Profile

0003-misc-render-request-body-s-JSON-schema-35818.patch

Benjamin Dauvergne, 05 Nov 2019 06:54 PM

Download (9.87 KB)

View differences:

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(-)
passerelle/base/templatetags/passerelle.py
1 1
from __future__ import absolute_import
2 2

  
3
import collections
3 4
import re
4 5

  
5 6
from django import template
7
from django.utils.html import mark_safe, format_html, conditional_escape, escape
8
from django.utils.translation import ugettext as _
6 9
from django.contrib.contenttypes.models import ContentType
7 10
from django.contrib.auth import get_permission_codename
8 11
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
......
84 87
@stringfilter
85 88
def censor(string):
86 89
    return re.sub(r'://([^/]*):([^/]*?)@', r'://\1:***@', string)
90

  
91

  
92
def render_json_schema(schema):
93
    if not isinstance(schema, dict):
94
        if schema is True:
95
            return mark_safe('<em>ALWAYS VALID</em>')
96
        if schema is False:
97
            return mark_safe('<em>ALWAYS INVALID</em>')
98
        return format_html('<tt>{!r}</tt>', schema)
99

  
100
    def many_of(name, schemas):
101
        s = format_html('<b>{}</b>', name)
102
        parts = [render_json_schema(schema) for schema in schemas]
103
        if any('\n' in part for part in parts):
104
            s += ' (<ul>'
105
            for part in parts:
106
                s += format_html('<li>{0}</li>\n', part)
107
            s += '</ul>)'
108
        else:
109
            s += ' [ ' + ' | '.join(parts) + ' ]'
110
        return mark_safe(s)
111

  
112
    if 'anyOf' in schema:
113
        return many_of('anyOf', schema['anyOf'])
114

  
115
    if 'oneOf' in schema:
116
        return many_of('oneOf', schema['oneOf'])
117

  
118
    if 'allOf' in schema:
119
        return many_of('allOf', schema['allOf'])
120

  
121
    original_schema = schema
122
    schema = schema.copy()
123
    schema.pop('$schema', None)
124
    schema.pop('$id', None)
125
    title = schema.pop('title', None)
126
    description = schema.pop('description', None)
127
    typ = schema.pop('type', None)
128
    if typ == 'null':
129
        return mark_safe('<b>null</b>')
130
    if typ == 'string':
131
        enum = schema.pop('enum', [])
132
        min_length = schema.pop('minLength', '')
133
        max_length = schema.pop('maxLength', '')
134
        pattern = schema.pop('pattern', '')
135
        if enum:
136
            enum = mark_safe(' | '.join(
137
                [format_html('"<tt>{}</tt>"', el) for el in enum]))
138
        s = '<b>string'
139
        if max_length or min_length:
140
            s += format_html('[{0}:{1}]', min_length, max_length)
141
        s += '</b>'
142
        if enum:
143
            s += ' %s' % enum
144
        if pattern:
145
            s += format_html(' /<tt>{}</tt>/', pattern)
146
        if schema:
147
            s += format_html('\n{!r}', schema)
148
        return mark_safe(s)
149
    if typ == 'integer':
150
        if not schema:
151
            return mark_safe('<b>integer</b>')
152
    if typ == 'number':
153
        if not schema:
154
            return mark_safe('<b>number</b>')
155
    if typ == 'array':
156
        s = '<b>array</b> '
157
        if 'items' in schema:
158
            s += render_json_schema(schema['items'])
159
        return mark_safe(s)
160
    if typ == 'object':
161
        s = '<b>object</b>'
162
        unflatten = schema.pop('unflatten', False)
163
        merge_extra = schema.pop('merge_extra', False)
164
        properties = schema.pop('properties', {})
165
        required_keys = schema.pop('required', [])
166
        additional_properties = schema.pop('additionalProperties', True)
167
        if unflatten:
168
            s += ', <em class="unflatten">unflatten</em>'
169
        if merge_extra:
170
            s += ', <em class="merge-extra">merge_extra</em>'
171
        if not additional_properties:
172
            s += ', <em class="additional-properties-false">no additional properties</em>'
173
        if title:
174
            s += format_html(', <em class="title">{}</em>', title)
175
        if schema:
176
            s += format_html('<tt class="raw">{!r}</tt>', schema)
177
        if description:
178
            s += format_html('\n<p class="description">{}</p>', description)
179
        s += ' '
180
        if properties:
181
            s += '\n<ul>'
182
            keys = properties
183
            if not isinstance(properties, collections.OrderedDict):
184
                keys = sorted(properties, key=lambda key: key.lower())
185
            for key in keys:
186
                sub = properties.get(key, {}).copy()
187
                required = key in required_keys
188
                # FIXME: workaround wrong schemas in vivaticket, gesbac,
189
                # planitech, astregs, atal, iparapheur, iws, lille_kimoce
190
                if sub.get('required') in (True, False):
191
                    required |= sub.pop('required')
192
                sub_description = sub.pop('description', '')
193
                sub_title = sub.pop('title', '')
194
                s += format_html('<li><tt>{0}</tt>', key)
195
                if required:
196
                    s += format_html('<span title="{}" class="required">*</span>', _('required'))
197
                if description or sub:
198
                    s += ' :'
199
                if sub_title:
200
                    s += format_html(' <em>{0}</em>', sub_title)
201
                elif sub_description and '\n' not in sub_description:
202
                    s += format_html(' <em>{0}</em>', sub_description)
203
                if sub_title or '\n' in sub_description:
204
                    s += format_html('\n<p class="description">{}</p>', sub_description)
205
                if sub:
206
                    s += format_html('\n{0}', render_json_schema(sub))
207
                s += '</li>'
208
            s += '</ul>'
209
        return mark_safe(s)
210
    if typ == 'boolean':
211
        if not schema:
212
            return mark_safe('<b>bool</b>')
213
    return format_html('<em>unknown {!r}</em>', original_schema)
214

  
215

  
216
@register.simple_tag(takes_context=False)
217
def render_body_schemas(body_schemas):
218
    if not body_schemas:
219
        return ''
220

  
221
    s = mark_safe('<ul>')
222
    for key in body_schemas:
223
        if key == 'application/json':
224
            s += mark_safe('<li><tt>application/json</tt> : <span class"json-schema">')
225
            s += render_json_schema(body_schemas['application/json'])
226
            s += mark_safe('</span></li>')
227
        else:
228
            s += format_html('<li><tt>{0}</tt></li>', key)
229
    s += mark_safe('<ul>')
230
    return mark_safe(s)
231

  
passerelle/templates/passerelle/manage/service_view.html
65 65
       {% if endpoint.methods|length > 1 %}
66 66
         ({{endpoint.http_method|upper}})
67 67
       {% endif %}
68
       {% if endpoint.has_params %}
69 68
       <ul class="params">
70
         {% for param in endpoint.get_params %}
71
           <li>{{param.name}}
72
               {% if param.optional %}({% trans 'optional' %}{% if param.default_value %},
73
                 {% trans 'default value:' %} {{param.default_value}}{% endif %}){% endif %}
74
                 {% if param.description %}{% trans ':' %} {{param.description}}{% endif %}
69
         {% if endpoint.get_params %}
70
           <li>{% trans "GET parameters" %}
71
             <ul class="get-params">
72
               {% for param in endpoint.get_params %}
73
                 <li>{{param.name}}
74
                     {% if param.optional %}({% trans 'optional' %}{% if param.default_value %},
75
                       {% trans 'default value:' %} {{param.default_value}}{% endif %}){% endif %}
76
                       {% if param.description %}{% trans ':' %} {{param.description}}{% endif %}
77
                       <b class="type">{% if param.type %}{{ param.type }}{% else %}string{% endif %}</b>
78
                 </li>
79
               {% endfor %}
80
             </ul>
75 81
           </li>
76
         {% endfor %}
82
         {% endif %}
83
         {% if endpoint.body_schemas %}
84
           <li>{% trans "Request body" %}
85
             <div class="body-schemas">
86
             {% render_body_schemas body_schemas=endpoint.body_schemas %}
87
             </div>
88
           </li>
89
         {% endif %}
77 90
       </ul>
78
       {% endif %}
79 91
       {% if endpoint.long_description %}
80 92
         <div class="long-description">
81 93
           {{ endpoint.long_description }}
passerelle/utils/api.py
124 124
    def long_description(self):
125 125
        return self.http_method == 'post' and self.post and self.post.get('long_description')
126 126

  
127
    @property
128
    def body_schemas(self):
129
        if (self.http_method == 'post'
130
                and self.post
131
                and 'request_body' in self.post
132
                and 'schema' in self.post['request_body']):
133
            return self.post['request_body']['schema']
134
        return {}
135

  
127 136
    def get_params(self):
128 137
        params = []
129 138
        defaults = dict(zip(
130 139
            reversed(inspect.getargspec(self.func).args),
131 140
            reversed(inspect.getargspec(self.func).defaults or [])))
132 141
        for param in inspect.getargspec(self.func).args[2:]:
142
            if param == 'post_data':
143
                continue
133 144
            param_info = {'name': param}
134 145
            if self.parameters and param in self.parameters and self.parameters[param].get('description'):
135 146
                param_info['description'] = self.parameters[param].get('description')
136
-