[Ruby] Fix incorrect escaping of Ruby forward slashes (#16474)

* [Ruby] Test correct escaping of pattern sequences

Ruby is not correctly escaping pattern sequences containing forward
slashes in their definition. This commit adds tests that verify the
correct behaviour of the code generator.

See issue #5582.

* [Ruby] Correctly escape patterns containing forward slashes

Ruby regexs are always generated as match patterns enclosed in slash
characters (i.e. using the `/pattern/` syntax). Regular expressions
defined in the OpenAPI declaration via the `pattern` attribute follow
[ECMA 262](https://262.ecma-international.org/5.1/#sec-15.10.1) which
means they already include the correct escaping of forward slashes as
far as Ruby is concerned.

The current Ruby codegen is incorrectly escaping all forward slashes,
which ultimately causes the generated code to include additional
incorrect escape sequences which cause the generated file to have an
invalid syntax.

This commit ports the same fix introduced in #1539 for the Python
codegen, as both Ruby and Python use perl-flavored regular expressions
so they behave in the same way when it comes to escaping forward
slashes.

Fixes #5582.
This commit is contained in:
Ivan Giuliani 2023-09-05 13:12:29 +01:00 committed by GitHub
parent 8608103c9f
commit 065b48177b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 0 deletions

View File

@ -204,6 +204,20 @@ abstract public class AbstractRubyCodegen extends DefaultCodegen implements Code
return addRegularExpressionDelimiter(pattern); return addRegularExpressionDelimiter(pattern);
} }
@Override
public String addRegularExpressionDelimiter(String pattern) {
if (StringUtils.isEmpty(pattern)) {
return pattern;
}
if (!pattern.matches("^/.*")) {
// Perform a negative lookbehind on each `/` to ensure that it is escaped.
return "/" + pattern.replaceAll("(?<!\\\\)\\/", "\\\\/") + "/";
}
return pattern;
}
@Override @Override
public String toParamName(String name) { public String toParamName(String name) {
// obtain the name from parameterNameMapping directly if provided // obtain the name from parameterNameMapping directly if provided

View File

@ -712,4 +712,33 @@ public class RubyClientCodegenTest {
Assert.assertFalse(cp2.required); Assert.assertFalse(cp2.required);
Assert.assertEquals(cp2.dataType, "VerySpecialStringInRuby"); Assert.assertEquals(cp2.dataType, "VerySpecialStringInRuby");
} }
@Test(description = "test regex patterns")
public void testRegularExpressionOpenAPISchemaVersion3() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue_1517.yaml");
final RubyClientCodegen codegen = new RubyClientCodegen();
codegen.setOpenAPI(openAPI);
final String path = "/ping";
final Operation p = openAPI.getPaths().get(path).getGet();
final CodegenOperation op = codegen.fromOperation(path, "get", p, null);
// pattern_no_forward_slashes '^pattern$'
Assert.assertEquals(op.allParams.get(0).pattern, "/^pattern$/");
// pattern_two_slashes '/^pattern$/'
Assert.assertEquals(op.allParams.get(1).pattern, "/^pattern$/");
// pattern_dont_escape_backslash '/^pattern\d{3}$/'
Assert.assertEquals(op.allParams.get(2).pattern, "/^pattern\\d{3}$/");
// pattern_dont_escape_escaped_forward_slash '/^pattern\/\d{3}$/'
Assert.assertEquals(op.allParams.get(3).pattern, "/^pattern\\/\\d{3}$/");
// pattern_escape_unescaped_forward_slash '^pattern/\d{3}$'
Assert.assertEquals(op.allParams.get(4).pattern, "/^pattern\\/\\d{3}$/");
// pattern_with_modifiers '/^pattern\d{3}$/i
Assert.assertEquals(op.allParams.get(5).pattern, "/^pattern\\d{3}$/i");
// not testing pattern_with_backslash_after_bracket '/^[\pattern\d{3}$/i'
// as "/^[\\pattern\\d{3}$/i" is invalid regex because [ is not escaped and there is no closing ]
// Assert.assertEquals(op.allParams.get(6).pattern, "/^[\\pattern\\d{3}$/i");
// alternation_with_forward_slash '/ax$|/bx$'
Assert.assertEquals(op.allParams.get(7).pattern, "/ax$|/bx$");
// patten_starts_ends_with_slash '/root/'
Assert.assertEquals(op.allParams.get(8).pattern, "/root/");
}
} }

View File

@ -50,6 +50,17 @@ paths:
schema: schema:
type: string type: string
pattern: '/^[\pattern\d{3}$/i' pattern: '/^[\pattern\d{3}$/i'
- name: alternation_with_forward_slash
in: header
schema:
type: string
pattern: '/ax$|/bx$'
- name: patten_starts_ends_with_slash
in: header
schema:
type: string
pattern: '/root/'
description: 'Should match only /root/ but not root'
responses: responses:
'200': '200':