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

Improve support @constraint annotation mapping to OpenAPI spec #1541

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
37dd2b2
Improve support @constraint annotation mapping to OpenAPI spec
SachinAkash01 Sep 16, 2023
a8e7871
Improve support `@constraint` annotation mapping to OpenAPI spec
SachinAkash01 Sep 16, 2023
78c8af1
Update License Header on decimalMax.bal
SachinAkash01 Sep 17, 2023
64adfaa
Update openapi-cli/src/test/resources/ballerina-to-openapi/expected_g…
SachinAkash01 Sep 17, 2023
78a82d7
Update License Header on decimalMin.bal
SachinAkash01 Sep 17, 2023
5ed8777
Update License Header on floatMax.bal
SachinAkash01 Sep 17, 2023
35e99c3
Update License Header on floatMin.bal
SachinAkash01 Sep 17, 2023
7db9f2c
Update License Header on integerMax.bal
SachinAkash01 Sep 17, 2023
ef843a7
Update License Header on integerMin.bal
SachinAkash01 Sep 17, 2023
1c418bf
New line in the end on decimalMin.yaml
SachinAkash01 Sep 17, 2023
5041827
Update decimalMin.yaml
SachinAkash01 Sep 17, 2023
8ec04a4
Update decimalMin.yaml
SachinAkash01 Sep 17, 2023
aafd47f
New line in the end on floatMax.yaml
SachinAkash01 Sep 17, 2023
5b32a8f
Update floatMax.yaml
SachinAkash01 Sep 17, 2023
6137db1
New line in the end on floatMin.yaml
SachinAkash01 Sep 17, 2023
18eae13
New line in the end on integerMax.yaml
SachinAkash01 Sep 17, 2023
7b49fff
New line in the end on integerMin.yaml
SachinAkash01 Sep 17, 2023
a673877
Update: importing only relevant classes from the java package in Open…
SachinAkash01 Sep 17, 2023
7775636
Remove try-catch block, throw ParseException, and refactor setNumberC…
SachinAkash01 Sep 18, 2023
4ea88f0
Merge branch 'constraint_support' of https://github.com/SachinAkash01…
SachinAkash01 Sep 18, 2023
bdbe39b
Remove try-catch block, throw ParseException, and refactor setNumberC…
SachinAkash01 Sep 18, 2023
d77b8b5
Resolve checkstyle violations
SachinAkash01 Sep 18, 2023
e512220
Update openapi-bal-service/src/main/java/io/ballerina/openapi/convert…
SachinAkash01 Sep 18, 2023
a7a1f03
Tests for the record field type
SachinAkash01 Sep 18, 2023
7bf0af5
Merge branch 'constraint_support' of https://github.com/SachinAkash01…
SachinAkash01 Sep 18, 2023
409420e
Resolve [1541#discussion_r1328249916](https://github.com/ballerina-pl…
SachinAkash01 Sep 18, 2023
ef22c05
Remove additional lines above annotations on type definitions
SachinAkash01 Sep 18, 2023
1f639ce
Resolve line spaces
SachinAkash01 Sep 18, 2023
1c6cc89
Refactor: Change return type to void in setConstraintValueToSchema()
SachinAkash01 Sep 18, 2023
1d419c6
Update float and decimal return type tests in ResponseTests.java
SachinAkash01 Sep 18, 2023
df172ed
To-Do comment: error handling on integer constraints
SachinAkash01 Sep 19, 2023
956107b
Fix Tests: ModuleReferenceTests
SachinAkash01 Sep 19, 2023
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 @@ -18,24 +18,7 @@

package io.ballerina.openapi.converter.service;

import io.ballerina.compiler.api.symbols.ArrayTypeSymbol;
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
import io.ballerina.compiler.api.symbols.ConstantSymbol;
import io.ballerina.compiler.api.symbols.Documentable;
import io.ballerina.compiler.api.symbols.Documentation;
import io.ballerina.compiler.api.symbols.EnumSymbol;
import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol;
import io.ballerina.compiler.api.symbols.MapTypeSymbol;
import io.ballerina.compiler.api.symbols.ReadonlyTypeSymbol;
import io.ballerina.compiler.api.symbols.RecordFieldSymbol;
import io.ballerina.compiler.api.symbols.RecordTypeSymbol;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.api.symbols.SymbolKind;
import io.ballerina.compiler.api.symbols.TupleTypeSymbol;
import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol;
import io.ballerina.compiler.api.symbols.TypeDescKind;
import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol;
import io.ballerina.compiler.api.symbols.TypeSymbol;
import io.ballerina.compiler.api.symbols.UnionTypeSymbol;
import io.ballerina.compiler.api.symbols.*;
import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.ExpressionNode;
import io.ballerina.compiler.syntax.tree.IntersectionTypeDescriptorNode;
Expand Down Expand Up @@ -719,54 +702,107 @@ private ArraySchema handleArray(int arrayDimensions, Schema property, ArraySchem
}

/**
* This util uses to set the constraint value for relevant schema field.
* This util uses to set the integer constraint values for relevant schema field.
*/
private Schema setConstraintValueToSchema(ConstraintAnnotation constraintAnnot, Schema property) {

if (property instanceof ArraySchema) {
property.setMaxItems(constraintAnnot.getMaxLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMaxLength().get()) : null);
property.setMinItems(constraintAnnot.getMinLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMinLength().get()) : null);
} else {
property.setMaxLength(constraintAnnot.getMaxLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMaxLength().get()) : null);
property.setMinLength(constraintAnnot.getMinLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMinLength().get()) : null);
}
private Schema setIntegerConstraintValuesToSchema(ConstraintAnnotation constraintAnnot, Schema properties) {
BigDecimal minimum = null;
BigDecimal maximum = null;
if (constraintAnnot.getMinValue().isPresent()) {
minimum = BigDecimal.valueOf(Integer.parseInt(constraintAnnot.getMinValue().get()));
} else if (constraintAnnot.getMinValueExclusive().isPresent()) {
minimum = BigDecimal.valueOf(Integer.parseInt(constraintAnnot.getMinValueExclusive().get()));
properties.setExclusiveMinimum(true);
}

if (constraintAnnot.getMaxValue().isPresent()) {
maximum = BigDecimal.valueOf(Integer.parseInt(constraintAnnot.getMaxValue().get()));
} else if (constraintAnnot.getMaxValueExclusive().isPresent()) {
maximum = BigDecimal.valueOf(Integer.parseInt(constraintAnnot.getMaxValueExclusive().get()));
properties.setExclusiveMaximum(true);
}
properties.setMinimum(minimum);
properties.setMaximum(maximum);
return properties;
}

/**
* This util uses to set the number (float, double) constraint values for relevant schema field.
*/
private Schema setNumberConstraintValuesToSchema(ConstraintAnnotation constraintAnnot, Schema properties) {
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
BigDecimal minimum = null;
BigDecimal maximum = null;
try {
BigDecimal minimum = null;
BigDecimal maximum = null;
if (constraintAnnot.getMinValue().isPresent()) {
try {
minimum = BigDecimal.valueOf(Integer.parseInt(constraintAnnot.getMinValue().get()));
} catch (NumberFormatException e) {
minimum = BigDecimal.valueOf(NumberFormat.getInstance().parse(
constraintAnnot.getMinValue().get()).doubleValue());
}
minimum = BigDecimal.valueOf((NumberFormat.getInstance()
.parse(constraintAnnot.getMinValue().get()).doubleValue()));
} else if (constraintAnnot.getMinValueExclusive().isPresent()) {
minimum = BigDecimal.valueOf((NumberFormat.getInstance()
.parse(constraintAnnot.getMinValueExclusive().get()).doubleValue()));
properties.setExclusiveMinimum(true);
}

if (constraintAnnot.getMaxValue().isPresent()) {
try {
maximum = BigDecimal.valueOf(Integer.parseInt(constraintAnnot.getMaxValue().get()));
} catch (NumberFormatException e) {
maximum = BigDecimal.valueOf((NumberFormat.getInstance()
.parse(constraintAnnot.getMaxValue().get()).doubleValue()));
}

maximum = BigDecimal.valueOf((NumberFormat.getInstance()
.parse(constraintAnnot.getMaxValue().get()).doubleValue()));
} else if (constraintAnnot.getMaxValueExclusive().isPresent()) {
maximum = BigDecimal.valueOf((NumberFormat.getInstance()
.parse(constraintAnnot.getMaxValueExclusive().get()).doubleValue()));
properties.setExclusiveMaximum(true);
}
property.setMinimum(minimum);
property.setMaximum(maximum);

} catch (ParseException parserMessage) {
properties.setMinimum(minimum);
properties.setMaximum(maximum);
} catch (ParseException exception) {
DiagnosticMessages error = DiagnosticMessages.OAS_CONVERTOR_110;
ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(error.getCode(),
error.getDescription(), null, parserMessage.getMessage());
error.getDescription(), null, exception.getMessage());
diagnostics.add(diagnostic);
}
return properties;
}

return property;
/**
* This util uses to set the string constraint values for relevant schema field.
*/
private Schema setStringConstraintValuesToSchema(ConstraintAnnotation constraintAnnot, Schema properties) {
properties.setMaxLength(constraintAnnot.getMaxLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMaxLength().get()) : null);
properties.setMinLength(constraintAnnot.getMinLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMinLength().get()) : null);
return properties;
}

/**
* This util uses to set the array constraint values for relevant schema field.
*/
private Schema setArrayConstraintValuesToSchema(ConstraintAnnotation constraintAnnot, Schema properties) {
properties.setMaxItems(constraintAnnot.getMaxLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMaxLength().get()) : null);
properties.setMinItems(constraintAnnot.getMinLength().isPresent() ?
Integer.valueOf(constraintAnnot.getMinLength().get()) : null);
return properties;
}

/**
* This util uses to set the constraint values for relevant schema field.
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
*/
private Schema setConstraintValueToSchema(ConstraintAnnotation constraintAnnot, Schema properties) {
try {
if (properties instanceof ArraySchema) {
setArrayConstraintValuesToSchema(constraintAnnot, properties);
} else if (properties instanceof StringSchema){
setStringConstraintValuesToSchema(constraintAnnot, properties);
} else if (properties instanceof IntegerSchema) {
setIntegerConstraintValuesToSchema(constraintAnnot, properties);
} else if (properties instanceof NumberSchema) {
setNumberConstraintValuesToSchema(constraintAnnot, properties);
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (NumberFormatException exception) {
DiagnosticMessages error = DiagnosticMessages.OAS_CONVERTOR_110;
ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(error.getCode(),
error.getDescription(), null, exception.getMessage());
diagnostics.add(diagnostic);
}
return properties;
}

/**
Expand All @@ -776,8 +812,8 @@ private void extractedConstraintAnnotation(MetadataNode metadata,
ConstraintAnnotation.ConstraintAnnotationBuilder constraintBuilder) {
NodeList<AnnotationNode> annotations = metadata.annotations();
annotations.stream().filter(annot -> (annot.annotReference() instanceof QualifiedNameReferenceNode &&
((QualifiedNameReferenceNode) annot.annotReference()).modulePrefix().text()
.equals("constraint")))
((QualifiedNameReferenceNode) annot.annotReference()).modulePrefix().text()
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
.equals("constraint")))
.forEach(value -> {
Optional<MappingConstructorExpressionNode> fieldValues = value.annotValue();
if (fieldValues.isPresent()) {
Expand All @@ -791,7 +827,7 @@ private void extractedConstraintAnnotation(MetadataNode metadata,
ExpressionNode expressionNode = specificFieldNode.valueExpr().get();
SyntaxKind kind = expressionNode.kind();
if (kind == SyntaxKind.NUMERIC_LITERAL) {
String constraintValue = expressionNode.toString();
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
String constraintValue = expressionNode.toString().trim();
fillConstraintValue(constraintBuilder, name, constraintValue);
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,46 @@ public void testArrayType() throws IOException {
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/array.yaml");
}

@Test(description = "When the record field has integer (minValueExclusive) type")
public void testIntegerMinType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint/integerMin.bal");
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/integerMin.yaml");
}

@Test(description = "When the record field has integer (maxValueExclusive) type")
public void testIntegerMaxType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint/integerMax.bal");
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/integerMax.yaml");
}

@Test(description = "When the record field has float (minValueExclusive) type")
public void testFloatMinType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint/floatMin.bal");
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/floatMin.yaml");
}

@Test(description = "When the record field has float (maxValueExclusive) type")
public void testFloatMaxType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint/floatMax.bal");
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/floatMax.yaml");
}

@Test(description = "When the record field has decimal (minValueExclusive) type")
public void testDecimalMinType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint/decimalMin.bal");
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/decimalMin.yaml");
}

@Test(description = "When the record field has decimal (maxValueExclusive) type")
public void testDecimalMaxType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("constraint/decimalMax.bal");
//Compare generated yaml file with expected yaml content
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "constraint/decimalMax.yaml");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,22 +238,22 @@ public void testForNoContentReturnCode() throws IOException {

@Test(description = "When the response has float return type")
public void testResponseWithFloatReturnType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("response/float.bal");
Path ballerinaFilePath = RES_DIR.resolve("response/floatMin.bal");
OASContractGenerator openApiConverterUtils = new OASContractGenerator();
openApiConverterUtils.generateOAS3DefinitionsAllService(ballerinaFilePath, this.tempDir, null
, false);
Assert.assertTrue(openApiConverterUtils.getErrors().isEmpty());
compareWithGeneratedFile(ballerinaFilePath, "response/float.yaml");
compareWithGeneratedFile(ballerinaFilePath, "response/floatMin.yaml");
}

@Test(description = "When the response has decimal return type")
public void testResponseWithDecimalReturnType() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("response/decimal.bal");
Path ballerinaFilePath = RES_DIR.resolve("response/decimalMin.bal");
OASContractGenerator openApiConverterUtils = new OASContractGenerator();
openApiConverterUtils.generateOAS3DefinitionsAllService(ballerinaFilePath, this.tempDir, null
, false);
Assert.assertTrue(openApiConverterUtils.getErrors().isEmpty());
compareWithGeneratedFile(ballerinaFilePath, "response/decimal.yaml");
compareWithGeneratedFile(ballerinaFilePath, "response/decimalMin.yaml");
}

@Test(description = "When the response has byte[] return type")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ballerina/http;
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
import ballerina/constraint;

@constraint:Number {
maxValueExclusive: 5.55,
minValue: 2.55
}

public type Marks decimal;

public type School record {
Marks marks;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload School body) returns error? {
return;
}
}
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ballerina/http;
import ballerina/constraint;

@constraint:Number {
minValueExclusive: 2.55,
maxValue: 5.55
}

public type Marks decimal;

public type School record {
Marks marks;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload School body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ballerina/http;
import ballerina/constraint;

@constraint:Float {
maxValueExclusive: 10.5,
minValue: 2.5
}

SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
public type Rating float;

public type Hotel record {
Rating rate;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload Hotel body) returns error? {
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ballerina/http;
import ballerina/constraint;

@constraint:Float {
minValueExclusive: 2.5,
maxValue: 10.5
}

public type Rating float;

public type Hotel record {
Rating rate;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload Hotel body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ballerina/http;
import ballerina/constraint;

@constraint:Int{
maxValueExclusive: 100,
minValue: 10
}
public type Position int;

public type Child record {
Position position;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload Child body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ballerina/http;
import ballerina/constraint;

@constraint:Int{
minValueExclusive: 0,
maxValue: 50
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
}
public type Position int;

public type Child record {
Position position;
};

service /payloadV on new http:Listener(9090) {
resource function post pet(@http:Payload Child body) returns error? {
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ components:
type: number
format: float
PersonLimitItemsInteger:
maximum: 67.0
maximum: 67
type: integer
format: int32
Hobby:
Expand Down
Loading