Repository: encode/django-rest-framework

Issue:
Error mapping Python regex to OpenAPI.
## Checklist

- [ v ] I have verified that that issue exists against the `master` branch of Django REST framework.
- [ v ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [ v ] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [ x ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [ v ] I have reduced the issue to the simplest possible case.
- [ x ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce

Use `URLField` in model and use openapi schema generator. Then copy-paste generated JSON to editor.swagger.io

## Expected behavior

Regexp should be valid for openapi.


## Actual behavior

Regexp is invalid.


![obraz](https://user-images.githubusercontent.com/7823689/75548411-7c14fd80-5a2d-11ea-8f37-c62521911eab.png)


```yml
pattern: "^(?:[a-z0-9\\.\\-\\+]*)://(?:[^\\s:@/]+(?::[^\\s:@/]*)?@)?(?:(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}|\\[[0-9a-f:\\.]+\\]|([a-z¡-\uFFFF0-9](?:[a-z¡-\uFFFF0-9-]{0,61}[a-z¡-\uFFFF0-9])?(?:\\.(?!-)[a-z¡-\uFFFF0-9-]{1,63}(?<!-))*\\.(?!-)(?:[a-z¡-\uFFFF-]{2,63}|xn--[a-z0-9]{1,59})(?<!-)\\.?|localhost))(?::\\d{2,5})?(?:[/?#][^\\s]*)?\\Z"

```


Discussion:
The regex comes straight off the validator, which we attach in `map_field_validators()`. It's a good regex, so why isn't it acceptable to the Swagger validator? 

I can only find one match for "regex" in [the spec](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md).

So..

1. What's the required regex format? 
2. Is there any way of mapping to that from the validator? 
3. If not, do we need to skip adding the `pattern` for all cases, or can we identify a subset? 


@carltongibson I found the root cause of this problem.
Swagger Editor does not allow regex to be wrapped in `"`(double-quotes). It must be wrapped in `'`(single-quotes).

For example: 
pattern: '^(\d+\.)?(\d+\.)?(\*|\d+)$' => No error
pattern: ^(\d+\.)?(\d+\.)?(\*|\d+)$ => No error
pattern: "^(\d+\.)?(\d+\.)?(\*|\d+)$" => **Error**


I also found that pyyaml wraps it in `"`. Also, not all regex are wrapped like this. Checkout: https://i.pinimg.com/originals/76/d9/52/76d952bf3c40d001dbdcb39c65f3c4f1.png
Hi @dhaval-mehta. Nice. 

So two questions: 

> Swagger Editor does not allow regex to be wrapped in "(double-quotes). It must be wrapped in '(single-quotes).

Is that a reasonable restriction? What do the Swagger folks say about that? 

> I also found that pyyaml wraps it in ". Also, not all regex are wrapped like this. 

What do the pyyaml folks say about that? 

I'm not seeing anything we can action as yet... 🤔

Resolving patch:
diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py
--- a/rest_framework/schemas/openapi.py
+++ b/rest_framework/schemas/openapi.py
@@ -554,7 +554,9 @@ def map_field_validators(self, field, schema):
             if isinstance(v, URLValidator):
                 schema['format'] = 'uri'
             if isinstance(v, RegexValidator):
-                schema['pattern'] = v.regex.pattern
+                # In Python, the token \Z does what \z does in other engines.
+                # https://stackoverflow.com/questions/53283160
+                schema['pattern'] = v.regex.pattern.replace('\\Z', '\\z')
             elif isinstance(v, MaxLengthValidator):
                 attr_name = 'maxLength'
                 if isinstance(field, serializers.ListField):


Test patch:
diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py
--- a/tests/schemas/test_openapi.py
+++ b/tests/schemas/test_openapi.py
@@ -855,6 +855,7 @@ def test_serializer_validators(self):
         assert properties['url']['type'] == 'string'
         assert properties['url']['nullable'] is True
         assert properties['url']['default'] == 'http://www.example.com'
+        assert '\\Z' not in properties['url']['pattern']
 
         assert properties['uuid']['type'] == 'string'
         assert properties['uuid']['format'] == 'uuid'