drf-spectacular icon indicating copy to clipboard operation
drf-spectacular copied to clipboard

many serializer field regex validator, but show single pattern

Open by-Exist opened this issue 3 years ago • 2 comments

When I have used multiple RegexValidator, I only see a single pattern.

I am using google translator because my english is not good. Sorry if you are uncomfortable with reading.

This is a problem that occurs in AutoSchema's _map_field_validators method, and the pattern is overwritten in schema['pattern'] when validator of the same type is used.

If you don't use multiple RegexValidators and just use one RegexValidator with complex patterns, you can solve the problem. However, I prefer to use multiple RegexValidators in smaller units rather than using RegexValidators with fat patterns.

I am defining CustomAutoSchema to solve the problem in the following way.

# schema['pattern'] = pattern
if "pattern" not in schema:
    schema["pattern"] = pattern
else:
    if isinstance(schema["pattern"], str):
        schema["pattern"] = [schema["pattern"], pattern]
    elif isinstance(schema["pattern"], list):
        schema["pattern"].append(pattern)

Is the process of handling Schema of RegexValidator intended?

I always use drf_spectacular with gratitude.

by-Exist avatar May 14 '21 13:05 by-Exist

Hi @by-Exist,

interesting case! OpenAPI requires the pattern to be a string, so a list of patterns is invalid strictly speaking.

If the validators were joined with an OR this would be a solution: pattern: (pattern1|pattern2|pattern3).

However, it looks like multiple validators are connected with an AND (DRF processing logic). This is a lot harder to do with an regex as this involves lookahead among other things. i'm not sure if there is even a ECMA-compliant regex (OpenAPI requirement) that can model this properly

tfranzel avatar May 14 '21 14:05 tfranzel

I designed the following code by searching.

class CustomAutoSchema(AutoSchema):
    def _get_converted_regex_pattern(
        self, pattern: str, regex_validator: validators.RegexValidator
    ):
        format_string = "(?={})" if not regex_validator.inverse_match else "(?!{})"
        return format_string.format(pattern)

    def _combine_regex_patterns(self, patterns):
        combine_pattern_format_string = "^{}.*$"
        return combine_pattern_format_string.format("".join(patterns))

    def _map_field_validators(self, field, schema):
        converted_regex_patterns = []
        for v in field.validators:
	# ...
            elif isinstance(v, validators.RegexValidator):
                pattern = v.regex.pattern.encode("ascii", "backslashreplace").decode()
                pattern = pattern.replace(r"\x", r"\u00")
                pattern = pattern.replace(r"\Z", "$").replace(r"\A", "^")
                # schema['pattern'] = pattern
                if len(converted_regex_patterns) == 0:
                    schema["pattern"] = pattern
                    converted_pattern = self._get_converted_regex_pattern(pattern, v)
                    converted_regex_patterns.append(converted_pattern)
                else:
                    converted_pattern = self._get_converted_regex_pattern(pattern, v)
                    converted_regex_patterns.append(converted_pattern)
                    combine_pattern = self._combine_regex_patterns(
                        converted_regex_patterns
                    )
                    schema["pattern"] = combine_pattern

However, I am new to regular expressions so I can't check if the code is correct.

by-Exist avatar May 15 '21 13:05 by-Exist