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 29 commits
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 @@ -155,8 +155,9 @@ public void createComponentSchema(Map<String, Schema> schema, TypeSymbol typeSym
}
break;
case STRING:
schema.put(componentName, setConstraintValueToSchema(constraintAnnot,
new StringSchema().description(typeDoc)));
Schema stringSchema = new StringSchema().description(typeDoc);
setConstraintValueToSchema(constraintAnnot, stringSchema);
schema.put(componentName, stringSchema);
components.setSchemas(schema);
break;
case JSON:
Expand All @@ -165,25 +166,28 @@ public void createComponentSchema(Map<String, Schema> schema, TypeSymbol typeSym
components.setSchemas(schema);
break;
case INT:
schema.put(componentName, setConstraintValueToSchema(constraintAnnot,
new IntegerSchema().description(typeDoc)));
Schema intSchema = new IntegerSchema().description(typeDoc);
setConstraintValueToSchema(constraintAnnot, intSchema);
schema.put(componentName, intSchema);
components.setSchemas(schema);
break;
case DECIMAL:
schema.put(componentName, setConstraintValueToSchema(constraintAnnot,
new NumberSchema().format(DOUBLE).description(typeDoc)));
Schema decimalSchema = new NumberSchema().format(DOUBLE).description(typeDoc);
setConstraintValueToSchema(constraintAnnot, decimalSchema);
schema.put(componentName, decimalSchema);
components.setSchemas(schema);
break;
case FLOAT:
schema.put(componentName, setConstraintValueToSchema(constraintAnnot,
new NumberSchema().format(FLOAT).description(typeDoc)));
Schema floatSchema = new NumberSchema().format(FLOAT).description(typeDoc);
setConstraintValueToSchema(constraintAnnot, floatSchema);
schema.put(componentName, floatSchema);
components.setSchemas(schema);
break;
case ARRAY:
case TUPLE:
ArraySchema arraySchema = mapArrayToArraySchema(schema, type, componentName);
schema.put(componentName, setConstraintValueToSchema(constraintAnnot,
arraySchema.description(typeDoc)));
setConstraintValueToSchema(constraintAnnot, arraySchema.description(typeDoc));
schema.put(componentName, arraySchema);
components.setSchemas(schema);
break;
case UNION:
Expand Down Expand Up @@ -481,7 +485,7 @@ private Schema handleMapType(Map<String, Schema> schema, String componentName, S
}

/**
* This function uses to handle the field datatype has TypeReference(ex: Record or Enum).
* This function is used to handle the field datatype has TypeReference(ex: Record or Enum).
*/
private Schema<?> handleTypeReference(Map<String, Schema> schema, TypeReferenceTypeSymbol typeReferenceSymbol,
Schema<?> property, boolean isCyclicRecord) {
Expand All @@ -499,7 +503,7 @@ private Schema<?> handleTypeReference(Map<String, Schema> schema, TypeReferenceT
}

/**
* This function uses to generate schema when field has union type as data type.
* This function is used to generate schema when field has union type as data type.
* <pre>
* type Pet record {
* Dog|Cat type;
Expand Down Expand Up @@ -568,7 +572,7 @@ private boolean isSameRecord(String parentComponentName, TypeReferenceTypeSymbol
}

/**
* This function generate oneOf composed schema for record fields.
* This function generates oneOf composed schema for record fields.
*/
private Schema generateOneOfSchema(Schema property, List<Schema> properties) {
boolean isTypeReference = properties.size() == 1 && properties.get(0).get$ref() == null;
Expand Down Expand Up @@ -685,7 +689,7 @@ private Schema getSchemaForUnionType(UnionTypeSymbol symbol, Schema symbolProper
}

/**
* This util function is to handle the type reference symbol is record type or enum type.
* This util function is used to handle the type reference symbol is record type or enum type.
*/
private Schema getSchemaForTypeReferenceSymbol(TypeSymbol referenceType, Schema symbolProperty,
String componentName, Map<String, Schema> schema) {
Expand Down Expand Up @@ -719,58 +723,104 @@ private ArraySchema handleArray(int arrayDimensions, Schema property, ArraySchem
}

/**
* This util uses to set the constraint value for relevant schema field.
* This util is used to set the integer constraint values for relevant schema field.
*/
private Schema setConstraintValueToSchema(ConstraintAnnotation constraintAnnot, Schema property) {
private void 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);
}

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);
}
/**
* This util is used to set the number (float, double) constraint values for relevant schema field.
*/
private void setNumberConstraintValuesToSchema(ConstraintAnnotation constraintAnnot, Schema properties)
throws ParseException {
BigDecimal minimum = null;
BigDecimal maximum = null;
if (constraintAnnot.getMinValue().isPresent()) {
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()) {
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);
}
properties.setMinimum(minimum);
properties.setMaximum(maximum);
}

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());
}
}
/**
* This util is used to set the string constraint values for relevant schema field.
*/
private void 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);
}

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()));
}
/**
* This util is used to set the array constraint values for relevant schema field.
*/
private void 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);
}

/**
* This util is used to set the constraint values for relevant schema field.
*/
private void 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 {
/**
* Ballerina currently supports only Int, Number (Float, Decimal), String & Array constraints,
* with plans to extend constraint support in the future.
*/
setNumberConstraintValuesToSchema(constraintAnnot, properties);
SachinAkash01 marked this conversation as resolved.
Show resolved Hide resolved
}
property.setMinimum(minimum);
property.setMaximum(maximum);

} catch (ParseException parserMessage) {
} catch (ParseException parseException) {
DiagnosticMessages error = DiagnosticMessages.OAS_CONVERTOR_110;
ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(error.getCode(),
error.getDescription(), null, parserMessage.getMessage());
error.getDescription(), null, parseException.getMessage());
diagnostics.add(diagnostic);
}

return property;
}

/**
* This util uses to extract the annotation values in `@constraint` and store it in builder.
* This util is used to extract the annotation values in `@constraint` and store it in builder.
*/
private void extractedConstraintAnnotation(MetadataNode metadata,
ConstraintAnnotation.ConstraintAnnotationBuilder constraintBuilder) {
Expand All @@ -791,8 +841,8 @@ 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
fillConstraintValue(constraintBuilder, name, constraintValue);
fillConstraintValue(constraintBuilder, name, expressionNode
.toString().trim());
}
}
}
Expand All @@ -802,7 +852,7 @@ private void extractedConstraintAnnotation(MetadataNode metadata,
}

/**
* This util uses to build the constraint builder with available constraint annotation field value.
* This util is used to build the constraint builder with available constraint annotation field value.
*/
private void fillConstraintValue(ConstraintAnnotation.ConstraintAnnotationBuilder constraintBuilder,
String name, String constraintValue) {
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,34 @@
// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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.

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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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.

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;
}
}
Loading
Loading