Skip to content

issue-9509 adding support to v3. Escaping for ref defined patterns #1346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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 @@ -3185,7 +3185,7 @@ private void addVars(CodegenModel codegenModel, List<CodegenProperty> vars, Map<
if (this.openAPI == null) {
LOGGER.warn("open api utility object was not properly set.");
} else {
OpenAPIUtil.addPropertiesFromRef(this.openAPI, propertySchema, codegenProperty);
OpenAPIUtil.addPropertiesFromRef(this.openAPI, propertySchema, codegenProperty, this::toRegularExpression);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want to use toRegularExpression defined in child classes like AbstractJavaCodegen or TypeScriptAxiosClientCodegen.
Thus, we pass it as a Function<T,T>(UnaryOperator<String>) instead of implementing it in OpenAPIUtil

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.swagger.codegen.v3.CodegenProperty;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import java.util.function.*;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
Expand All @@ -13,7 +14,7 @@

public class OpenAPIUtil {

public static void addPropertiesFromRef(OpenAPI openAPI, Schema refSchema, CodegenProperty codegenProperty) {
public static void addPropertiesFromRef(OpenAPI openAPI, Schema refSchema, CodegenProperty codegenProperty, UnaryOperator<String> toRegularExpression) {
final Map<String, Schema> allSchemas = openAPI.getComponents().getSchemas();
if (allSchemas == null || allSchemas.isEmpty()) {
return;
Expand All @@ -23,7 +24,7 @@ public static void addPropertiesFromRef(OpenAPI openAPI, Schema refSchema, Codeg
return;
}
if (StringUtils.isBlank(codegenProperty.pattern)) {
codegenProperty.pattern = schema.getPattern();
codegenProperty.pattern = toRegularExpression.apply(schema.getPattern());
}
codegenProperty.minLength = schema.getMinLength();
codegenProperty.maxLength = schema.getMaxLength();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package io.swagger.codegen.v3.generators;

import io.swagger.codegen.v3.CodegenArgument;
import io.swagger.codegen.v3.CodegenConstants;
import io.swagger.codegen.v3.CodegenOperation;
import io.swagger.codegen.v3.CodegenParameter;
import io.swagger.codegen.v3.CodegenProperty;
import io.swagger.codegen.v3.CodegenResponse;
import io.swagger.codegen.v3.CodegenType;
import io.swagger.codegen.v3.*;
import io.swagger.codegen.v3.generators.java.*;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
Expand All @@ -23,19 +18,18 @@
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.OpenAPIV3Parser;

import java.util.*;
import org.apache.commons.lang3.StringEscapeUtils;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

public class DefaultCodegenConfigTest {

private static final String SSN_ESCAPED_PATTERN = "^\\\\d{3}-\\\\d{2}-\\\\d{4}$";

@Test
public void testInitialValues() throws Exception {
final DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();
Expand Down Expand Up @@ -107,7 +101,7 @@ public void testFromOperation_BodyParamsUnique() {
PathItem dummyPath = new PathItem()
.post(new Operation())
.get(new Operation());

OpenAPI openAPI = new OpenAPI()
.path("dummy", dummyPath);

Expand All @@ -121,7 +115,7 @@ public void testFromOperation_BodyParamsUnique() {
operation.setRequestBody(body);
Parameter param = new Parameter().in("query").name("testParameter");
operation.addParametersItem(param);

CodegenOperation codegenOperation = codegen.fromOperation("/path", "GET", operation, null, openAPI);

Assert.assertEquals(true, codegenOperation.allParams.get(0).getVendorExtensions().get("x-has-more"));
Expand All @@ -137,7 +131,7 @@ public void testFromOperation_BodyParamsUnique() {
@Test(dataProvider = "testGetCollectionFormatProvider")
public void testGetCollectionFormat(Parameter.StyleEnum style, Boolean explode, String expectedCollectionFormat) {
final DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();

ArraySchema paramSchema = new ArraySchema()
.items(new IntegerSchema());
Parameter param = new Parameter()
Expand All @@ -146,12 +140,12 @@ public void testGetCollectionFormat(Parameter.StyleEnum style, Boolean explode,
.schema(paramSchema)
.style(style)
.explode(explode);

CodegenParameter codegenParameter = codegen.fromParameter(param, new HashSet<>());

Assert.assertEquals(codegenParameter.collectionFormat, expectedCollectionFormat);
}

@DataProvider(name = "testGetCollectionFormatProvider")
public Object[][] provideData_testGetCollectionFormat() {
// See: https://swagger.io/docs/specification/serialization/#query
Expand All @@ -160,28 +154,28 @@ public Object[][] provideData_testGetCollectionFormat() {
{ Parameter.StyleEnum.FORM, null, "multi" },
{ null, Boolean.TRUE, "multi" },
{ Parameter.StyleEnum.FORM, Boolean.TRUE, "multi" },

{ null, Boolean.FALSE, "csv" },
{ Parameter.StyleEnum.FORM, Boolean.FALSE, "csv" },

{ Parameter.StyleEnum.SPACEDELIMITED, Boolean.TRUE, "multi" },
{ Parameter.StyleEnum.SPACEDELIMITED, Boolean.FALSE, "space" },
{ Parameter.StyleEnum.SPACEDELIMITED, null, "multi" },

{ Parameter.StyleEnum.PIPEDELIMITED, Boolean.TRUE, "multi" },
{ Parameter.StyleEnum.PIPEDELIMITED, Boolean.FALSE, "pipe" },
{ Parameter.StyleEnum.PIPEDELIMITED, null, "multi" },
};
}

/**
* Tests that {@link DefaultCodegenConfig#fromOperation(String, String, Operation, java.util.Map, OpenAPI)} correctly
* resolves the consumes list when the request body is specified via reference rather than inline.
*/
@Test
public void testRequestBodyRefConsumesList() {
final OpenAPI openAPI = new OpenAPIV3Parser().read("src/test/resources/3_0_0/requestBodyRefTest.json");
final P_DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();
final P_DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();
final String path = "/test/requestBodyRefTest";
final Operation op = openAPI.getPaths().get(path).getPost();
final CodegenOperation codegenOp = codegen.fromOperation(path, "post", op, openAPI.getComponents().getSchemas(), openAPI);
Expand All @@ -196,28 +190,28 @@ public void testRequestBodyRefConsumesList() {
/**
* Tests when a 'application/x-www-form-urlencoded' request body is marked as required that all form
* params are also marked as required.
*
*
* @see #testOptionalFormParams()
*/
@Test
public void testRequiredFormParams() {
// Setup
final P_DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();
final P_DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();

final OpenAPI openAPI = new OpenAPIV3Parser().read("src/test/resources/3_0_0/requiredFormParamsTest.yaml");
final String path = "/test_required";

final Operation op = openAPI.getPaths().get(path).getPost();
Assert.assertNotNull(op);

// Test
final CodegenOperation codegenOp = codegen.fromOperation(path, "post", op, openAPI.getComponents().getSchemas(), openAPI);

// Verification
List<CodegenParameter> formParams = codegenOp.getFormParams();
Assert.assertNotNull(formParams);
Assert.assertEquals(formParams.size(), 2);

for (CodegenParameter formParam : formParams) {
Assert.assertTrue(formParam.getRequired(), "Form param '" + formParam.getParamName() + "' is not required.");
}
Expand All @@ -233,28 +227,28 @@ public void testRequiredFormParams() {
/**
* Tests when a 'application/x-www-form-urlencoded' request body is marked as optional that all form
* params are also marked as optional.
*
*
* @see #testRequiredFormParams()
*/
@Test
public void testOptionalFormParams() {
// Setup
final P_DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();
final P_DefaultCodegenConfig codegen = new P_DefaultCodegenConfig();

final OpenAPI openAPI = new OpenAPIV3Parser().read("src/test/resources/3_0_0/requiredFormParamsTest.yaml");
final String path = "/test_optional";

final Operation op = openAPI.getPaths().get(path).getPost();
Assert.assertNotNull(op);

// Test
final CodegenOperation codegenOp = codegen.fromOperation(path, "post", op, openAPI.getComponents().getSchemas(), openAPI);

// Verification
List<CodegenParameter> formParams = codegenOp.getFormParams();
Assert.assertNotNull(formParams);
Assert.assertEquals(formParams.size(), 2);

for (CodegenParameter formParam : formParams) {
Assert.assertFalse(formParam.getRequired(), "Form param '" + formParam.getParamName() + "' is required.");
}
Expand Down Expand Up @@ -318,6 +312,46 @@ public void testCommonPrefix(List<Object> vars, String expectedPrefix) {
Assert.assertEquals(codegen.findCommonPrefixOfVars(vars), expectedPrefix);
}

@Test
public void verifyProperJavaEscapingForRefSchemaPatterns() {
//given java client codegen
final AbstractJavaCodegen codegen = new JavaClientCodegen();

ApiResponse apiResponse = new ApiResponse();
Header inlineHeader = new Header().description("This is header1").schema(new Schema().type("string").example("header_val"));
apiResponse.addHeaderObject("header1", inlineHeader);
OpenAPI openAPI = new OpenAPI().components(new Components().responses(new HashMap<>()));
openAPI.getComponents().addHeaders("ref-header1", inlineHeader);
Map<String, Schema> allSchemas = new HashMap<>();
allSchemas.put("Url", buildStringSchemaWithPattern());
openAPI.getComponents().setSchemas(allSchemas);
codegen.preprocessOpenAPI(openAPI);

HashMap<String, Schema> properties = new HashMap<>();
Schema<Object> refSchema = new Schema<>();
refSchema.set$ref("#/components/schemas/Url");
properties.put("url", refSchema);
CodegenModel codegenModel = new CodegenModel();

//when
codegen.addVars(codegenModel, properties, new ArrayList<>());

//then
Assert.assertNotNull(codegenModel);
Assert.assertEquals(codegenModel.vars.size(), 1);
CodegenProperty urlProperty = codegenModel.vars.get(0);
Assert.assertEquals(urlProperty.getName(), "url");
//verifying java pattern is properly escaped
Assert.assertEquals(urlProperty.getPattern(), SSN_ESCAPED_PATTERN);
Copy link
Author

@evgeniimv evgeniimv May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically we verify that ^\d{3}-\d{2}-\d{4}$ pattern is transformed to ^\\d{3}-\\d{2}-\\d{4}$ when AbstractJavaCodegen is used

}

private Schema buildStringSchemaWithPattern() {
Schema schema = new Schema();
schema.setType("string");
// manually apply unescapeJava as if it was parsed from the source file
schema.setPattern(StringEscapeUtils.unescapeJava(SSN_ESCAPED_PATTERN));
return schema;
}
@DataProvider(name = "testCommonPrefixProvider")
public Object[][] provideData_testCommonPrefix() {
return new Object[][]{
Expand Down