diff --git a/modules/openapi-json-schema-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java b/modules/openapi-json-schema-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java index 9ed711c6649..2c6b82ac411 100644 --- a/modules/openapi-json-schema-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java +++ b/modules/openapi-json-schema-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java @@ -1041,7 +1041,9 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo cp.isNullable = false; cp.setHasMultipleTypes(true); } - postProcessPattern(cp.pattern, cp.vendorExtensions); + if (p.getPattern() != null) { + postProcessPattern(p.getPattern(), cp.vendorExtensions); + } // if we have a property that has a difficult name, either: // 1. name is reserved, like class int float // 2. name is invalid in python like '3rd' or 'Content-Type' @@ -1485,7 +1487,11 @@ protected Object processTestExampleData(Object value) { @Override public CodegenModel fromModel(String name, Schema sc) { CodegenModel cm = super.fromModel(name, sc); - + if (sc.getPattern() != null) { + postProcessPattern(sc.getPattern(), cm.vendorExtensions); + String pattern = (String) cm.vendorExtensions.get("x-regex"); + cm.setPattern(pattern); + } if (cm.isNullable) { cm.setIsNull(true); cm.isNullable = false; @@ -1891,34 +1897,21 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o example = "2"; } else if (StringUtils.isNotBlank(schema.getPattern())) { String pattern = schema.getPattern(); + List results = getPatternAndModifiers(pattern); + String extractedPattern = (String) results.get(0); + List regexFlags = (List) results.get(1); /* RxGen does not support our ECMA dialect https://github.com/curious-odd-man/RgxGen/issues/56 So strip off the leading / and trailing / and turn on ignore case if we have it */ - Pattern valueExtractor = Pattern.compile("^/?(.+?)/?(.?)$"); - Matcher m = valueExtractor.matcher(pattern); RgxGen rgxGen = null; - if (m.find()) { - int groupCount = m.groupCount(); - if (groupCount == 1) { - // only pattern found - String isolatedPattern = m.group(1); - rgxGen = new RgxGen(isolatedPattern); - } else if (groupCount == 2) { - // patterns and flag found - String isolatedPattern = m.group(1); - String flags = m.group(2); - if (flags.contains("i")) { - rgxGen = new RgxGen(isolatedPattern); - RgxGenProperties properties = new RgxGenProperties(); - RgxGenOption.CASE_INSENSITIVE.setInProperties(properties, true); - rgxGen.setProperties(properties); - } else { - rgxGen = new RgxGen(isolatedPattern); - } - } + if (regexFlags.size() > 0 && regexFlags.contains("i")) { + rgxGen = new RgxGen(extractedPattern); + RgxGenProperties properties = new RgxGenProperties(); + RgxGenOption.CASE_INSENSITIVE.setInProperties(properties, true); + rgxGen.setProperties(properties); } else { - rgxGen = new RgxGen(pattern); + rgxGen = new RgxGen(extractedPattern); } // this seed makes it so if we have [a-z] we pick a @@ -2358,6 +2351,16 @@ protected void updatePropertyForString(CodegenProperty property, Schema p) { property.pattern = toRegularExpression(p.getPattern()); } + @Override + public String toRegularExpression(String pattern) { + if (pattern == null) { + return null; + } + List results = getPatternAndModifiers(pattern); + String extractedPattern = (String) results.get(0); + return extractedPattern; + } + protected void updatePropertyForNumber(CodegenProperty property, Schema p) { property.setIsNumber(true); // float and double differentiation is determined with format info @@ -2482,6 +2485,46 @@ public ModelsMap postProcessModels(ModelsMap objs) { return postProcessModelsEnum(objs); } + /** + * @param pattern the regex pattern + * @return List> + */ + private List getPatternAndModifiers(String pattern) { + /* + Notes: + RxGen does not support our ECMA dialect https://github.com/curious-odd-man/RgxGen/issues/56 + So strip off the leading / and trailing / and turn on ignore case if we have it + + json schema test cases omit the leading and trailing /s, so make sure that the regex allows that + */ + Pattern valueExtractor = Pattern.compile("^/?(.+?)/?([simu]{0,4})$"); + Matcher m = valueExtractor.matcher(pattern); + if (m.find()) { + int groupCount = m.groupCount(); + if (groupCount == 1) { + // only pattern found + String isolatedPattern = m.group(1); + return Arrays.asList(isolatedPattern, null); + } else if (groupCount == 2) { + List modifiers = new ArrayList(); + // patterns and flag found + String isolatedPattern = m.group(1); + String flags = m.group(2); + if (flags.contains("s")) { + modifiers.add("DOTALL"); + } + if (flags.contains("i")) { + modifiers.add("IGNORECASE"); + } + if (flags.contains("m")) { + modifiers.add("MULTILINE"); + } + return Arrays.asList(isolatedPattern, modifiers); + } + } + return Arrays.asList(pattern, new ArrayList()); + } + /* * The OpenAPI pattern spec follows the Perl convention and style of modifiers. Python * does not support this in as natural a way so it needs to convert it. See @@ -2489,28 +2532,14 @@ public ModelsMap postProcessModels(ModelsMap objs) { */ public void postProcessPattern(String pattern, Map vendorExtensions) { if (pattern != null) { - int regexLength = pattern.length(); - String regex = pattern; - int i = pattern.lastIndexOf('/'); - if (regexLength >= 2 && pattern.charAt(0) == '/' && i != -1) { - // json schema tests do not include the leading and trailing slashes - // so I do not think that they are required - regex = pattern.substring(1, i); - } - regex = regex.replace("'", "\\'"); - List modifiers = new ArrayList(); - - if (i != -1) { - for (char c : pattern.substring(i).toCharArray()) { - if (regexModifiers.containsKey(c)) { - String modifier = regexModifiers.get(c); - modifiers.add(modifier); - } - } - } + List results = getPatternAndModifiers(pattern); + String extractedPattern = (String) results.get(0); + List modifiers = (List) results.get(1); - vendorExtensions.put("x-regex", regex); - vendorExtensions.put("x-modifiers", modifiers); + vendorExtensions.put("x-regex", extractedPattern); + if (modifiers.size() > 0) { + vendorExtensions.put("x-modifiers", modifiers); + } } } diff --git a/modules/openapi-json-schema-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java b/modules/openapi-json-schema-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java index 82c86262dbf..2b7aabae46a 100644 --- a/modules/openapi-json-schema-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java +++ b/modules/openapi-json-schema-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java @@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -164,4 +165,36 @@ public void testApisNotGenerated() throws Exception { Assert.assertFalse(Files.isDirectory(pathThatShouldNotExist)); output.deleteOnExit(); } + + @Test + public void testRegexWithoutTrailingSlashWorks() { + OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/11_regex.yaml"); + PythonClientCodegen codegen = new PythonClientCodegen(); + codegen.setOpenAPI(openAPI); + + String modelName = "UUID"; + Schema schema = openAPI.getComponents().getSchemas().get(modelName); + + CodegenModel cm = codegen.fromModel(modelName, schema); + String expectedRegexPattern = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"; + Assert.assertEquals(cm.getPattern(), expectedRegexPattern); + Assert.assertEquals(cm.vendorExtensions.get("x-regex"), expectedRegexPattern); + Assert.assertFalse(cm.vendorExtensions.containsKey("x-modifiers")); + } + + @Test + public void testRegexWithMultipleFlagsWorks() { + OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/11_regex.yaml"); + PythonClientCodegen codegen = new PythonClientCodegen(); + codegen.setOpenAPI(openAPI); + + String modelName = "StringWithRegexWithThreeFlags"; + Schema schema = openAPI.getComponents().getSchemas().get(modelName); + + CodegenModel cm = codegen.fromModel(modelName, schema); + String expectedRegexPattern = "a."; + Assert.assertEquals(cm.getPattern(), expectedRegexPattern); + Assert.assertEquals(cm.vendorExtensions.get("x-regex"), expectedRegexPattern); + Assert.assertEquals(cm.vendorExtensions.get("x-modifiers"), Arrays.asList("DOTALL", "IGNORECASE", "MULTILINE")); + } } diff --git a/modules/openapi-json-schema-generator/src/test/resources/3_0/11_regex.yaml b/modules/openapi-json-schema-generator/src/test/resources/3_0/11_regex.yaml new file mode 100644 index 00000000000..1fd9eeadc93 --- /dev/null +++ b/modules/openapi-json-schema-generator/src/test/resources/3_0/11_regex.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Test + version: 1.0.0-SNAPSHOT +paths: + /test: + get: + tags: + - Test Resource + parameters: + - name: uuid + in: query + schema: + $ref: '#/components/schemas/UUID' + responses: + "200": + description: OK +components: + schemas: + UUID: + format: uuid + pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}" + type: string + StringWithRegexWithThreeFlags: + format: uuid + pattern: "/a./sim" + type: string \ No newline at end of file diff --git a/samples/openapi3/client/petstore/python/docs/apis/tags/FakeApi.md b/samples/openapi3/client/petstore/python/docs/apis/tags/FakeApi.md index a368406d7b2..ad2384b1241 100644 --- a/samples/openapi3/client/petstore/python/docs/apis/tags/FakeApi.md +++ b/samples/openapi3/client/petstore/python/docs/apis/tags/FakeApi.md @@ -949,8 +949,8 @@ with petstore_api.ApiClient(configuration) as api_client: number=32.1, _float=3.14, double=67.8, - string="A", - pattern_without_delimiter="Aj", + string="a", + pattern_without_delimiter="AUR,rZ#UM/?R,Fp^l6$ARjbhJk C>", byte='YQ==', binary=open('/path/to/file', 'rb'), date="1970-01-01",