예제 #1
0
    def resolve_serializer(self, serializer, direction) -> ResolvedComponent:
        assert is_serializer(serializer)
        serializer = force_instance(serializer)

        component = ResolvedComponent(
            name=self._get_serializer_name(serializer, direction),
            type=ResolvedComponent.SCHEMA,
            object=serializer,
        )
        if component in self.registry:
            return self.registry[component]  # return component with schema

        self.registry.register(component)
        component.schema = self._map_serializer(serializer, direction)
        # 4 cases:
        #   1. polymorphic container component -> use
        #   2. concrete component with properties -> use
        #   3. concrete component without properties -> prob. transactional so discard
        #   4. explicit list component -> demultiplexed at usage location so discard
        keep_component = (
            any(nest_tag in component.schema for nest_tag in ['oneOf', 'allOf', 'anyOf'])
            or component.schema.get('properties', {})
        )
        if not keep_component:
            del self.registry[component]
            return ResolvedComponent(None, None)  # sentinel
        return component
예제 #2
0
    def _get_response_for_code(self, serializer):
        serializer = force_instance(serializer)

        if not serializer:
            return {'description': _('No response body')}
        elif isinstance(serializer, serializers.ListSerializer):
            if is_serializer(serializer.child):
                schema = self.resolve_serializer(serializer.child,
                                                 'response').ref
            else:
                schema = self._map_serializer_field(serializer.child,
                                                    'response')
        elif is_serializer(serializer):
            component = self.resolve_serializer(serializer, 'response')
            if not component.schema:
                return {'description': _('No response body')}
            schema = component.ref
        elif is_basic_type(serializer):
            schema = build_basic_type(serializer)
        elif isinstance(serializer, dict):
            # bypass processing and use given schema directly
            schema = serializer
        else:
            warn(
                f'could not resolve "{serializer}" for {self.method} {self.path}. Expected either '
                f'a serializer or some supported override mechanism. defaulting to '
                f'generic free-form object.')
            schema = build_basic_type(OpenApiTypes.OBJECT)
            schema['description'] = _('Unspecified response body')

        if self._is_list_view(serializer) and not get_override(
                serializer, 'many') is False:
            schema = build_array_type(schema)
            paginator = self._get_paginator()

            if paginator and is_serializer(serializer):
                paginated_name = f'Paginated{self._get_serializer_name(serializer, "response")}List'
                component = ResolvedComponent(
                    name=paginated_name,
                    type=ResolvedComponent.SCHEMA,
                    schema=paginator.get_paginated_response_schema(schema),
                    object=paginated_name,
                )
                self.registry.register(component)
                schema = component.ref
            elif paginator:
                schema = paginator.get_paginated_response_schema(schema)

        return {
            'content': {
                mt: {
                    'schema': schema
                }
                for mt in self.map_renderers('media_type')
            },
            # Description is required by spec, but descriptions for each response code don't really
            # fit into our model. Description is therefore put into the higher level slots.
            # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responseObject
            'description': ''
        }
예제 #3
0
    def resolve_serializer(self, serializer, direction) -> ResolvedComponent:
        assert is_serializer(serializer), (
            f'internal assumption violated because we expected a serializer here and instead '
            f'got a "{serializer}". This may be the result of another app doing some unexpected '
            f'magic or an invalid internal call. Feel free to report this as a bug at '
            f'https://github.com/tfranzel/drf-spectacular/issues ')
        serializer = force_instance(serializer)

        component = ResolvedComponent(
            name=self._get_serializer_name(serializer, direction),
            type=ResolvedComponent.SCHEMA,
            object=serializer,
        )
        if component in self.registry:
            return self.registry[component]  # return component with schema

        self.registry.register(component)
        component.schema = self._map_serializer(serializer, direction)
        # 4 cases:
        #   1. polymorphic container component -> use
        #   2. concrete component with properties -> use
        #   3. concrete component without properties -> prob. transactional so discard
        #   4. explicit list component -> demultiplexed at usage location so discard
        keep_component = (any(nest_tag in component.schema
                              for nest_tag in ['oneOf', 'allOf', 'anyOf'])
                          or component.schema.get('properties', {}))
        if not keep_component:
            del self.registry[component]
            return ResolvedComponent(None, None)  # sentinel
        return component
예제 #4
0
    def _map_basic_serializer(self, serializer, direction):
        serializer = force_instance(serializer)
        required = set()
        properties = {}

        for field in serializer.fields.values():
            if isinstance(field, serializers.HiddenField):
                continue

            schema = self._map_serializer_field(field, direction)
            # skip field if there is no schema for the direction
            if not schema:
                continue

            if field.required or schema.get('readOnly'):
                required.add(field.field_name)

            self._map_field_validators(field, schema)

            properties[field.field_name] = safe_ref(schema)

        if spectacular_settings.COMPONENT_SPLIT_PATCH:
            if self.method == 'PATCH' and direction == 'request':
                required = []

        return build_object_type(
            properties=properties,
            required=required,
            description=inspect.getdoc(serializer),
        )
예제 #5
0
    def map_serializer(self, auto_schema, method: str):
        """ custom handling for @extend_schema's injection of PolymorphicProxySerializer """
        serializer = self.target
        sub_components = []

        for sub_serializer in serializer.serializers:
            sub_serializer = force_instance(sub_serializer)
            resolved_sub_serializer = auto_schema.resolve_serializer(
                method, sub_serializer)

            try:
                discriminator_field = sub_serializer.fields[
                    serializer.resource_type_field_name]
                resource_type = discriminator_field.to_representation(None)
            except:  # noqa: E722
                warn(
                    f'sub-serializer {resolved_sub_serializer.name} of {serializer.component_name} '
                    f'must contain the discriminator field "{serializer.resource_type_field_name}". '
                    f'defaulting to sub-serializer name, but schema will likely not match the API.'
                )
                resource_type = resolved_sub_serializer.name

            sub_components.append((resource_type, resolved_sub_serializer.ref))

        return {
            'oneOf': [ref for _, ref in sub_components],
            'discriminator': {
                'propertyName': serializer.resource_type_field_name,
                'mapping': {
                    resource_type: ref['$ref']
                    for resource_type, ref in sub_components
                }
            }
        }
예제 #6
0
    def _get_request_body(self):
        # only unsafe methods can have a body
        if self.method not in ('PUT', 'PATCH', 'POST'):
            return None

        serializer = force_instance(self.get_request_serializer())

        request_body_required = False
        if is_list_serializer(serializer):
            if is_serializer(serializer.child):
                component = self.resolve_serializer(serializer.child,
                                                    'request')
                schema = build_array_type(component.ref)
            else:
                schema = build_array_type(
                    self._map_serializer_field(serializer.child, 'request'))
            request_body_required = True
        elif is_serializer(serializer):
            if self.method == 'PATCH':
                serializer.partial = True
            component = self.resolve_serializer(serializer, 'request')
            if not component.schema:
                # serializer is empty so skip content enumeration
                return None
            schema = component.ref
            # request body is only required if any required property is not read-only
            readonly_props = [
                p for p, s in component.schema.get('properties', {}).items()
                if s.get('readOnly')
            ]
            required_props = component.schema.get('required', [])
            request_body_required = any(req not in readonly_props
                                        for req in required_props)
        elif is_basic_type(serializer):
            schema = build_basic_type(serializer)
            if not schema:
                return None
        else:
            warn(
                f'could not resolve request body for {self.method} {self.path}. defaulting to generic '
                'free-form object. (maybe annotate a Serializer class?)')
            schema = build_object_type(
                additionalProperties={},
                description='Unspecified request body',
            )

        request_body = {
            'content': {
                media_type: build_media_type_object(
                    schema,
                    self._get_examples(serializer, 'request', media_type))
                for media_type in self.map_parsers()
            }
        }

        if request_body_required:
            request_body['required'] = request_body_required

        return request_body
예제 #7
0
    def _map_serializer(self, method, serializer):
        serializer = force_instance(serializer)
        serializer_extension = OpenApiSerializerExtension.get_match(serializer)

        if serializer_extension:
            return serializer_extension.map_serializer(self, method)
        else:
            return self._map_basic_serializer(method, serializer)
예제 #8
0
    def _get_explicit_sub_components(self, auto_schema, direction):
        sub_components = []
        for resource_type, sub_serializer in self.target.serializers.items():
            sub_serializer = force_instance(sub_serializer)
            resolved_sub_serializer = auto_schema.resolve_serializer(
                sub_serializer, direction)
            sub_components.append((resource_type, resolved_sub_serializer.ref))

        return sub_components
예제 #9
0
    def _map_serializer(self, serializer, direction):
        serializer = force_instance(serializer)
        serializer_extension = OpenApiSerializerExtension.get_match(serializer)

        if serializer_extension:
            schema = serializer_extension.map_serializer(self, direction)
        else:
            schema = self._map_basic_serializer(serializer, direction)

        return self._postprocess_serializer_schema(schema, serializer, direction)
예제 #10
0
    def map_parsers(self):
        """
        Get request parsers.

        Handling cases with `FileField`.
        """
        parsers = super().map_parsers()
        serializer = force_instance(self.get_request_serializer())
        for field_name, field in getattr(serializer, "fields", {}).items():
            if isinstance(field, serializers.FileField) and self.method in ("PUT", "PATCH", "POST"):
                return ["multipart/form-data", "application/x-www-form-urlencoded"]
        return parsers
예제 #11
0
    def _map_response_type_hint(self, method):
        hint = get_override(method, 'field') or typing.get_type_hints(method).get('return')

        if is_serializer(hint) or is_field(hint):
            return self._map_serializer_field(force_instance(hint), 'response')

        try:
            return resolve_type_hint(hint)
        except UnableToProceedError:
            warn(
                f'unable to resolve type hint for function "{method.__name__}". consider '
                f'using a type hint or @extend_schema_field. defaulting to string.'
            )
            return build_basic_type(OpenApiTypes.STR)
예제 #12
0
    def _get_response_for_code(self, path, method, serializer):
        serializer = force_instance(serializer)

        if not serializer:
            return {'description': 'No response body'}
        elif isinstance(serializer, serializers.ListSerializer):
            schema = self.resolve_serializer(method, serializer.child).ref
        elif is_serializer(serializer):
            component = self.resolve_serializer(method, serializer)
            if not component:
                return {'description': 'No response body'}
            schema = component.ref
        elif is_basic_type(serializer):
            schema = build_basic_type(serializer)
        elif isinstance(serializer, dict):
            # bypass processing and use given schema directly
            schema = serializer
        else:
            warn(
                f'could not resolve "{serializer}" for {method} {path}. Expected either '
                f'a serializer or some supported override mechanism. defaulting to '
                f'generic free-form object.')
            schema = build_basic_type(OpenApiTypes.OBJECT)
            schema['description'] = 'Unspecified response body'

        if isinstance(serializer, serializers.ListSerializer) or is_list_view(
                path, method, self.view):
            # TODO i fear is_list_view is not covering all the cases
            schema = build_array_type(schema)
            paginator = self._get_paginator()
            if paginator:
                schema = paginator.get_paginated_response_schema(schema)

        return {
            'content': {
                mt: {
                    'schema': schema
                }
                for mt in self.map_renderers(path, method)
            },
            # Description is required by spec, but descriptions for each response code don't really
            # fit into our model. Description is therefore put into the higher level slots.
            # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responseObject
            'description': ''
        }
예제 #13
0
    def _map_type_hint(self, method):
        hint = getattr(method, '_spectacular_annotation', None) or typing.get_type_hints(method).get('return')

        if is_serializer(hint) or is_field(hint):
            return self._map_serializer_field(force_instance(hint))
        elif is_basic_type(hint):
            return build_basic_type(hint)
        elif getattr(hint, '__origin__', None) is typing.Union:
            if type(None) == hint.__args__[1] and len(hint.__args__) == 2:
                schema = build_basic_type(hint.__args__[0])
                schema['nullable'] = True
                return schema
            else:
                warn(f'type hint {hint} not supported yet. defaulting to "string"')
                return build_basic_type(OpenApiTypes.STR)
        else:
            warn(f'type hint for function "{method.__name__}" is unknown. defaulting to string.')
            return build_basic_type(OpenApiTypes.STR)
예제 #14
0
    def _get_request_body(self):
        # only unsafe methods can have a body
        if self.method not in ('PUT', 'PATCH', 'POST'):
            return None

        serializer = force_instance(self.get_request_serializer())

        request_body_required = False
        if isinstance(serializer, serializers.ListSerializer):
            component = self.resolve_serializer(serializer.child, 'request')
            schema = build_array_type(component.ref)
            request_body_required = True
        elif is_serializer(serializer):
            component = self.resolve_serializer(serializer, 'request')
            if not component:
                # serializer is empty so skip content enumeration
                return None
            schema = component.ref
            if component.schema.get('required', []):
                request_body_required = True
        elif is_basic_type(serializer):
            schema = build_basic_type(serializer)
            if not schema:
                return None
        else:
            warn(
                f'could not resolve request body for {self.method} {self.path}. defaulting to generic '
                'free-form object. (maybe annotate a Serializer class?)'
            )
            schema = {
                'type': 'object',
                'additionalProperties': {},  # https://github.com/swagger-api/swagger-codegen/issues/1318
                'description': 'Unspecified request body',
            }

        request_body = {
            'content': {
                request_media_types: {'schema': schema} for request_media_types in self.map_parsers()
            }
        }
        if request_body_required:
            request_body['required'] = request_body_required

        return request_body
예제 #15
0
    def _map_serializer(self, auto_schema, direction, mapping):
        sub_components = []

        for key, serializer_class in mapping.items():
            sub_serializer = force_instance(serializer_class)
            resolved_sub_serializer = auto_schema.resolve_serializer(
                sub_serializer, direction)
            sub_components.append((key, resolved_sub_serializer.ref))

        return {
            "oneOf": [ref for _, ref in sub_components],
            "discriminator": {
                "propertyName": self.target.type_field_name,
                "mapping": {
                    resource_type: ref["$ref"]
                    for resource_type, ref in sub_components
                },
            },
        }
예제 #16
0
    def _map_response_type_hint(self, method):
        hint = get_override(method, 'field') or typing.get_type_hints(method).get('return')

        if is_serializer(hint) or is_field(hint):
            return self._map_serializer_field(force_instance(hint), 'response')
        elif is_basic_type(hint, allow_none=False):
            return build_basic_type(hint)
        elif getattr(hint, '__origin__', None) is typing.Union:
            if type(None) == hint.__args__[1] and len(hint.__args__) == 2:
                schema = build_basic_type(hint.__args__[0])
                schema['nullable'] = True
                return schema
            else:
                warn(f'type hint {hint} not supported yet. defaulting to "string"')
                return build_basic_type(OpenApiTypes.STR)
        else:
            warn(
                f'type hint for function "{method.__name__}" is unknown. consider using '
                f'a type hint or @extend_schema_field. defaulting to string.'
            )
            return build_basic_type(OpenApiTypes.STR)
예제 #17
0
    def _map_serializer(self, auto_schema, direction, mapping):
        sub_components = []

        for key, serializer_class in mapping.items():
            sub_serializer = force_instance(serializer_class)
            resolved_sub_serializer = auto_schema.resolve_serializer(
                sub_serializer,
                direction
            )
            sub_components.append((key, resolved_sub_serializer.ref))

        return {
            'oneOf': [ref for _, ref in sub_components],
            'discriminator': {
                'propertyName': self.target.type_field_name,
                'mapping': {
                    resource_type: ref['$ref']
                    for resource_type, ref in sub_components
                }
            }
        }
예제 #18
0
    def _get_implicit_sub_components(self, auto_schema, direction):
        sub_components = []
        for sub_serializer in self.target.serializers:
            sub_serializer = force_instance(sub_serializer)
            resolved_sub_serializer = auto_schema.resolve_serializer(
                sub_serializer, direction)

            try:
                discriminator_field = sub_serializer.fields[
                    self.target.resource_type_field_name]
                resource_type = discriminator_field.to_representation(None)
            except:  # noqa: E722
                warn(
                    f'sub-serializer {resolved_sub_serializer.name} of {self.target.component_name} '
                    f'must contain the discriminator field "{self.target.resource_type_field_name}". '
                    f'defaulting to sub-serializer name, but schema will likely not match the API.'
                )
                resource_type = resolved_sub_serializer.name

            sub_components.append((resource_type, resolved_sub_serializer.ref))

        return sub_components
예제 #19
0
    def resolve_serializer(self, method, serializer) -> ResolvedComponent:
        assert is_serializer(serializer)
        serializer = force_instance(serializer)

        component = ResolvedComponent(
            name=self._get_serializer_name(method, serializer),
            type=ResolvedComponent.SCHEMA,
            object=serializer,
        )
        if component in self.registry:
            return self.registry[component]  # return component with schema

        self.registry.register(component)
        component.schema = self._map_serializer(method, serializer)
        # 3 cases:
        #   1. polymorphic container component -> use
        #   2. concrete component with properties -> use
        #   3. concrete component without properties -> prob. transactional so discard
        if 'oneOf' not in component.schema and not component.schema[
                'properties']:
            del self.registry[component]
            return ResolvedComponent(None, None)  # sentinel
        return component
예제 #20
0
    def _map_basic_serializer(self, serializer, direction):
        serializer = force_instance(serializer)
        required = set()
        properties = {}

        for field in serializer.fields.values():
            if isinstance(field, serializers.HiddenField):
                continue
            if field.field_name in get_override(serializer, 'exclude_fields',
                                                []):
                continue

            schema = self._map_serializer_field(field, direction)
            # skip field if there is no schema for the direction
            if not schema:
                continue

            add_to_required = (
                field.required or
                (schema.get('readOnly')
                 and not spectacular_settings.COMPONENT_NO_READ_ONLY_REQUIRED))
            if add_to_required:
                required.add(field.field_name)

            self._map_field_validators(field, schema)

            properties[field.field_name] = safe_ref(schema)

        if spectacular_settings.COMPONENT_SPLIT_PATCH:
            if self.method == 'PATCH' and direction == 'request':
                required = []

        return build_object_type(
            properties=properties,
            required=required,
            description=get_doc(serializer.__class__),
        )
예제 #21
0
def test_force_instance():
    assert isinstance(force_instance(serializers.CharField),
                      serializers.CharField)
    assert force_instance(5) == 5
    assert force_instance(dict) == dict
예제 #22
0
    def _map_serializer_field(self, field, direction):
        meta = self._get_serializer_field_meta(field)

        if has_override(field, 'field'):
            override = get_override(field, 'field')
            if is_basic_type(override):
                schema = build_basic_type(override)
            elif isinstance(override, dict):
                schema = override
            else:
                schema = self._map_serializer_field(force_instance(override), direction)

            field_component_name = get_override(field, 'field_component_name')
            if field_component_name:
                component = ResolvedComponent(
                    name=field_component_name,
                    type=ResolvedComponent.SCHEMA,
                    schema=schema,
                    object=field,
                )
                self.registry.register_on_missing(component)
                return append_meta(component.ref, meta)
            else:
                return append_meta(schema, meta)

        serializer_field_extension = OpenApiSerializerFieldExtension.get_match(field)
        if serializer_field_extension:
            schema = serializer_field_extension.map_serializer_field(self, direction)
            return append_meta(schema, meta)

        # nested serializer
        if isinstance(field, serializers.Serializer):
            component = self.resolve_serializer(field, direction)
            return append_meta(component.ref, meta) if component else None

        # nested serializer with many=True gets automatically replaced with ListSerializer
        if isinstance(field, serializers.ListSerializer):
            if is_serializer(field.child):
                component = self.resolve_serializer(field.child, direction)
                return append_meta(build_array_type(component.ref), meta) if component else None
            else:
                schema = self._map_serializer_field(field.child, direction)
                return append_meta(build_array_type(schema), meta)

        # Related fields.
        if isinstance(field, serializers.ManyRelatedField):
            schema = self._map_serializer_field(field.child_relation, direction)
            # remove hand-over initkwargs applying only to outer scope
            schema.pop('description', None)
            schema.pop('readOnly', None)
            return append_meta(build_array_type(schema), meta)

        if isinstance(field, serializers.PrimaryKeyRelatedField):
            # read_only fields do not have a Manager by design. go around and get field
            # from parent. also avoid calling Manager. __bool__ as it might be customized
            # to hit the database.
            if getattr(field, 'queryset', None) is not None:
                model_field = field.queryset.model._meta.pk
            else:
                if isinstance(field.parent, serializers.ManyRelatedField):
                    relation_field = field.parent.parent.Meta.model._meta.get_field(field.parent.source)
                else:
                    relation_field = field.parent.Meta.model._meta.get_field(field.source)
                model_field = relation_field.related_model._meta.pk

            # primary keys are usually non-editable (readOnly=True) and map_model_field correctly
            # signals that attribute. however this does not apply in the context of relations.
            schema = self._map_model_field(model_field, direction)
            schema.pop('readOnly', None)
            return append_meta(schema, meta)

        if isinstance(field, serializers.StringRelatedField):
            return append_meta(build_basic_type(OpenApiTypes.STR), meta)

        if isinstance(field, serializers.SlugRelatedField):
            return append_meta(build_basic_type(OpenApiTypes.STR), meta)

        if isinstance(field, serializers.HyperlinkedIdentityField):
            return append_meta(build_basic_type(OpenApiTypes.URI), meta)

        if isinstance(field, serializers.HyperlinkedRelatedField):
            return append_meta(build_basic_type(OpenApiTypes.URI), meta)

        if isinstance(field, serializers.MultipleChoiceField):
            return append_meta(build_array_type(build_choice_field(field)), meta)

        if isinstance(field, serializers.ChoiceField):
            return append_meta(build_choice_field(field), meta)

        if isinstance(field, serializers.ListField):
            if isinstance(field.child, _UnvalidatedField):
                return append_meta(build_array_type({}), meta)
            elif is_serializer(field.child):
                component = self.resolve_serializer(field.child, direction)
                return append_meta(build_array_type(component.ref), meta) if component else None
            else:
                schema = self._map_serializer_field(field.child, direction)
                return append_meta(build_array_type(schema), meta)

        # DateField and DateTimeField type is string
        if isinstance(field, serializers.DateField):
            return append_meta(build_basic_type(OpenApiTypes.DATE), meta)

        if isinstance(field, serializers.DateTimeField):
            return append_meta(build_basic_type(OpenApiTypes.DATETIME), meta)

        if isinstance(field, serializers.TimeField):
            return append_meta(build_basic_type(OpenApiTypes.TIME), meta)

        if isinstance(field, serializers.EmailField):
            return append_meta(build_basic_type(OpenApiTypes.EMAIL), meta)

        if isinstance(field, serializers.URLField):
            return append_meta(build_basic_type(OpenApiTypes.URI), meta)

        if isinstance(field, serializers.UUIDField):
            return append_meta(build_basic_type(OpenApiTypes.UUID), meta)

        if isinstance(field, serializers.DurationField):
            return append_meta(build_basic_type(OpenApiTypes.STR), meta)

        if isinstance(field, serializers.IPAddressField):
            # TODO this might be a DRF bug. protocol is not propagated to serializer although it
            #  should have been. results in always 'both' (thus no format)
            if 'ipv4' == field.protocol.lower():
                schema = build_basic_type(OpenApiTypes.IP4)
            elif 'ipv6' == field.protocol.lower():
                schema = build_basic_type(OpenApiTypes.IP6)
            else:
                schema = build_basic_type(OpenApiTypes.STR)
            return append_meta(schema, meta)

        # DecimalField has multipleOf based on decimal_places
        if isinstance(field, serializers.DecimalField):
            if getattr(field, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING):
                content = {**build_basic_type(OpenApiTypes.STR), 'format': 'decimal'}
            else:
                content = build_basic_type(OpenApiTypes.DECIMAL)

            if field.max_whole_digits:
                content['maximum'] = int(field.max_whole_digits * '9') + 1
                content['minimum'] = -content['maximum']
            self._map_min_max(field, content)
            return append_meta(content, meta)

        if isinstance(field, serializers.FloatField):
            content = build_basic_type(OpenApiTypes.FLOAT)
            self._map_min_max(field, content)
            return append_meta(content, meta)

        if isinstance(field, serializers.IntegerField):
            content = build_basic_type(OpenApiTypes.INT)
            self._map_min_max(field, content)
            # 2147483647 is max for int32_size, so we use int64 for format
            if int(content.get('maximum', 0)) > 2147483647 or int(content.get('minimum', 0)) > 2147483647:
                content['format'] = 'int64'
            return append_meta(content, meta)

        if isinstance(field, serializers.FileField):
            if spectacular_settings.COMPONENT_SPLIT_REQUEST and direction == 'request':
                content = build_basic_type(OpenApiTypes.BINARY)
            else:
                use_url = getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
                content = build_basic_type(OpenApiTypes.URI if use_url else OpenApiTypes.STR)
            return append_meta(content, meta)

        if isinstance(field, serializers.SerializerMethodField):
            method = getattr(field.parent, field.method_name)
            return append_meta(self._map_response_type_hint(method), meta)

        if anyisinstance(field, [serializers.BooleanField, serializers.NullBooleanField]):
            return append_meta(build_basic_type(OpenApiTypes.BOOL), meta)

        if isinstance(field, serializers.JSONField):
            return append_meta(build_basic_type(OpenApiTypes.OBJECT), meta)

        if anyisinstance(field, [serializers.DictField, serializers.HStoreField]):
            content = build_basic_type(OpenApiTypes.OBJECT)
            if not isinstance(field.child, _UnvalidatedField):
                content['additionalProperties'] = self._map_serializer_field(field.child, direction)
            return append_meta(content, meta)

        if isinstance(field, serializers.CharField):
            return append_meta(build_basic_type(OpenApiTypes.STR), meta)

        if isinstance(field, serializers.ReadOnlyField):
            # direct source from the serializer
            assert field.source_attrs, f'ReadOnlyField "{field}" needs a proper source'
            target = follow_field_source(field.parent.Meta.model, field.source_attrs)

            if callable(target):
                schema = self._map_response_type_hint(target)
            elif isinstance(target, models.Field):
                schema = self._map_model_field(target, direction)
            else:
                assert False, f'ReadOnlyField target "{field}" must be property or model field'
            return append_meta(schema, meta)

        # DRF was not able to match the model field to an explicit SerializerField and therefore
        # used its generic fallback serializer field that simply wraps the model field.
        if isinstance(field, serializers.ModelField):
            schema = self._map_model_field(field.model_field, direction)
            return append_meta(schema, meta)

        warn(f'could not resolve serializer field "{field}". defaulting to "string"')
        return append_meta(build_basic_type(OpenApiTypes.STR), meta)