Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Fixes regex pattern handling #20

Merged
merged 6 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<Object> results = getPatternAndModifiers(pattern);
String extractedPattern = (String) results.get(0);
List<String> regexFlags = (List<String>) 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
Expand Down Expand Up @@ -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<Object> 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
Expand Down Expand Up @@ -2482,35 +2485,61 @@ public ModelsMap postProcessModels(ModelsMap objs) {
return postProcessModelsEnum(objs);
}

/**
* @param pattern the regex pattern
* @return List<String pattern, List<String modifer>>
*/
private List<Object> 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<String> modifiers = new ArrayList<String>();
// 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<String>());
}

/*
* 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
* https://docs.python.org/2/howto/regex.html#compilation-flags for details.
*/
public void postProcessPattern(String pattern, Map<String, Object> 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<String> modifiers = new ArrayList<String>();

if (i != -1) {
for (char c : pattern.substring(i).toCharArray()) {
if (regexModifiers.containsKey(c)) {
String modifier = regexModifiers.get(c);
modifiers.add(modifier);
}
}
}
List<Object> results = getPatternAndModifiers(pattern);
String extractedPattern = (String) results.get(0);
List<String> modifiers = (List<String>) 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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down