Compare commits

...

8 Commits

Author SHA1 Message Date
Cristi Vîjdea a7fbba4967 Update changelog for 1.5.0 2018-03-12 19:26:12 +02:00
Cristi Vîjdea 66785b1ee8 Update swagger-ui to 3.12.1 2018-03-12 19:06:20 +02:00
John Carter fb240f6a5b Detect type of SlugRelatedField with readonly=True (#83)
* Added test for #82 - readonly SlugRelatedField not showing format uuid
* Added ArticleGroup model
* Added example of SlugRelatedField to a uuid field with and without readonly
* Check remote_field.model in get_related_model

Closes #82.
2018-03-10 13:29:20 +02:00
John Carter 2c459024d1 Handle django rest framework HiddenField (#79)
* Added HiddenFieldInspector (#78)
* Updated docs + added test for HiddenField

Added HiddenField to testproj (no change in yaml as expected)

Closes #78.
2018-03-09 22:46:47 +02:00
ko-pp b62161f762 Escape '.' wildcard in testproj urlregex (#80)
Fix: Django's URL patterns being regexes, '.' has to be escaped to be used as a literal value and not as a joker.
2018-03-09 18:28:12 +02:00
Cristi Vîjdea 9ad55bac99 Guard against attempted deletion of inexistent attributes
Fixes #76
2018-03-05 19:59:03 +02:00
Cristi Vîjdea 3d3b7899e5 Update swagger-ui to 3.12.0 and ReDoc to 1.21.2 2018-03-05 17:07:45 +02:00
Cristi Vîjdea 6ea8711a1f Fix in-place modification of swagger_auto_schema arguments (#75)
Fixes #74
2018-03-05 11:51:51 +02:00
22 changed files with 224 additions and 62 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ In ``urls.py``:
) )
urlpatterns = [ urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'), url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'), url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
... ...
+41
View File
@@ -3,6 +3,47 @@ Changelog
######### #########
*********
**1.5.0**
*********
*Release date: Mar 12, 2018*
- **IMPROVED:** ``serializers.HiddenField`` are now hidden (:issue:`78`, :pr:`79`, thanks to :ghuser:`therefromhere`)
*NOTE:* in order for this to work, you will have to add the new ``drf_yasg.inspectors.HiddenFieldInspector`` to your
``DEFAULT_FIELD_INSPECTORS`` array if you changed it from the default value
- **IMPROVED:** type of model field is now detected for ``serializers.SlugRelatedField`` with ``read_only=True``
(:issue:`82`, :pr:`83`, thanks to :ghuser:`therefromhere`)
*********
**1.4.7**
*********
*Release date: Mar 05, 2018*
- **FIXED:** prevent crashes caused by attempting to delete object attributes which do not exist in the first place
(:issue:`76`)
*********
**1.4.6**
*********
*Release date: Mar 05, 2018*
- **IMPROVED:** updated ``swagger-ui`` to version 3.12.0
- **IMPROVED:** updated ``ReDoc`` to version 1.21.2
*********
**1.4.5**
*********
*Release date: Mar 05, 2018*
- **FIXED:** fixed an issue with modification of ``swagger_auto_schema`` arguments in-place during introspection, which
would sometimes cause an incomplete Swagger document to be generated after the first pass (:issue:`74`, :pr:`75`)
********* *********
**1.4.4** **1.4.4**
********* *********
+1
View File
@@ -66,6 +66,7 @@ to this list.
:class:`'drf_yasg.inspectors.ChoiceFieldInspector' <.inspectors.ChoiceFieldInspector>`, |br| \ :class:`'drf_yasg.inspectors.ChoiceFieldInspector' <.inspectors.ChoiceFieldInspector>`, |br| \
:class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \ :class:`'drf_yasg.inspectors.FileFieldInspector' <.inspectors.FileFieldInspector>`, |br| \
:class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \ :class:`'drf_yasg.inspectors.DictFieldInspector' <.inspectors.DictFieldInspector>`, |br| \
:class:`'drf_yasg.inspectors.HiddenFieldInspector' <.inspectors.HiddenFieldInspector>`, |br| \
:class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \ :class:`'drf_yasg.inspectors.SimpleFieldInspector' <.inspectors.SimpleFieldInspector>`, |br| \
:class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \ :class:`'drf_yasg.inspectors.StringDefaultFieldInspector' <.inspectors.StringDefaultFieldInspector>`, |br| \
``]`` ``]``
+10 -10
View File
@@ -211,9 +211,9 @@
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.4", "version": "2.3.5",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz",
"integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==",
"requires": { "requires": {
"core-util-is": "1.0.2", "core-util-is": "1.0.2",
"inherits": "2.0.3", "inherits": "2.0.3",
@@ -225,9 +225,9 @@
} }
}, },
"redoc": { "redoc": {
"version": "1.21.0", "version": "1.21.2",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.21.0.tgz", "resolved": "https://registry.npmjs.org/redoc/-/redoc-1.21.2.tgz",
"integrity": "sha1-RY8E7b7MqyVbQORhZA0eRG949N0=", "integrity": "sha1-uMeLfDxtwFjmeZniwvYbqFjewaY=",
"requires": { "requires": {
"core-js": "2.5.3", "core-js": "2.5.3",
"dropkickjs": "2.1.10", "dropkickjs": "2.1.10",
@@ -301,7 +301,7 @@
"requires": { "requires": {
"builtin-status-codes": "3.0.0", "builtin-status-codes": "3.0.0",
"inherits": "2.0.3", "inherits": "2.0.3",
"readable-stream": "2.3.4", "readable-stream": "2.3.5",
"to-arraybuffer": "1.0.1", "to-arraybuffer": "1.0.1",
"xtend": "4.0.1" "xtend": "4.0.1"
} }
@@ -315,9 +315,9 @@
} }
}, },
"swagger-ui-dist": { "swagger-ui-dist": {
"version": "3.10.0", "version": "3.12.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.10.0.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.12.1.tgz",
"integrity": "sha1-ilrzP/ImPHFaFD9z8qjUfVOZQ+8=" "integrity": "sha1-uIeIiPAM8BPvlf9xfluRK9SoTjo="
}, },
"tiny-emitter": { "tiny-emitter": {
"version": "2.0.2", "version": "2.0.2",
+2 -2
View File
@@ -1,8 +1,8 @@
{ {
"name": "drf-yasg", "name": "drf-yasg",
"dependencies": { "dependencies": {
"redoc": "^1.21.0", "redoc": "^1.21.2",
"swagger-ui-dist": "^3.10.0" "swagger-ui-dist": "^3.12.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+1
View File
@@ -11,6 +11,7 @@ SWAGGER_DEFAULTS = {
'drf_yasg.inspectors.ChoiceFieldInspector', 'drf_yasg.inspectors.ChoiceFieldInspector',
'drf_yasg.inspectors.FileFieldInspector', 'drf_yasg.inspectors.FileFieldInspector',
'drf_yasg.inspectors.DictFieldInspector', 'drf_yasg.inspectors.DictFieldInspector',
'drf_yasg.inspectors.HiddenFieldInspector',
'drf_yasg.inspectors.SimpleFieldInspector', 'drf_yasg.inspectors.SimpleFieldInspector',
'drf_yasg.inspectors.StringDefaultFieldInspector', 'drf_yasg.inspectors.StringDefaultFieldInspector',
], ],
+4 -3
View File
@@ -1,3 +1,4 @@
import copy
import logging import logging
import re import re
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
@@ -355,9 +356,9 @@ class OpenAPISchemaGenerator(object):
view_inspector = view_inspector_cls(view, path, method, components, request, overrides) view_inspector = view_inspector_cls(view, path, method, components, request, overrides)
operation = view_inspector.get_operation(operation_keys) operation = view_inspector.get_operation(operation_keys)
if set(operation.consumes) == set(self.consumes): if 'consumes' in operation and set(operation.consumes) == set(self.consumes):
del operation.consumes del operation.consumes
if set(operation.produces) == set(self.produces): if 'produces' in operation and set(operation.produces) == set(self.produces):
del operation.produces del operation.produces
return operation return operation
@@ -388,7 +389,7 @@ class OpenAPISchemaGenerator(object):
if method in overrides: if method in overrides:
overrides = overrides[method] overrides = overrides[method]
return overrides return copy.deepcopy(overrides)
def get_path_parameters(self, path, view_cls): def get_path_parameters(self, path, view_cls):
"""Return a list of Parameter instances corresponding to any templated path variables. """Return a list of Parameter instances corresponding to any templated path variables.
+3 -2
View File
@@ -4,7 +4,8 @@ from .base import (
) )
from .field import ( from .field import (
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, InlineSerializerInspector, CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, InlineSerializerInspector,
ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector,
HiddenFieldInspector,
) )
from .query import CoreAPICompatInspector, DjangoRestResponsePagination from .query import CoreAPICompatInspector, DjangoRestResponsePagination
from .view import SwaggerAutoSchema from .view import SwaggerAutoSchema
@@ -24,7 +25,7 @@ __all__ = [
# field inspectors # field inspectors
'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector', 'InlineSerializerInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector', 'SimpleFieldInspector',
'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector', 'StringDefaultFieldInspector',
'CamelCaseJSONFilter', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
# view inspectors # view inspectors
'SwaggerAutoSchema', 'SwaggerAutoSchema',
+18 -4
View File
@@ -89,9 +89,9 @@ class InlineSerializerInspector(SerializerInspector):
properties=properties, properties=properties,
required=required or None, required=required or None,
) )
if not ref_name: if not ref_name and 'title' in result:
# on an inline model, the title is derived from the field name # on an inline model, the title is derived from the field name
# but is visually displayed like the model named, which is confusing # but is visually displayed like the model name, which is confusing
# it is better to just remove title from inline models # it is better to just remove title from inline models
del result.title del result.title
return result return result
@@ -161,7 +161,11 @@ def get_related_model(model, source):
:return: related model or ``None`` :return: related model or ``None``
""" """
try: try:
return getattr(model, source).rel.related_model descriptor = getattr(model, source)
try:
return descriptor.rel.related_model
except Exception:
return descriptor.field.remote_field.model
except Exception: # pragma: no cover except Exception: # pragma: no cover
return None return None
@@ -465,11 +469,21 @@ class DictFieldInspector(FieldInspector):
return NotHandled return NotHandled
class HiddenFieldInspector(FieldInspector):
"""Hide ``HiddenField``."""
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
if isinstance(field, serializers.HiddenField):
return None
return NotHandled
class StringDefaultFieldInspector(FieldInspector): class StringDefaultFieldInspector(FieldInspector):
"""For otherwise unhandled fields, return them as plain :data:`.TYPE_STRING` objects.""" """For otherwise unhandled fields, return them as plain :data:`.TYPE_STRING` objects."""
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): # pragma: no cover def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs): # pragma: no cover
# TODO unhandled fields: TimeField HiddenField JSONField # TODO unhandled fields: TimeField JSONField
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs) SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type, use_references, **kwargs)
return SwaggerType(type=openapi.TYPE_STRING) return SwaggerType(type=openapi.TYPE_STRING)
File diff suppressed because one or more lines are too long
@@ -45,11 +45,18 @@
oauth2.auth.code = qp.code; oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl}); oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else { } else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({ oauth2.errCb({
authId: oauth2.auth.name, authId: oauth2.auth.name,
source: "auth", source: "auth",
level: "error", level: "error",
message: "Authorization failed: no accessCode received from the server" message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
}); });
} }
} else { } else {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9 -4
View File
@@ -11,10 +11,15 @@ from rest_framework.views import APIView
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
#: used to forcibly remove the body of a request via :func:`.swagger_auto_schema`
no_body = object()
unset = object() class no_body(object):
"""Used as a sentinel value to forcibly remove the body of a request via :func:`.swagger_auto_schema`."""
pass
class unset(object):
"""Used as a sentinel value for function parameters not set by the caller where ``None`` would be a valid value."""
pass
def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None, def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None,
@@ -33,7 +38,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
:param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object; :param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object;
this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS`` this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS``
setting, and can be set to ``None`` to prevent this operation from being generated setting, and can be set to ``None`` to prevent this operation from being generated
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :data:`.no_body`. The value given here :param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :class:`.no_body`. The value given here
will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``. will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``.
A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-03-10 01:42
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('articles', '0002_article_article_type'),
]
operations = [
migrations.CreateModel(
name='ArticleGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('title', models.CharField(help_text='title model help_text', max_length=255, unique=True)),
('slug', models.SlugField(blank=True, help_text='slug model help_text', unique=True)),
],
),
migrations.AddField(
model_name='article',
name='group',
field=models.ForeignKey(blank=True, default=None, on_delete=django.db.models.deletion.PROTECT, related_name='articles_as_main', to='articles.ArticleGroup'),
),
migrations.AddField(
model_name='article',
name='original_group',
field=models.ForeignKey(blank=True, default=None, on_delete=django.db.models.deletion.PROTECT, related_name='articles_as_original', to='articles.ArticleGroup'),
),
]
+13
View File
@@ -1,3 +1,5 @@
import uuid
from django.db import models from django.db import models
@@ -14,3 +16,14 @@ class Article(models.Model):
) )
cover = models.ImageField(upload_to='article/original/', blank=True) cover = models.ImageField(upload_to='article/original/', blank=True)
group = models.ForeignKey('ArticleGroup', related_name='articles_as_main', blank=True, default=None,
on_delete=models.PROTECT)
original_group = models.ForeignKey('ArticleGroup', related_name='articles_as_original', blank=True, default=None,
on_delete=models.PROTECT)
class ArticleGroup(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
title = models.CharField(help_text="title model help_text", max_length=255, blank=False, unique=True)
slug = models.SlugField(help_text="slug model help_text", unique=True, blank=True)
+4 -2
View File
@@ -1,7 +1,7 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from articles.models import Article from articles.models import Article, ArticleGroup
class ArticleSerializer(serializers.ModelSerializer): class ArticleSerializer(serializers.ModelSerializer):
@@ -12,11 +12,13 @@ class ArticleSerializer(serializers.ModelSerializer):
) )
uuid = serializers.UUIDField(help_text="should articles have UUIDs?", read_only=True) uuid = serializers.UUIDField(help_text="should articles have UUIDs?", read_only=True)
cover_name = serializers.FileField(use_url=False, source='cover', read_only=True) cover_name = serializers.FileField(use_url=False, source='cover', read_only=True)
group = serializers.SlugRelatedField(slug_field='uuid', queryset=ArticleGroup.objects.all())
original_group = serializers.SlugRelatedField(slug_field='uuid', read_only=True)
class Meta: class Meta:
model = Article model = Article
fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified', fields = ('title', 'author', 'body', 'slug', 'date_created', 'date_modified',
'references', 'uuid', 'cover', 'cover_name', 'article_type') 'references', 'uuid', 'cover', 'cover_name', 'article_type', 'group', 'original_group', )
read_only_fields = ('date_created', 'date_modified', read_only_fields = ('date_created', 'date_modified',
'references', 'uuid', 'cover_name') 'references', 'uuid', 'cover_name')
lookup_field = 'slug' lookup_field = 'slug'
+4 -1
View File
@@ -1,3 +1,4 @@
from django.utils import timezone
from rest_framework import serializers from rest_framework import serializers
from .models import Todo, TodoAnother, TodoYetAnother from .models import Todo, TodoAnother, TodoYetAnother
@@ -6,7 +7,9 @@ from .models import Todo, TodoAnother, TodoYetAnother
class TodoSerializer(serializers.ModelSerializer): class TodoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Todo model = Todo
fields = ('title',) fields = ('title', 'a_hidden_field',)
a_hidden_field = serializers.HiddenField(default=timezone.now)
class TodoAnotherSerializer(serializers.ModelSerializer): class TodoAnotherSerializer(serializers.ModelSerializer):
+8
View File
@@ -542,6 +542,7 @@ definitions:
required: required:
- title - title
- body - body
- group
type: object type: object
properties: properties:
title: title:
@@ -601,6 +602,13 @@ definitions:
- 3 - 3
- 7 - 7
- 8 - 8
group:
type: string
format: uuid
original_group:
type: string
format: uuid
readOnly: true
Project: Project:
required: required:
- projectName - projectName
+34
View File
@@ -2,11 +2,14 @@ import json
from collections import OrderedDict from collections import OrderedDict
import pytest import pytest
from rest_framework import routers, serializers, viewsets
from rest_framework.response import Response
from drf_yasg import codecs, openapi from drf_yasg import codecs, openapi
from drf_yasg.codecs import yaml_sane_load from drf_yasg.codecs import yaml_sane_load
from drf_yasg.errors import SwaggerGenerationError from drf_yasg.errors import SwaggerGenerationError
from drf_yasg.generators import OpenAPISchemaGenerator from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.utils import swagger_auto_schema
def test_schema_is_valid(swagger, codec_yaml): def test_schema_is_valid(swagger, codec_yaml):
@@ -79,3 +82,34 @@ def test_securiy_requirements(swagger_settings, mock_schema_request):
swagger = generator.get_schema(mock_schema_request, public=True) swagger = generator.get_schema(mock_schema_request, public=True)
assert swagger['security'] == [] assert swagger['security'] == []
def test_replaced_serializer():
class DetailSerializer(serializers.Serializer):
detail = serializers.CharField()
class DetailViewSet(viewsets.ViewSet):
serializer_class = DetailSerializer
@swagger_auto_schema(responses={404: openapi.Response("Not found or Not accessible", DetailSerializer)})
def retrieve(self, request, pk=None):
serializer = DetailSerializer({'detail': None})
return Response(serializer.data)
router = routers.DefaultRouter()
router.register(r'details', DetailViewSet, base_name='details')
generator = OpenAPISchemaGenerator(
info=openapi.Info(title="Test generator", default_version="v1"),
version="v2",
url='',
patterns=router.urls
)
for _ in range(3):
swagger = generator.get_schema(None, True)
assert 'Detail' in swagger['definitions']
assert 'detail' in swagger['definitions']['Detail']['properties']
responses = swagger['paths']['/details/{id}/']['get']['responses']
assert '404' in responses
assert responses['404']['schema']['$ref'] == "#/definitions/Detail"