Skip to content
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

[master] Fix for providing support for pattern constraint in openapi to ballerina code generation #1340

Merged
merged 14 commits into from
May 18, 2023
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 @@ -50,6 +50,16 @@ public void generateHandleUnsupportedData() throws IOException, BallerinaOpenAp
syntaxTree = ballerinaSchemaGenerator.generateSyntaxTree();
}

@Test(description = "Functionality tests for swagger parser behaviour when the regex is having syntax errors",
expectedExceptions = BallerinaOpenApiException.class,
expectedExceptionsMessageRegExp = "OpenAPI definition has errors: .*")
public void testInvalidRegexPatterns() throws IOException, BallerinaOpenApiException {
Path definitionPath = RES_DIR.resolve("swagger/invalid_pattern_string.yaml");
OpenAPI openAPI = GeneratorUtils.getOpenAPIFromOpenAPIV3Parser(definitionPath);
BallerinaTypesGenerator ballerinaSchemaGenerator = new BallerinaTypesGenerator(openAPI);
syntaxTree = ballerinaSchemaGenerator.generateSyntaxTree();
}

//Get string as a content of ballerina file
private String getStringFromGivenBalFile(Path expectedServiceFile, String s) throws IOException {
Stream<String> expectedServiceLines = Files.lines(expectedServiceFile.resolve(s));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ public void testNullableRefTypesWithConstraint() throws IOException, BallerinaOp
assertFalse(hasErrors);
}

@Test(description = "Test for schema properties having pattern constraint.")
public void testStringSchemaPropertyWithPattern() throws IOException, BallerinaOpenApiException,
FormatterException {
OpenAPI openAPI = GeneratorUtils.normalizeOpenAPI(RES_DIR.resolve("swagger/constraint" +
"/pattern_string.yaml"), true);
BallerinaTypesGenerator ballerinaSchemaGenerator = new BallerinaTypesGenerator(openAPI);
SyntaxTree syntaxTree = ballerinaSchemaGenerator.generateSyntaxTree();
TestUtils.compareGeneratedSyntaxTreewithExpectedSyntaxTree(
"schema/ballerina/constraint/string_pattern.bal", syntaxTree);
List<Diagnostic> diagnostics = getDiagnostics(syntaxTree);
boolean hasErrors = diagnostics.stream()
.anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity()));
assertFalse(hasErrors);
}

@AfterMethod
private void deleteGeneratedFiles() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.ballerina.openapi.generators.schema;

import io.ballerina.compiler.syntax.tree.SyntaxTree;
import io.ballerina.openapi.core.GeneratorUtils;
import io.ballerina.openapi.core.exception.BallerinaOpenApiException;
import io.ballerina.openapi.core.generators.schema.BallerinaTypesGenerator;
import io.ballerina.openapi.generators.common.TestUtils;
import io.ballerina.tools.diagnostics.Diagnostic;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;
import io.swagger.v3.oas.models.OpenAPI;
import org.ballerinalang.formatter.core.FormatterException;
import org.testng.annotations.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

import static io.ballerina.openapi.generators.common.TestUtils.getDiagnostics;
import static org.testng.Assert.assertFalse;

/**
* This test class contains negative tests related to constraint validation.
*/
public class NegativeConstraintTests {
private static final Path RES_DIR = Paths.get("src/test/resources/generators/schema").toAbsolutePath();

@Test(description = "Tests for non-string type record field has invalid pattern constraint." +
"There is no pattern validation within swagger parser although swagger support for only string value to " +
"have regular pattern" +
"(https://swagger.io/docs/specification/data-models/data-types/#string:~:text=The%20pattern%20keyword%20)" +
"Therefore, code generation for patterns with non-string types is silently ignored here. This will be" +
" handled in a separate PR in the future.")
public void testNonStringSchemaPropertyWithPattern() throws IOException, BallerinaOpenApiException,
FormatterException {
OpenAPI openAPI = GeneratorUtils.normalizeOpenAPI(RES_DIR.resolve("swagger/constraint" +
"/pattern_except_string_type.yaml"), true);
BallerinaTypesGenerator ballerinaSchemaGenerator = new BallerinaTypesGenerator(openAPI);
SyntaxTree syntaxTree = ballerinaSchemaGenerator.generateSyntaxTree();
TestUtils.compareGeneratedSyntaxTreewithExpectedSyntaxTree(
"schema/ballerina/constraint/pattern_except_string_type.bal", syntaxTree);
List<Diagnostic> diagnostics = getDiagnostics(syntaxTree);
boolean hasErrors = diagnostics.stream()
.anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity()));
assertFalse(hasErrors);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ballerina/constraint;

public type PersonHobbyItemsInteger int;

public type Person record {
int name?;
PersonHobbyItemsInteger[] hobby?;
@constraint:Int {maxValue: 5}
int id;
decimal salary?;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ballerina/constraint;

@constraint:String {pattern: re `[ a-zA-Z0-9/.+!@#$%^&*()+\- ]*`}
public type PersonHobbyItemsString string;

public type Person record {
@constraint:String {maxLength: 14}
string name?;
PersonHobbyItemsString[] hobby?;
@constraint:Int {maxValue: 5}
int id;
@constraint:String {pattern: re `^[a-zA-Z0-9_]+$`}
string salary?;
string net?;
int count?;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
openapi: 3.0.1
info:
title: Service Openapi Yaml
version: 2.0.0
servers:
- url: "{server}:{port}/"
variables:
server:
default: http://localhost
port:
default: "9090"
paths:
/greeting:
put:
summary: A resource for generating greetings
operationId: getGreeting
parameters:
- name: name
in: query
description: the input string name
required: true
schema:
type: string
requestBody:
content:
text/plain:
schema:
$ref: "#/components/schemas/Person"
responses:
"200":
description: Ok
components:
schemas:
Person:
type: object
required:
- id
properties:
name:
type: string
pattern: [a-zA-Z0-9,\\.\']
hobby:
type: string
id:
type: integer
maximum: 5
salary:
type: string
pattern: ^[a-zA-Z0-9_]+$
net:
type: string
count:
type: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
openapi: 3.0.1
info:
title: Service Openapi Yaml
version: 2.0.0
servers:
- url: "{server}:{port}/"
variables:
server:
default: http://localhost
port:
default: "9090"
paths:
/greeting:
put:
summary: A resource for generating greetings
operationId: getGreeting
parameters:
- name: name
in: query
description: the input string name
required: true
schema:
type: string
requestBody:
content:
text/plain:
schema:
$ref: "#/components/schemas/Person"
responses:
"200":
description: Ok
components:
schemas:
Person:
type: object
required:
- id
properties:
name:
type: integer
pattern: '[ a-zA-Z0-9/.+!@#$%^&*()+\- ]*'
hobby:
type: array
items:
type: integer
pattern: '[ a-zA-Z0-9/.+!@#$%^&*()+\- ]*'
id:
type: integer
maximum: 5
salary:
type: number
pattern: ^[a-zA-Z0-9_]+$
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
openapi: 3.0.1
info:
title: Service Openapi Yaml
version: 2.0.0
servers:
- url: "{server}:{port}/"
variables:
server:
default: http://localhost
port:
default: "9090"
paths:
/greeting:
put:
summary: A resource for generating greetings
operationId: getGreeting
parameters:
- name: name
in: query
description: the input string name
required: true
schema:
type: string
requestBody:
content:
text/plain:
schema:
$ref: "#/components/schemas/Person"
responses:
"200":
description: Ok
components:
schemas:
Person:
type: object
required:
- id
properties:
name:
type: string
pattern: ^(?!(.*[\"\*\\\>\<\?\/\:\|]+.*)|(.*[\.]?.*[\.]+$)|(.*[ ]+$)) # Use case 01 : This pattern can not support via Ballerina regex
maxLength: 14
hobby:
type: array
items:
type: string
pattern: '[ a-zA-Z0-9/.+!@#$%^&*()+\- ]*'
id:
type: integer
maximum: 5
salary:
type: string
pattern: ^[a-zA-Z0-9_]+$
net:
type: string
pattern: ^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$ # Use case 02 : This pattern can not support via Ballerina regex
count:
type: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
openapi: 3.0.1
info:
title: Service Openapi Yaml
version: 2.0.0
servers:
- url: "{server}:{port}/"
variables:
server:
default: http://localhost
port:
default: "9090"
paths:
/greeting:
put:
summary: A resource for generating greetings
operationId: getGreeting
parameters:
- name: name
in: query
description: the input string name
required: true
schema:
type: string
requestBody:
content:
text/plain:
schema:
$ref: "#/components/schemas/Person"
responses:
"200":
description: Ok
components:
schemas:
Person:
type: object
required:
- id
properties:
name:
type: string
pattern: "[a-zA-Z0-9,\\.\']"
hobby:
type: string
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ components:
type: object
properties:
username:
pattern: ^[a-zA-Z0-9_\-]+$
type: string
description: Username of the account
nickname:
Expand Down
1 change: 1 addition & 0 deletions openapi-cli/src/test/resources/testng.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ under the License.
<class name="io.ballerina.openapi.generators.schema.MapSchemaTests"/>
<class name="io.ballerina.openapi.generators.schema.MapSchemaNegativeTests"/>
<class name="io.ballerina.openapi.generators.schema.EnumGenerationTests"/>
<class name="io.ballerina.openapi.generators.schema.NegativeConstraintTests"/>
<class name="io.ballerina.openapi.generators.testcases.BallerinaTestGeneratorTests"/>
<class name="io.ballerina.openapi.generators.client.OneOfResponsesTests"/>
<class name="io.ballerina.openapi.generators.openapi.DataTypeTests"/>
Expand Down
1 change: 1 addition & 0 deletions openapi-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {
implementation "org.ballerinalang:formatter-core"
implementation "org.ballerinalang:ballerina-cli"
implementation "org.ballerinalang:ballerina-tools-api"
implementation "org.ballerinalang:ballerina-runtime"
implementation "io.ballerina.stdlib:http-native"
implementation "com.google.code.findbugs:jsr305"
testImplementation "org.testng:testng"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,12 @@ public static OpenAPI getOpenAPIFromOpenAPIV3Parser(Path definitionPath) throws
if (parseResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) {
throw new BallerinaOpenApiException(ErrorMessages.unsupportedOpenAPIVersion());
}

StringBuilder errorMessage = new StringBuilder("OpenAPI definition has errors: \n");
for (String message : parseResult.getMessages()) {
errorMessage.append(message).append(LINE_SEPARATOR);
}

throw new BallerinaOpenApiException(errorMessage.toString());
}
return parseResult.getOpenAPI();
Expand Down Expand Up @@ -743,7 +745,8 @@ private static boolean isConstraintExists(Schema<?> propertyValue) {
propertyValue.getMaxItems() != null ||
propertyValue.getMinItems() != null ||
propertyValue.getExclusiveMinimum() != null ||
propertyValue.getExclusiveMaximum() != null;
propertyValue.getExclusiveMaximum() != null ||
propertyValue.getPattern() != null;
}

/**
Expand Down
Loading