From 5e6c18d6b6a198c5f078da730fcc87d33354aaab Mon Sep 17 00:00:00 2001 From: lnash94 Date: Tue, 12 Nov 2024 09:41:51 +0530 Subject: [PATCH 1/9] [Automated] Update the toml files for client native tests --- openapi-client-native/ballerina-tests/Dependencies.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openapi-client-native/ballerina-tests/Dependencies.toml b/openapi-client-native/ballerina-tests/Dependencies.toml index a0742b639..d502962cb 100644 --- a/openapi-client-native/ballerina-tests/Dependencies.toml +++ b/openapi-client-native/ballerina-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240806-083400-aabac46a" +distribution-version = "2201.10.0" [[package]] org = "ballerina" @@ -75,7 +75,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.12.0" +version = "2.12.2" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, @@ -233,7 +233,7 @@ dependencies = [ [[package]] org = "ballerina" name = "mime" -version = "2.10.0" +version = "2.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From 370b3628aff37a80677d1468dc7190aec09ff0b3 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Tue, 12 Nov 2024 11:27:21 +0530 Subject: [PATCH 2/9] Add fix for field overriding issue in the included records --- .../service/mapper/type/RecordTypeMapper.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index 04db223f9..bc79c4fcd 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -120,7 +120,18 @@ static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components c .typeDescriptor(); Map includedRecordFieldMap = includedRecordTypeSymbol.fieldDescriptors(); for (Map.Entry includedRecordField : includedRecordFieldMap.entrySet()) { - recordFieldMap.remove(includedRecordField.getKey()); + //logic comes here i guess + RecordFieldSymbol recordFieldSymbol = recordFieldMap.get(includedRecordField.getKey()); + RecordFieldSymbol includedRecordFieldValue = includedRecordField.getValue(); + if (recordFieldSymbol != null) { + //check for the types + if (includedRecordFieldValue.typeDescriptor().equals(recordFieldSymbol.typeDescriptor())) { + // check for the default values availability + if (!recordFieldSymbol.hasDefaultValue()) { + recordFieldMap.remove(includedRecordField.getKey()); + } + } + } } } } From 8c851f7c8aee85339142c6a96422f1b35e22f10b Mon Sep 17 00:00:00 2001 From: lnash94 Date: Tue, 12 Nov 2024 15:59:23 +0530 Subject: [PATCH 3/9] Add mapping for constant default value in record fields --- .../mapper/model/ModuleMemberVisitor.java | 17 ++++++++++++++ .../service/mapper/type/RecordTypeMapper.java | 23 +++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java index d621c24d4..714e29424 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java @@ -19,6 +19,7 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; +import io.ballerina.compiler.syntax.tree.ConstantDeclarationNode; import io.ballerina.compiler.syntax.tree.ListenerDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeVisitor; @@ -44,6 +45,7 @@ public class ModuleMemberVisitor extends NodeVisitor { Set listenerDeclarationNodes = new LinkedHashSet<>(); Set interceptorServiceClassNodes = new LinkedHashSet<>(); Set serviceContractTypeNodes = new LinkedHashSet<>(); + Set constantDeclarationNodes = new LinkedHashSet<>(); SemanticModel semanticModel; public ModuleMemberVisitor(SemanticModel semanticModel) { @@ -70,6 +72,11 @@ public void visit(ClassDefinitionNode classDefinitionNode) { interceptorServiceClassNodes.add(classDefinitionNode); } + @Override + public void visit(ConstantDeclarationNode constantDeclarationNode) { + constantDeclarationNodes.add(constantDeclarationNode); + } + public Set getListenerDeclarationNodes() { return listenerDeclarationNodes; } @@ -92,6 +99,16 @@ public Optional getInterceptorServiceClassNode(String typeN return Optional.empty(); } + public Optional getConstantDeclarationNode(String constantName) { + for (ConstantDeclarationNode constantDeclarationNode : constantDeclarationNodes) { + if (MapperCommonUtils.unescapeIdentifier(constantDeclarationNode.variableName() + .text()).equals(constantName)) { + return Optional.of(constantDeclarationNode); + } + } + return Optional.empty(); + } + public Set getServiceContractTypeNodes() { return serviceContractTypeNodes; } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index bc79c4fcd..484b55cc9 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -24,11 +24,14 @@ 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.syntax.tree.ConstantDeclarationNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.RecordFieldWithDefaultValueNode; import io.ballerina.compiler.syntax.tree.RecordTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.Token; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages; import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic; @@ -177,13 +180,14 @@ public static Optional getRecordFieldDefaultValue(String recordName, Str Optional recordDefNodeOpt = moduleMemberVisitor.getTypeDefinitionNode(recordName); if (recordDefNodeOpt.isPresent() && recordDefNodeOpt.get().typeDescriptor() instanceof RecordTypeDescriptorNode recordDefNode) { - return getRecordFieldDefaultValue(fieldName, recordDefNode); + return getRecordFieldDefaultValue(fieldName, recordDefNode, moduleMemberVisitor); } return Optional.empty(); } private static Optional getRecordFieldDefaultValue(String fieldName, - RecordTypeDescriptorNode recordDefNode) { + RecordTypeDescriptorNode recordDefNode, + ModuleMemberVisitor moduleMemberVisitor) { NodeList recordFields = recordDefNode.fields(); RecordFieldWithDefaultValueNode defaultValueNode = recordFields.stream() .filter(field -> field instanceof RecordFieldWithDefaultValueNode) @@ -194,12 +198,27 @@ private static Optional getRecordFieldDefaultValue(String fieldName, return Optional.empty(); } ExpressionNode defaultValueExpression = defaultValueNode.expression(); + // ConstantDeclaration + if (defaultValueExpression instanceof SimpleNameReferenceNode reference) { + defaultValueExpression = getExpressionNodeForConstantDeclaration(moduleMemberVisitor, + defaultValueExpression, reference); + } if (MapperCommonUtils.isNotSimpleValueLiteralKind(defaultValueExpression.kind())) { return Optional.empty(); } return Optional.of(MapperCommonUtils.parseBalSimpleLiteral(defaultValueExpression.toString().trim())); } + private static ExpressionNode getExpressionNodeForConstantDeclaration(ModuleMemberVisitor moduleMemberVisitor, ExpressionNode defaultValueExpression, SimpleNameReferenceNode reference) { + Optional constantDeclarationNode = moduleMemberVisitor + .getConstantDeclarationNode(reference.name().text()); + if (constantDeclarationNode.isPresent()) { + ConstantDeclarationNode constantNode = constantDeclarationNode.get(); + defaultValueExpression = (ExpressionNode) constantNode.initializer(); + } + return defaultValueExpression; + } + public static RecordTypeInfo getDirectRecordType(TypeSymbol typeSymbol, String recordName) { if (typeSymbol.typeKind() == TypeDescKind.RECORD) { return new RecordTypeInfo(recordName, (RecordTypeSymbol) typeSymbol); From 9f9d3b62f5307ab9533ec2f04c608a7fdcb70640 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Wed, 13 Nov 2024 11:03:11 +0530 Subject: [PATCH 4/9] Add mapping for constant default value in parameters --- .../parameter/AbstractParameterMapper.java | 15 ++++++-- .../parameter/HeaderParameterMapper.java | 3 +- .../parameter/QueryParameterMapper.java | 3 +- .../service/mapper/type/RecordTypeMapper.java | 13 +------ .../mapper/utils/MapperCommonUtils.java | 15 ++++++++ .../expected_gen/header_scenario13.yaml | 20 +++++++++- .../parameter_annotation/annotated_query.yaml | 38 +++++++++++++++---- .../headers/header_scenario13.bal | 11 +++++- .../parameter_annotation/annotated_query.bal | 5 +++ 9 files changed, 96 insertions(+), 27 deletions(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java index ab5fcb092..537204e49 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java @@ -20,7 +20,9 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; -import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.openapi.service.mapper.model.ModuleMemberVisitor; import io.ballerina.openapi.service.mapper.model.OperationInventory; import io.ballerina.openapi.service.mapper.utils.MapperCommonUtils; import io.swagger.v3.oas.models.parameters.Parameter; @@ -28,6 +30,8 @@ import java.util.List; import java.util.Objects; +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getExpressionNodeForConstantDeclaration; + /** * This {@link AbstractParameterMapper} class represents the abstract parameter mapper. * @@ -55,8 +59,13 @@ public void setParameter() throws ParameterMapperException { parameterList.forEach(operationInventory::setParameter); } - static Object getDefaultValue(DefaultableParameterNode parameterNode) { - Node defaultValueExpression = parameterNode.expression(); + static Object getDefaultValue(DefaultableParameterNode parameterNode, ModuleMemberVisitor moduleMemberVisitor) { + ExpressionNode defaultValueExpression = (ExpressionNode) parameterNode.expression(); + if (defaultValueExpression instanceof SimpleNameReferenceNode reference) { + defaultValueExpression = getExpressionNodeForConstantDeclaration( + moduleMemberVisitor, + defaultValueExpression, reference); + } if (MapperCommonUtils.isNotSimpleValueLiteralKind(defaultValueExpression.kind())) { return null; } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java index 80608c3dc..61c1b2deb 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java @@ -73,7 +73,8 @@ public HeaderParameterMapper(ParameterNode parameterNode, Map ap this.description = apiDocs.get(headerParameter.getName().get()); this.treatNilableAsOptional = treatNilableAsOptional; if (parameterNode instanceof DefaultableParameterNode defaultableHeaderParam) { - this.defaultValue = AbstractParameterMapper.getDefaultValue(defaultableHeaderParam); + this.defaultValue = AbstractParameterMapper.getDefaultValue(defaultableHeaderParam, + additionalData.moduleMemberVisitor()); } this.typeMapper = typeMapper; } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java index 36a7b34b2..a8dc4eb2e 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java @@ -69,7 +69,8 @@ public QueryParameterMapper(ParameterNode parameterNode, Map api this.semanticModel = additionalData.semanticModel(); this.typeMapper = typeMapper; if (parameterNode instanceof DefaultableParameterNode defaultableQueryParam) { - this.defaultValue = AbstractParameterMapper.getDefaultValue(defaultableQueryParam); + this.defaultValue = AbstractParameterMapper.getDefaultValue(defaultableQueryParam, + additionalData.moduleMemberVisitor()); } } } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index 484b55cc9..55dcd30e9 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -24,14 +24,12 @@ 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.syntax.tree.ConstantDeclarationNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.RecordFieldWithDefaultValueNode; import io.ballerina.compiler.syntax.tree.RecordTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; -import io.ballerina.compiler.syntax.tree.Token; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages; import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic; @@ -51,6 +49,7 @@ import java.util.Optional; import java.util.Set; +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getExpressionNodeForConstantDeclaration; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getRecordFieldTypeDescription; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getTypeName; @@ -209,16 +208,6 @@ private static Optional getRecordFieldDefaultValue(String fieldName, return Optional.of(MapperCommonUtils.parseBalSimpleLiteral(defaultValueExpression.toString().trim())); } - private static ExpressionNode getExpressionNodeForConstantDeclaration(ModuleMemberVisitor moduleMemberVisitor, ExpressionNode defaultValueExpression, SimpleNameReferenceNode reference) { - Optional constantDeclarationNode = moduleMemberVisitor - .getConstantDeclarationNode(reference.name().text()); - if (constantDeclarationNode.isPresent()) { - ConstantDeclarationNode constantNode = constantDeclarationNode.get(); - defaultValueExpression = (ExpressionNode) constantNode.initializer(); - } - return defaultValueExpression; - } - public static RecordTypeInfo getDirectRecordType(TypeSymbol typeSymbol, String recordName) { if (typeSymbol.typeKind() == TypeDescKind.RECORD) { return new RecordTypeInfo(recordName, (RecordTypeSymbol) typeSymbol); diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java index 808f48354..e1358b9fc 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java @@ -36,6 +36,7 @@ import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.BasicLiteralNode; +import io.ballerina.compiler.syntax.tree.ConstantDeclarationNode; import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; import io.ballerina.compiler.syntax.tree.DistinctTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; @@ -54,6 +55,7 @@ import io.ballerina.compiler.syntax.tree.ResourcePathParameterNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; @@ -61,6 +63,7 @@ import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages; import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic; import io.ballerina.openapi.service.mapper.diagnostic.OpenAPIMapperDiagnostic; +import io.ballerina.openapi.service.mapper.model.ModuleMemberVisitor; import io.ballerina.openapi.service.mapper.model.OASResult; import io.ballerina.openapi.service.mapper.model.ResourceFunction; import io.ballerina.openapi.service.mapper.model.ResourceFunctionDeclaration; @@ -562,4 +565,16 @@ public static Node getTypeDescriptor(TypeDefinitionNode typeDefinitionNode) { } return node; } + + public static ExpressionNode getExpressionNodeForConstantDeclaration(ModuleMemberVisitor moduleMemberVisitor, + ExpressionNode defaultValueExpression, + SimpleNameReferenceNode reference) { + Optional constantDeclarationNode = moduleMemberVisitor + .getConstantDeclarationNode(reference.name().text()); + if (constantDeclarationNode.isPresent()) { + ConstantDeclarationNode constantNode = constantDeclarationNode.get(); + defaultValueExpression = (ExpressionNode) constantNode.initializer(); + } + return defaultValueExpression; + } } diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml index a960053ef..9310ac79d 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml @@ -134,6 +134,24 @@ paths: - 1 - 2 - 3 + - name: h8 + in: header + schema: + type: string + default: Pod + - name: header31 + in: header + required: true + schema: + type: string + - name: header32 + in: header + required: true + schema: + type: array + items: + type: integer + format: int64 responses: "200": description: Ok @@ -146,7 +164,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ErrorPayload" + $ref: '#/components/schemas/ErrorPayload' components: schemas: ErrorPayload: diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml index ce2090314..1955a44df 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml @@ -33,7 +33,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ErrorPayload" + $ref: '#/components/schemas/ErrorPayload' /student9: post: operationId: postStudent9 @@ -52,13 +52,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Student" + $ref: '#/components/schemas/Student' "400": description: BadRequest content: application/json: schema: - $ref: "#/components/schemas/ErrorPayload" + $ref: '#/components/schemas/ErrorPayload' /student10: post: operationId: postStudent10 @@ -67,20 +67,20 @@ paths: in: query required: true schema: - $ref: "#/components/schemas/Status" + $ref: '#/components/schemas/Status' responses: "201": description: Created content: application/json: schema: - $ref: "#/components/schemas/Student" + $ref: '#/components/schemas/Student' "400": description: BadRequest content: application/json: schema: - $ref: "#/components/schemas/ErrorPayload" + $ref: '#/components/schemas/ErrorPayload' /student11: post: operationId: postStudent11 @@ -89,7 +89,7 @@ paths: in: query schema: allOf: - - $ref: "#/components/schemas/Status" + - $ref: '#/components/schemas/Status' default: ACTIVE responses: "201": @@ -103,7 +103,29 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ErrorPayload" + $ref: '#/components/schemas/ErrorPayload' + /student12: + post: + operationId: postStudent12 + parameters: + - name: status + in: query + schema: + type: string + default: Service + responses: + "201": + description: Created + content: + application/json: + schema: + type: object + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' components: schemas: ErrorPayload: diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal index 71d708744..963f82323 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal @@ -21,13 +21,22 @@ type Header2 record {| boolean header24; |}; +type Header3 record {| + string header31; + int[] header32; +|}; + +public const RESOURCE_KIND_POD = "Pod"; + service /payloadV on helloEp { resource function get header(@http:Header Header1 h, @http:Header string h0 = "", @http:Header string h1 = "\"John\"", @http:Header string[] h2 = [], @http:Header string[] h3 = ["one", "two", "three"], @http:Header int[] h4 = [1, 2, 3], @http:Header float[] h5 = [1, 2.3, 4.56], @http:Header Record h6 = {name: "John", city: "London"}, - @http:Header Header2 h7= {header21: "header1", header22: [1,2,3], header24: false, header23: []}) returns string { + @http:Header Header2 h7= {header21: "header1", header22: [1,2,3], header24: false, header23: []}, + @http:Header string h8 = RESOURCE_KIND_POD, + @http:Header Header3 h7= {header31: RESOURCE_KIND_POD, header32: [1,2,3]},) returns string { return "new"; } } diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal index 8851a1252..fe130557c 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal @@ -1,6 +1,7 @@ import ballerina/http; listener http:Listener ep0 = new (443, config = {host: "petstore3.swagger.io"}); +public const RESOURCE_KIND_SERVICE = "Service"; type Student record { string Name; @@ -28,4 +29,8 @@ service /payloadV on ep0 { resource function post student11(@http:Query Status status = "ACTIVE") returns json { return {Name: "john", Status: status}; } + + resource function post student12(@http:Query string status = RESOURCE_KIND_SERVICE) returns json { + return {Name: "john", Status: status}; + } } From 6d672f49f9d7095f7d809533091f955652d20326 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Wed, 13 Nov 2024 17:15:15 +0530 Subject: [PATCH 5/9] Add constant mapping using semantic model --- .../mapper/model/ModuleMemberVisitor.java | 17 --- .../parameter/AbstractParameterMapper.java | 20 +-- .../parameter/HeaderParameterMapper.java | 2 +- .../parameter/QueryParameterMapper.java | 2 +- .../service/mapper/type/RecordTypeMapper.java | 38 ++--- .../mapper/utils/MapperCommonUtils.java | 15 -- .../generators/openapi/RecordTests.java | 6 + .../expected_gen/header_scenario13.yaml | 8 +- .../parameter_annotation/annotated_query.yaml | 46 ++++-- .../expected_gen/record/included_record.yaml | 133 ++++++++++++++++++ .../headers/header_scenario13.bal | 4 +- .../parameter_annotation/annotated_query.bal | 8 ++ .../record/included_record.bal | 61 ++++++++ .../openapi/cmd/BallerinaToOpenAPITests.java | 24 ++++ .../project_openapi_bal_ext/result_1.yaml | 10 +- .../Ballerina.toml | 4 + .../result.yaml | 133 ++++++++++++++++++ .../service_file.bal | 61 ++++++++ .../resources/expected_types.bal | 105 ++++++++++++++ 19 files changed, 617 insertions(+), 80 deletions(-) create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal create mode 100644 openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/Ballerina.toml create mode 100644 openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/result.yaml create mode 100644 openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/service_file.bal create mode 100644 openapi-integration-tests/src/test/resources/ballerina_sources/resources/expected_types.bal diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java index 714e29424..d621c24d4 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/model/ModuleMemberVisitor.java @@ -19,7 +19,6 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; -import io.ballerina.compiler.syntax.tree.ConstantDeclarationNode; import io.ballerina.compiler.syntax.tree.ListenerDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeVisitor; @@ -45,7 +44,6 @@ public class ModuleMemberVisitor extends NodeVisitor { Set listenerDeclarationNodes = new LinkedHashSet<>(); Set interceptorServiceClassNodes = new LinkedHashSet<>(); Set serviceContractTypeNodes = new LinkedHashSet<>(); - Set constantDeclarationNodes = new LinkedHashSet<>(); SemanticModel semanticModel; public ModuleMemberVisitor(SemanticModel semanticModel) { @@ -72,11 +70,6 @@ public void visit(ClassDefinitionNode classDefinitionNode) { interceptorServiceClassNodes.add(classDefinitionNode); } - @Override - public void visit(ConstantDeclarationNode constantDeclarationNode) { - constantDeclarationNodes.add(constantDeclarationNode); - } - public Set getListenerDeclarationNodes() { return listenerDeclarationNodes; } @@ -99,16 +92,6 @@ public Optional getInterceptorServiceClassNode(String typeN return Optional.empty(); } - public Optional getConstantDeclarationNode(String constantName) { - for (ConstantDeclarationNode constantDeclarationNode : constantDeclarationNodes) { - if (MapperCommonUtils.unescapeIdentifier(constantDeclarationNode.variableName() - .text()).equals(constantName)) { - return Optional.of(constantDeclarationNode); - } - } - return Optional.empty(); - } - public Set getServiceContractTypeNodes() { return serviceContractTypeNodes; } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java index 537204e49..cb3f690e9 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java @@ -18,19 +18,19 @@ package io.ballerina.openapi.service.mapper.parameter; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ConstantSymbol; +import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.values.ConstantValue; import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; -import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; -import io.ballerina.openapi.service.mapper.model.ModuleMemberVisitor; import io.ballerina.openapi.service.mapper.model.OperationInventory; import io.ballerina.openapi.service.mapper.utils.MapperCommonUtils; import io.swagger.v3.oas.models.parameters.Parameter; import java.util.List; import java.util.Objects; - -import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getExpressionNodeForConstantDeclaration; +import java.util.Optional; /** * This {@link AbstractParameterMapper} class represents the abstract parameter mapper. @@ -59,12 +59,14 @@ public void setParameter() throws ParameterMapperException { parameterList.forEach(operationInventory::setParameter); } - static Object getDefaultValue(DefaultableParameterNode parameterNode, ModuleMemberVisitor moduleMemberVisitor) { + static Object getDefaultValue(DefaultableParameterNode parameterNode, SemanticModel semanticModel) { ExpressionNode defaultValueExpression = (ExpressionNode) parameterNode.expression(); - if (defaultValueExpression instanceof SimpleNameReferenceNode reference) { - defaultValueExpression = getExpressionNodeForConstantDeclaration( - moduleMemberVisitor, - defaultValueExpression, reference); + Optional symbol = semanticModel.symbol(defaultValueExpression); + if (symbol.isPresent() && symbol.get() instanceof ConstantSymbol constantSymbol) { + Object constValue = constantSymbol.constValue(); + if (constValue instanceof ConstantValue value) { + return value.value(); + } } if (MapperCommonUtils.isNotSimpleValueLiteralKind(defaultValueExpression.kind())) { return null; diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java index 61c1b2deb..6763564f0 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/HeaderParameterMapper.java @@ -74,7 +74,7 @@ public HeaderParameterMapper(ParameterNode parameterNode, Map ap this.treatNilableAsOptional = treatNilableAsOptional; if (parameterNode instanceof DefaultableParameterNode defaultableHeaderParam) { this.defaultValue = AbstractParameterMapper.getDefaultValue(defaultableHeaderParam, - additionalData.moduleMemberVisitor()); + additionalData.semanticModel()); } this.typeMapper = typeMapper; } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java index a8dc4eb2e..6369f4090 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java @@ -70,7 +70,7 @@ public QueryParameterMapper(ParameterNode parameterNode, Map api this.typeMapper = typeMapper; if (parameterNode instanceof DefaultableParameterNode defaultableQueryParam) { this.defaultValue = AbstractParameterMapper.getDefaultValue(defaultableQueryParam, - additionalData.moduleMemberVisitor()); + additionalData.semanticModel()); } } } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index 55dcd30e9..852e4c95f 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -17,19 +17,22 @@ */ package io.ballerina.openapi.service.mapper.type; +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ConstantSymbol; import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; 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.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.values.ConstantValue; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.RecordFieldWithDefaultValueNode; import io.ballerina.compiler.syntax.tree.RecordTypeDescriptorNode; -import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages; import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic; @@ -49,7 +52,6 @@ import java.util.Optional; import java.util.Set; -import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getExpressionNodeForConstantDeclaration; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getRecordFieldTypeDescription; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getTypeName; @@ -122,17 +124,12 @@ static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components c .typeDescriptor(); Map includedRecordFieldMap = includedRecordTypeSymbol.fieldDescriptors(); for (Map.Entry includedRecordField : includedRecordFieldMap.entrySet()) { - //logic comes here i guess RecordFieldSymbol recordFieldSymbol = recordFieldMap.get(includedRecordField.getKey()); RecordFieldSymbol includedRecordFieldValue = includedRecordField.getValue(); - if (recordFieldSymbol != null) { - //check for the types - if (includedRecordFieldValue.typeDescriptor().equals(recordFieldSymbol.typeDescriptor())) { - // check for the default values availability - if (!recordFieldSymbol.hasDefaultValue()) { - recordFieldMap.remove(includedRecordField.getKey()); - } - } + boolean isRemovableField = recordFieldSymbol != null && includedRecordFieldValue.typeDescriptor() + .equals(recordFieldSymbol.typeDescriptor()) && !recordFieldSymbol.hasDefaultValue(); + if (isRemovableField) { + recordFieldMap.remove(includedRecordField.getKey()); } } } @@ -160,7 +157,7 @@ public static Map mapRecordFields(Map } if (recordFieldSymbol.hasDefaultValue()) { Optional recordFieldDefaultValueOpt = getRecordFieldDefaultValue(recordName, recordFieldName, - additionalData.moduleMemberVisitor()); + additionalData.moduleMemberVisitor(), additionalData.semanticModel()); if (recordFieldDefaultValueOpt.isPresent()) { TypeMapper.setDefaultValue(recordFieldSchema, recordFieldDefaultValueOpt.get()); } else { @@ -175,18 +172,19 @@ public static Map mapRecordFields(Map } public static Optional getRecordFieldDefaultValue(String recordName, String fieldName, - ModuleMemberVisitor moduleMemberVisitor) { + ModuleMemberVisitor moduleMemberVisitor, + SemanticModel semanticModel) { Optional recordDefNodeOpt = moduleMemberVisitor.getTypeDefinitionNode(recordName); if (recordDefNodeOpt.isPresent() && recordDefNodeOpt.get().typeDescriptor() instanceof RecordTypeDescriptorNode recordDefNode) { - return getRecordFieldDefaultValue(fieldName, recordDefNode, moduleMemberVisitor); + return getRecordFieldDefaultValue(fieldName, recordDefNode, semanticModel); } return Optional.empty(); } private static Optional getRecordFieldDefaultValue(String fieldName, RecordTypeDescriptorNode recordDefNode, - ModuleMemberVisitor moduleMemberVisitor) { + SemanticModel semanticModel) { NodeList recordFields = recordDefNode.fields(); RecordFieldWithDefaultValueNode defaultValueNode = recordFields.stream() .filter(field -> field instanceof RecordFieldWithDefaultValueNode) @@ -197,10 +195,12 @@ private static Optional getRecordFieldDefaultValue(String fieldName, return Optional.empty(); } ExpressionNode defaultValueExpression = defaultValueNode.expression(); - // ConstantDeclaration - if (defaultValueExpression instanceof SimpleNameReferenceNode reference) { - defaultValueExpression = getExpressionNodeForConstantDeclaration(moduleMemberVisitor, - defaultValueExpression, reference); + Optional symbol = semanticModel.symbol(defaultValueExpression); + if (symbol.isPresent() && symbol.get() instanceof ConstantSymbol constantSymbol) { + Object constValue = constantSymbol.constValue(); + if (constValue instanceof ConstantValue value) { + return Optional.of(value.value()); + } } if (MapperCommonUtils.isNotSimpleValueLiteralKind(defaultValueExpression.kind())) { return Optional.empty(); diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java index e1358b9fc..808f48354 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java @@ -36,7 +36,6 @@ import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.BasicLiteralNode; -import io.ballerina.compiler.syntax.tree.ConstantDeclarationNode; import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; import io.ballerina.compiler.syntax.tree.DistinctTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; @@ -55,7 +54,6 @@ import io.ballerina.compiler.syntax.tree.ResourcePathParameterNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; -import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; @@ -63,7 +61,6 @@ import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages; import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic; import io.ballerina.openapi.service.mapper.diagnostic.OpenAPIMapperDiagnostic; -import io.ballerina.openapi.service.mapper.model.ModuleMemberVisitor; import io.ballerina.openapi.service.mapper.model.OASResult; import io.ballerina.openapi.service.mapper.model.ResourceFunction; import io.ballerina.openapi.service.mapper.model.ResourceFunctionDeclaration; @@ -565,16 +562,4 @@ public static Node getTypeDescriptor(TypeDefinitionNode typeDefinitionNode) { } return node; } - - public static ExpressionNode getExpressionNodeForConstantDeclaration(ModuleMemberVisitor moduleMemberVisitor, - ExpressionNode defaultValueExpression, - SimpleNameReferenceNode reference) { - Optional constantDeclarationNode = moduleMemberVisitor - .getConstantDeclarationNode(reference.name().text()); - if (constantDeclarationNode.isPresent()) { - ConstantDeclarationNode constantNode = constantDeclarationNode.get(); - defaultValueExpression = (ExpressionNode) constantNode.initializer(); - } - return defaultValueExpression; - } } diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RecordTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RecordTests.java index b08d12be8..096634ffa 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RecordTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RecordTests.java @@ -125,6 +125,12 @@ public void testRestFieldInRecord() throws IOException { TestUtils.compareWithGeneratedFile(ballerinaFilePath, "record/record_rest_param.yaml"); } + @Test(description = "Test for record has included record with same fields") + public void testIncludedRecordWithSameFields() throws IOException { + Path ballerinaFilePath = RES_DIR.resolve("record/included_record.bal"); + TestUtils.compareWithGeneratedFile(ballerinaFilePath, "record/included_record.yaml"); + } + @AfterMethod public void cleanUp() { TestUtils.deleteDirectory(this.tempDir); diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml index 9310ac79d..87d2d8702 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/header_scenario13.yaml @@ -141,9 +141,9 @@ paths: default: Pod - name: header31 in: header - required: true schema: type: string + default: Pod - name: header32 in: header required: true @@ -152,6 +152,10 @@ paths: items: type: integer format: int64 + default: + - 1 + - 2 + - 3 responses: "200": description: Ok @@ -164,7 +168,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorPayload' + $ref: "#/components/schemas/ErrorPayload" components: schemas: ErrorPayload: diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml index 1955a44df..f8c1dca86 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/parameter_annotation/annotated_query.yaml @@ -33,7 +33,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorPayload' + $ref: "#/components/schemas/ErrorPayload" /student9: post: operationId: postStudent9 @@ -52,13 +52,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Student' + $ref: "#/components/schemas/Student" "400": description: BadRequest content: application/json: schema: - $ref: '#/components/schemas/ErrorPayload' + $ref: "#/components/schemas/ErrorPayload" /student10: post: operationId: postStudent10 @@ -67,20 +67,20 @@ paths: in: query required: true schema: - $ref: '#/components/schemas/Status' + $ref: "#/components/schemas/Status" responses: "201": description: Created content: application/json: schema: - $ref: '#/components/schemas/Student' + $ref: "#/components/schemas/Student" "400": description: BadRequest content: application/json: schema: - $ref: '#/components/schemas/ErrorPayload' + $ref: "#/components/schemas/ErrorPayload" /student11: post: operationId: postStudent11 @@ -89,7 +89,7 @@ paths: in: query schema: allOf: - - $ref: '#/components/schemas/Status' + - $ref: "#/components/schemas/Status" default: ACTIVE responses: "201": @@ -103,7 +103,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorPayload' + $ref: "#/components/schemas/ErrorPayload" /student12: post: operationId: postStudent12 @@ -125,7 +125,30 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorPayload' + $ref: "#/components/schemas/ErrorPayload" + /student13: + post: + operationId: postStudent13 + parameters: + - name: kind + in: query + schema: + allOf: + - $ref: "#/components/schemas/ResourceKind" + default: Service + responses: + "201": + description: Created + content: + application/json: + schema: + type: object + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" components: schemas: ErrorPayload: @@ -151,6 +174,11 @@ components: type: string method: type: string + ResourceKind: + type: string + enum: + - Service + - Pod Status: type: string enum: diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml new file mode 100644 index 000000000..d7805374f --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml @@ -0,0 +1,133 @@ +openapi: 3.0.1 +info: + title: PayloadV + version: 0.0.0 +servers: + - url: "{server}:{port}/payloadV" + variables: + server: + default: http://localhost + port: + default: "7080" +paths: + /Pods: + get: + operationId: getPods + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pod" + /Services: + get: + operationId: getServices + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Service" +components: + schemas: + Metadata: + required: + - name + type: object + properties: + name: + type: string + displayName: + type: string + description: + type: string + Pod: + type: object + allOf: + - $ref: "#/components/schemas/Resource" + - required: + - spec + type: object + properties: + kind: + allOf: + - $ref: "#/components/schemas/ResourceKind" + default: Pod + spec: + $ref: "#/components/schemas/PodSpec" + PodSpec: + required: + - nodeName + type: object + properties: + nodeName: + type: string + Resource: + type: object + allOf: + - $ref: "#/components/schemas/ResourceBase" + - required: + - spec + type: object + properties: + spec: + type: object + properties: {} + status: + $ref: "#/components/schemas/Status" + ResourceBase: + required: + - group + - kind + - metadata + - version + type: object + properties: + group: + type: string + version: + type: string + kind: + $ref: "#/components/schemas/ResourceKind" + metadata: + $ref: "#/components/schemas/Metadata" + ResourceKind: + type: string + enum: + - Service + - Pod + Service: + type: object + allOf: + - $ref: "#/components/schemas/Resource" + - required: + - spec + type: object + properties: + kind: + allOf: + - $ref: "#/components/schemas/ResourceKind" + default: Service + spec: + $ref: "#/components/schemas/ServiceSpec" + ServiceSpec: + required: + - clusterIP + type: object + properties: + clusterIP: + type: string + Status: + required: + - observedGeneration + type: object + properties: + observedGeneration: + type: integer + format: int64 diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal index 963f82323..0532e54f3 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/headers/header_scenario13.bal @@ -22,7 +22,7 @@ type Header2 record {| |}; type Header3 record {| - string header31; + string header31 = RESOURCE_KIND_POD; int[] header32; |}; @@ -36,7 +36,7 @@ service /payloadV on helloEp { @http:Header Record h6 = {name: "John", city: "London"}, @http:Header Header2 h7= {header21: "header1", header22: [1,2,3], header24: false, header23: []}, @http:Header string h8 = RESOURCE_KIND_POD, - @http:Header Header3 h7= {header31: RESOURCE_KIND_POD, header32: [1,2,3]},) returns string { + @http:Header Header3 h9= {header32: [1,2,3]}) returns string { return "new"; } } diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal index fe130557c..203496cf8 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/parameter_annotation/annotated_query.bal @@ -1,7 +1,11 @@ import ballerina/http; listener http:Listener ep0 = new (443, config = {host: "petstore3.swagger.io"}); + public const RESOURCE_KIND_SERVICE = "Service"; +public const RESOURCE_KIND_POD = "Pod"; + +public type ResourceKind RESOURCE_KIND_SERVICE|RESOURCE_KIND_POD; type Student record { string Name; @@ -33,4 +37,8 @@ service /payloadV on ep0 { resource function post student12(@http:Query string status = RESOURCE_KIND_SERVICE) returns json { return {Name: "john", Status: status}; } + + resource function post student13(@http:Query ResourceKind kind = RESOURCE_KIND_SERVICE) returns json { + return {Name: "john"}; + } } diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal new file mode 100644 index 000000000..22729fb7c --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal @@ -0,0 +1,61 @@ +import ballerina/http; + +public const RESOURCE_KIND_SERVICE = "Service"; +public const RESOURCE_KIND_POD = "Pod"; + +public type ResourceKind RESOURCE_KIND_SERVICE|RESOURCE_KIND_POD; + +public type Metadata record { + string name; + string displayName?; + string description?; +}; + +public type Status record { + int observedGeneration; +}; + +public type ResourceBase record { + string group; + string version; + ResourceKind kind; + Metadata metadata; +}; + +public type Resource record { + *ResourceBase; + record {} spec; + Status status?; +}; + +public type ServiceSpec record { + string clusterIP; +}; + +public type Service record { + *Resource; + ResourceKind kind = RESOURCE_KIND_SERVICE; + ServiceSpec spec; + Status status?; +}; + +public type PodSpec record { + string nodeName; +}; + +public type Pod record { + *Resource; + ResourceKind kind = RESOURCE_KIND_POD; + PodSpec spec; + Status status?; +}; + +service /payloadV on new http:Listener(7080) { + resource function get Pods() returns Pod[] { + return []; + } + + resource function get Services() returns Service[] { + return []; + } +} diff --git a/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/BallerinaToOpenAPITests.java b/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/BallerinaToOpenAPITests.java index 83082d548..239f569b5 100644 --- a/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/BallerinaToOpenAPITests.java +++ b/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/BallerinaToOpenAPITests.java @@ -178,6 +178,30 @@ public void openAPIGenWithBalExt() throws IOException, InterruptedException { "project_openapi_bal_ext/result_1.yaml", true); } + @Test(description = "Map included record type which has same fields") + public void includedRecordTest() throws IOException, InterruptedException { + // 1. generate OAS + executeCommand("project_openapi_with_included_test/service_file.bal", + "payloadV_openapi.yaml", + "project_openapi_with_included_test/result.yaml", false); + // 2. generate type using the OAS which generated point 1 + generateTypes(); + } + + private void generateTypes() throws IOException, InterruptedException { + List buildArgs = new LinkedList<>(); + buildArgs.add("-i"); + buildArgs.add(String.valueOf(TEST_RESOURCE.resolve("payloadV_openapi.yaml"))); + buildArgs.add("-o"); + buildArgs.add(tmpDir.toString()); + boolean successful = TestUtil.executeOpenAPI(DISTRIBUTION_FILE_NAME, TEST_RESOURCE, buildArgs); + Assert.assertTrue(Files.exists(Paths.get(tmpDir.toString()).resolve("types.bal"))); + String generatedOpenAPI = getStringFromGivenBalFile(Paths.get(tmpDir.toString()).resolve("types.bal")); + String expectedYaml = getStringFromGivenBalFile(TEST_RESOURCE.resolve( + "resources/expected_types.bal")); + Assert.assertEquals(expectedYaml, generatedOpenAPI); + } + @AfterClass public void cleanUp() throws IOException { TestUtil.cleanDistribution(); diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml index 0dafce369..996094380 100644 --- a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml @@ -57,7 +57,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Date DateFields: @@ -81,7 +81,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: DateFields OptionalTimeOfDayFields: @@ -100,7 +100,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: OptionalTimeOfDayFields Seconds: @@ -111,7 +111,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Seconds Student: @@ -165,6 +165,6 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: ZoneOffset diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/Ballerina.toml b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/Ballerina.toml new file mode 100644 index 000000000..d3cc51d4b --- /dev/null +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org= "ballerina" +name= "openapi" +version= "2.0.0" diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/result.yaml b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/result.yaml new file mode 100644 index 000000000..18dfb316e --- /dev/null +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/result.yaml @@ -0,0 +1,133 @@ +openapi: 3.0.1 +info: + title: PayloadV + version: 2.0.0 +servers: + - url: "{server}:{port}/payloadV" + variables: + server: + default: http://localhost + port: + default: "7080" +paths: + /Pods: + get: + operationId: getPods + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pod' + /Services: + get: + operationId: getServices + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Service' +components: + schemas: + Metadata: + required: + - name + type: object + properties: + name: + type: string + displayName: + type: string + description: + type: string + Pod: + type: object + allOf: + - $ref: '#/components/schemas/Resource' + - required: + - spec + type: object + properties: + kind: + allOf: + - $ref: '#/components/schemas/ResourceKind' + default: Pod + spec: + $ref: '#/components/schemas/PodSpec' + PodSpec: + required: + - nodeName + type: object + properties: + nodeName: + type: string + Resource: + type: object + allOf: + - $ref: '#/components/schemas/ResourceBase' + - required: + - spec + type: object + properties: + spec: + type: object + properties: {} + status: + $ref: '#/components/schemas/Status' + ResourceBase: + required: + - group + - kind + - metadata + - version + type: object + properties: + group: + type: string + version: + type: string + kind: + $ref: '#/components/schemas/ResourceKind' + metadata: + $ref: '#/components/schemas/Metadata' + ResourceKind: + type: string + enum: + - Service + - Pod + Service: + type: object + allOf: + - $ref: '#/components/schemas/Resource' + - required: + - spec + type: object + properties: + kind: + allOf: + - $ref: '#/components/schemas/ResourceKind' + default: Service + spec: + $ref: '#/components/schemas/ServiceSpec' + ServiceSpec: + required: + - clusterIP + type: object + properties: + clusterIP: + type: string + Status: + required: + - observedGeneration + type: object + properties: + observedGeneration: + type: integer + format: int64 diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/service_file.bal b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/service_file.bal new file mode 100644 index 000000000..22729fb7c --- /dev/null +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_with_included_test/service_file.bal @@ -0,0 +1,61 @@ +import ballerina/http; + +public const RESOURCE_KIND_SERVICE = "Service"; +public const RESOURCE_KIND_POD = "Pod"; + +public type ResourceKind RESOURCE_KIND_SERVICE|RESOURCE_KIND_POD; + +public type Metadata record { + string name; + string displayName?; + string description?; +}; + +public type Status record { + int observedGeneration; +}; + +public type ResourceBase record { + string group; + string version; + ResourceKind kind; + Metadata metadata; +}; + +public type Resource record { + *ResourceBase; + record {} spec; + Status status?; +}; + +public type ServiceSpec record { + string clusterIP; +}; + +public type Service record { + *Resource; + ResourceKind kind = RESOURCE_KIND_SERVICE; + ServiceSpec spec; + Status status?; +}; + +public type PodSpec record { + string nodeName; +}; + +public type Pod record { + *Resource; + ResourceKind kind = RESOURCE_KIND_POD; + PodSpec spec; + Status status?; +}; + +service /payloadV on new http:Listener(7080) { + resource function get Pods() returns Pod[] { + return []; + } + + resource function get Services() returns Service[] { + return []; + } +} diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/resources/expected_types.bal b/openapi-integration-tests/src/test/resources/ballerina_sources/resources/expected_types.bal new file mode 100644 index 000000000..d7dd22840 --- /dev/null +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/resources/expected_types.bal @@ -0,0 +1,105 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; + +public type Status record { + int observedGeneration; +}; + +public type Pod record { + *Resource; + ResourceKind kind = "Pod"; + PodSpec spec; +}; + +public type PodSpec record { + string nodeName; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type ResourceBase record { + string group; + string version; + ResourceKind kind; + Metadata metadata; +}; + +public type Metadata record { + string name; + string displayName?; + string description?; +}; + +public type Resource record { + *ResourceBase; + record {} spec; + Status status?; +}; + +public type ServiceSpec record { + string clusterIP; +}; + +public type Service record { + *Resource; + ResourceKind kind = "Service"; + ServiceSpec spec; +}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +public type ResourceKind "Service"|"Pod"; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; +|}; From b6ad329b9d5856e9f2d7ce16d906ec75e0ff4cd9 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Thu, 14 Nov 2024 00:58:26 +0530 Subject: [PATCH 6/9] Fix review suggestions --- .../parameter/AbstractParameterMapper.java | 16 ++-- .../service/mapper/type/RecordTypeMapper.java | 90 +++++++++++++++--- .../mapper/utils/MapperCommonUtils.java | 11 +++ .../expected_gen/record/included_record.yaml | 93 ++++++++++++++++++- .../record/included_record.bal | 38 +++++++- 5 files changed, 221 insertions(+), 27 deletions(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java index cb3f690e9..7368345e4 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/AbstractParameterMapper.java @@ -18,12 +18,10 @@ package io.ballerina.openapi.service.mapper.parameter; import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.ConstantSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeSymbol; -import io.ballerina.compiler.api.values.ConstantValue; import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; -import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.openapi.service.mapper.model.OperationInventory; import io.ballerina.openapi.service.mapper.utils.MapperCommonUtils; import io.swagger.v3.oas.models.parameters.Parameter; @@ -32,6 +30,8 @@ import java.util.Objects; import java.util.Optional; +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getConstantValues; + /** * This {@link AbstractParameterMapper} class represents the abstract parameter mapper. * @@ -60,13 +60,11 @@ public void setParameter() throws ParameterMapperException { } static Object getDefaultValue(DefaultableParameterNode parameterNode, SemanticModel semanticModel) { - ExpressionNode defaultValueExpression = (ExpressionNode) parameterNode.expression(); + Node defaultValueExpression = parameterNode.expression(); Optional symbol = semanticModel.symbol(defaultValueExpression); - if (symbol.isPresent() && symbol.get() instanceof ConstantSymbol constantSymbol) { - Object constValue = constantSymbol.constValue(); - if (constValue instanceof ConstantValue value) { - return value.value(); - } + Optional constantValues = getConstantValues(symbol); + if (constantValues.isPresent()) { + return constantValues.get(); } if (MapperCommonUtils.isNotSimpleValueLiteralKind(defaultValueExpression.kind())) { return null; diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index 852e4c95f..02dcb5dff 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -18,7 +18,6 @@ package io.ballerina.openapi.service.mapper.type; import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.ConstantSymbol; import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; import io.ballerina.compiler.api.symbols.RecordFieldSymbol; import io.ballerina.compiler.api.symbols.RecordTypeSymbol; @@ -27,7 +26,6 @@ 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.values.ConstantValue; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; @@ -52,6 +50,7 @@ import java.util.Optional; import java.util.Set; +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getConstantValues; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getRecordFieldTypeDescription; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getTypeName; @@ -79,7 +78,8 @@ public static Schema getSchema(RecordTypeSymbol typeSymbol, Components component Set requiredFields = new HashSet<>(); Map recordFieldMap = new LinkedHashMap<>(typeSymbol.fieldDescriptors()); - List allOfSchemaList = mapIncludedRecords(typeSymbol, components, recordFieldMap, additionalData); + List allOfSchemaList = mapIncludedRecords(typeSymbol, components, recordFieldMap, additionalData, + recordName); Map properties = mapRecordFields(recordFieldMap, components, requiredFields, recordName, false, additionalData); @@ -107,7 +107,7 @@ public static Schema getSchema(RecordTypeSymbol typeSymbol, Components component static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components components, Map recordFieldMap, - AdditionalData additionalData) { + AdditionalData additionalData, String recordName) { List allOfSchemaList = new ArrayList<>(); List typeInclusions = typeSymbol.typeInclusions(); for (TypeSymbol typeInclusion : typeInclusions) { @@ -126,17 +126,81 @@ static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components c for (Map.Entry includedRecordField : includedRecordFieldMap.entrySet()) { RecordFieldSymbol recordFieldSymbol = recordFieldMap.get(includedRecordField.getKey()); RecordFieldSymbol includedRecordFieldValue = includedRecordField.getValue(); - boolean isRemovableField = recordFieldSymbol != null && includedRecordFieldValue.typeDescriptor() - .equals(recordFieldSymbol.typeDescriptor()) && !recordFieldSymbol.hasDefaultValue(); - if (isRemovableField) { - recordFieldMap.remove(includedRecordField.getKey()); + + if (recordFieldSymbol == null + || !includedRecordFieldValue.typeDescriptor().equals(recordFieldSymbol.typeDescriptor())) { + continue; } + eliminateRedundantFields(recordFieldMap, additionalData, recordName, typeInclusion, + includedRecordField, recordFieldSymbol, includedRecordFieldValue); } } } return allOfSchemaList; } + private static void eliminateRedundantFields(Map recordFieldMap, + AdditionalData additionalData, String recordName, + TypeSymbol typeInclusion, + Map.Entry includedRecordField, + RecordFieldSymbol recordFieldSymbol, + RecordFieldSymbol includedRecordFieldValue) { + + boolean recordHasDefault = recordFieldSymbol.hasDefaultValue(); + boolean includedHasDefault = includedRecordFieldValue.hasDefaultValue(); + boolean hasTypeInclusionName = typeInclusion.getName().isPresent(); + + if (recordHasDefault && includedHasDefault && hasTypeInclusionName) { + Optional recordFieldDefaultValueOpt = getRecordFieldDefaultValue(recordName, + includedRecordField.getKey(), additionalData.moduleMemberVisitor(), + additionalData.semanticModel()); + + Optional includedFieldDefaultValueOpt = getRecordFieldDefaultValue( + typeInclusion.getName().get(), includedRecordField.getKey(), + additionalData.moduleMemberVisitor(), additionalData.semanticModel()); + + /* + This check the scenarios + ex: + type RecA record {| + string a = "a"; + string aa; + |}; + type RecD record {| + *RecA; + string a = "aad"; + int d; + |}; + */ + boolean defaultsAreEqual = recordFieldDefaultValueOpt.isPresent() + && includedFieldDefaultValueOpt.isPresent() + && recordFieldDefaultValueOpt.get().toString() + .equals(includedFieldDefaultValueOpt.get().toString()); + + /* + This checks the scenario where RecA has `a` defaultable field. In this case, the + .hasDefaultValue() API returns true for both records, but RecA provides the value of the default. + ex: + type RecA record {| + string a = "a"; + string aa; + |}; + type RecB record {| + *RecA; + int b; + |}; + */ + boolean onlyIncludedHasDefault = recordFieldDefaultValueOpt.isEmpty() && + includedFieldDefaultValueOpt.isPresent(); + + if (defaultsAreEqual || onlyIncludedHasDefault) { + recordFieldMap.remove(includedRecordField.getKey()); + } + } else if (!recordHasDefault && !includedHasDefault) { + recordFieldMap.remove(includedRecordField.getKey()); + } + } + public static Map mapRecordFields(Map recordFieldMap, Components components, Set requiredFields, String recordName, boolean treatNilableAsOptional, @@ -172,7 +236,7 @@ public static Map mapRecordFields(Map } public static Optional getRecordFieldDefaultValue(String recordName, String fieldName, - ModuleMemberVisitor moduleMemberVisitor, + ModuleMemberVisitor moduleMemberVisitor, SemanticModel semanticModel) { Optional recordDefNodeOpt = moduleMemberVisitor.getTypeDefinitionNode(recordName); if (recordDefNodeOpt.isPresent() && @@ -196,11 +260,9 @@ private static Optional getRecordFieldDefaultValue(String fieldName, } ExpressionNode defaultValueExpression = defaultValueNode.expression(); Optional symbol = semanticModel.symbol(defaultValueExpression); - if (symbol.isPresent() && symbol.get() instanceof ConstantSymbol constantSymbol) { - Object constValue = constantSymbol.constValue(); - if (constValue instanceof ConstantValue value) { - return Optional.of(value.value()); - } + Optional value = getConstantValues(symbol); + if (value.isPresent()) { + return value; } if (MapperCommonUtils.isNotSimpleValueLiteralKind(defaultValueExpression.kind())) { return Optional.empty(); diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java index 808f48354..aa33462e1 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java @@ -34,6 +34,7 @@ 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.values.ConstantValue; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.BasicLiteralNode; import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; @@ -555,6 +556,16 @@ public static Optional getResourceFunction(Node function) { return Optional.empty(); } + public static Optional getConstantValues(Optional symbol) { + if (symbol.isPresent() && symbol.get() instanceof ConstantSymbol constantSymbol) { + Object constValue = constantSymbol.constValue(); + if (constValue instanceof ConstantValue value) { + return Optional.of(value.value()); + } + } + return Optional.empty(); + } + public static Node getTypeDescriptor(TypeDefinitionNode typeDefinitionNode) { Node node = typeDefinitionNode.typeDescriptor(); if (node instanceof DistinctTypeDescriptorNode distinctTypeDescriptorNode) { diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml index d7805374f..efd97b07c 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml @@ -10,7 +10,7 @@ servers: port: default: "7080" paths: - /Pods: + /pods: get: operationId: getPods responses: @@ -22,7 +22,7 @@ paths: type: array items: $ref: "#/components/schemas/Pod" - /Services: + /services: get: operationId: getServices responses: @@ -34,6 +34,42 @@ paths: type: array items: $ref: "#/components/schemas/Service" + /recB: + get: + operationId: getRecb + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecB" + /recC: + get: + operationId: getRecc + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecC" + /recD: + post: + operationId: postRecd + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecD" components: schemas: Metadata: @@ -68,6 +104,59 @@ components: properties: nodeName: type: string + RecA: + required: + - aa + type: object + properties: + a: + type: string + default: a + aa: + type: string + additionalProperties: false + RecB: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - b + type: object + properties: + b: + type: integer + format: int64 + additionalProperties: false + RecC: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - c + type: object + properties: + aa: + type: string + default: aa + c: + type: integer + format: int64 + additionalProperties: false + RecD: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - d + type: object + properties: + a: + type: string + default: aad + d: + type: integer + format: int64 + additionalProperties: false Resource: type: object allOf: diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal index 22729fb7c..159508de1 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal @@ -50,12 +50,46 @@ public type Pod record { Status status?; }; +type RecA record {| + string a = "a"; + string aa; +|}; + +type RecB record {| + *RecA; + int b; +|}; + +type RecC record {| + *RecA; + string aa = "aa"; + int c; +|}; + +type RecD record {| + *RecA; + string a = "aad"; + int d; +|}; + service /payloadV on new http:Listener(7080) { - resource function get Pods() returns Pod[] { + resource function get pods() returns Pod[] { + return []; + } + + resource function get services() returns Service[] { + return []; + } + + resource function get recB() returns RecB[] { + return []; + } + + resource function get recC() returns RecC[] { return []; } - resource function get Services() returns Service[] { + resource function post recD() returns RecD[] { return []; } } From 764d7bf4807712da7583b8ec72f70d3a911b0e7e Mon Sep 17 00:00:00 2001 From: lnash94 Date: Fri, 15 Nov 2024 12:24:31 +0530 Subject: [PATCH 7/9] Add optional field mapper --- .../service/mapper/type/RecordTypeMapper.java | 36 +++-- .../service/mapper/type/TypeMapperImpl.java | 3 +- .../expected_gen/record/included_record.yaml | 135 ++++++++++++++++++ .../record/included_record.bal | 76 ++++++++++ 4 files changed, 239 insertions(+), 11 deletions(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index 02dcb5dff..dc3240037 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -61,7 +61,6 @@ * @since 1.9.0 */ public class RecordTypeMapper extends AbstractTypeMapper { - public RecordTypeMapper(TypeReferenceTypeSymbol typeSymbol, AdditionalData additionalData) { super(typeSymbol, additionalData); } @@ -74,15 +73,16 @@ public Schema getReferenceSchema(Components components) { public static Schema getSchema(RecordTypeSymbol typeSymbol, Components components, String recordName, AdditionalData additionalData) { + Set fieldsOnlyForRequiredList = new HashSet<>(); ObjectSchema schema = new ObjectSchema(); Set requiredFields = new HashSet<>(); Map recordFieldMap = new LinkedHashMap<>(typeSymbol.fieldDescriptors()); List allOfSchemaList = mapIncludedRecords(typeSymbol, components, recordFieldMap, additionalData, - recordName); + recordName, fieldsOnlyForRequiredList); Map properties = mapRecordFields(recordFieldMap, components, requiredFields, - recordName, false, additionalData); + recordName, false, additionalData, fieldsOnlyForRequiredList); Optional restFieldType = typeSymbol.restTypeDescriptor(); if (restFieldType.isPresent()) { @@ -94,8 +94,8 @@ public static Schema getSchema(RecordTypeSymbol typeSymbol, Components component schema.additionalProperties(false); } - schema.setProperties(properties); schema.setRequired(requiredFields.stream().toList()); + schema.setProperties(properties); if (!allOfSchemaList.isEmpty()) { ObjectSchema schemaWithAllOf = new ObjectSchema(); allOfSchemaList.add(schema); @@ -107,7 +107,8 @@ public static Schema getSchema(RecordTypeSymbol typeSymbol, Components component static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components components, Map recordFieldMap, - AdditionalData additionalData, String recordName) { + AdditionalData additionalData, String recordName, + Set fieldsOnlyForRequiredList) { List allOfSchemaList = new ArrayList<>(); List typeInclusions = typeSymbol.typeInclusions(); for (TypeSymbol typeInclusion : typeInclusions) { @@ -124,15 +125,18 @@ static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components c .typeDescriptor(); Map includedRecordFieldMap = includedRecordTypeSymbol.fieldDescriptors(); for (Map.Entry includedRecordField : includedRecordFieldMap.entrySet()) { + if (!recordFieldMap.containsKey(includedRecordField.getKey())) { + continue; + } RecordFieldSymbol recordFieldSymbol = recordFieldMap.get(includedRecordField.getKey()); RecordFieldSymbol includedRecordFieldValue = includedRecordField.getValue(); - if (recordFieldSymbol == null - || !includedRecordFieldValue.typeDescriptor().equals(recordFieldSymbol.typeDescriptor())) { + if (!includedRecordFieldValue.typeDescriptor().equals(recordFieldSymbol.typeDescriptor())) { continue; } eliminateRedundantFields(recordFieldMap, additionalData, recordName, typeInclusion, - includedRecordField, recordFieldSymbol, includedRecordFieldValue); + includedRecordField, recordFieldSymbol, includedRecordFieldValue, + fieldsOnlyForRequiredList); } } } @@ -144,11 +148,15 @@ private static void eliminateRedundantFields(Map reco TypeSymbol typeInclusion, Map.Entry includedRecordField, RecordFieldSymbol recordFieldSymbol, - RecordFieldSymbol includedRecordFieldValue) { + RecordFieldSymbol includedRecordFieldValue, + Set fieldsOnlyForRequiredList) { boolean recordHasDefault = recordFieldSymbol.hasDefaultValue(); boolean includedHasDefault = includedRecordFieldValue.hasDefaultValue(); boolean hasTypeInclusionName = typeInclusion.getName().isPresent(); + boolean isIncludedOptional = includedRecordFieldValue.isOptional(); + boolean isRecordFieldOptional = recordFieldSymbol.isOptional(); + boolean recordFieldName = recordFieldSymbol.getName().isPresent(); if (recordHasDefault && includedHasDefault && hasTypeInclusionName) { Optional recordFieldDefaultValueOpt = getRecordFieldDefaultValue(recordName, @@ -199,12 +207,17 @@ private static void eliminateRedundantFields(Map reco } else if (!recordHasDefault && !includedHasDefault) { recordFieldMap.remove(includedRecordField.getKey()); } + if (!isRecordFieldOptional && isIncludedOptional && !recordHasDefault && recordFieldName) { + fieldsOnlyForRequiredList.add(MapperCommonUtils.unescapeIdentifier(recordFieldSymbol.getName().get())); + recordFieldMap.remove(includedRecordField.getKey()); + } } public static Map mapRecordFields(Map recordFieldMap, Components components, Set requiredFields, String recordName, boolean treatNilableAsOptional, - AdditionalData additionalData) { + AdditionalData additionalData, + Set fieldsOnlyForRequiredList) { Map properties = new LinkedHashMap<>(); for (Map.Entry recordField : recordFieldMap.entrySet()) { RecordFieldSymbol recordFieldSymbol = recordField.getValue(); @@ -213,6 +226,9 @@ public static Map mapRecordFields(Map (!treatNilableAsOptional || !UnionTypeMapper.hasNilableType(recordFieldSymbol.typeDescriptor()))) { requiredFields.add(recordFieldName); } + if (!fieldsOnlyForRequiredList.isEmpty()) { + requiredFields.addAll(fieldsOnlyForRequiredList); + } String recordFieldDescription = getRecordFieldTypeDescription(recordFieldSymbol); Schema recordFieldSchema = TypeMapperImpl.getTypeSchema(recordFieldSymbol.typeDescriptor(), components, additionalData); diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java index f22a780f2..e2f4dad62 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java @@ -32,6 +32,7 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.media.Schema; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -114,7 +115,7 @@ public Map getSchemaForRecordFields(Map requiredFields, String recordName, boolean treatNilableAsOptional) { return RecordTypeMapper.mapRecordFields(recordFieldMap, components, requiredFields, recordName, - treatNilableAsOptional, componentMapperData); + treatNilableAsOptional, componentMapperData, new HashSet<>()); } public TypeSymbol getReferredType(TypeSymbol typeSymbol) { diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml index efd97b07c..219d84805 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/record/included_record.yaml @@ -70,6 +70,52 @@ paths: type: array items: $ref: "#/components/schemas/RecD" + /recE: + post: + operationId: postRece + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecE" + /recH: + post: + operationId: postRech + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecH" + /recI: + post: + operationId: postReci + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecI" + /recJ: + post: + operationId: postRecj + responses: + "202": + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/RecK" components: schemas: Metadata: @@ -157,6 +203,95 @@ components: type: integer format: int64 additionalProperties: false + RecE: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - a + - e + type: object + properties: + a: + type: string + e: + type: string + additionalProperties: false + RecF: + type: object + properties: + f: + type: integer + format: int64 + additionalProperties: false + RecG: + type: object + allOf: + - $ref: "#/components/schemas/RecF" + - type: object + properties: + g: + type: integer + format: int64 + additionalProperties: false + RecH: + type: object + allOf: + - $ref: "#/components/schemas/RecG" + - required: + - f + - g + - h + type: object + properties: + h: + type: string + additionalProperties: false + RecI: + type: object + allOf: + - $ref: "#/components/schemas/RecG" + - required: + - g + - i + type: object + properties: + f: + type: integer + format: int64 + default: 10 + i: + type: string + additionalProperties: false + RecK: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - $ref: "#/components/schemas/RecF" + - $ref: "#/components/schemas/RecL" + - required: + - a + - first-name + - k + type: object + properties: + k: + type: integer + format: int64 + a: + type: string + additionalProperties: false + RecL: + required: + - id + type: object + properties: + first-name: + type: string + id: + type: integer + format: int64 + additionalProperties: false Resource: type: object allOf: diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal index 159508de1..506280cfb 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/record/included_record.bal @@ -72,6 +72,57 @@ type RecD record {| int d; |}; +// defaultable `a`` makes requried +type RecE record {| + *RecA; + string a; + string e; +|}; + +//optional test cases +type RecF record {| + int f?; +|}; + +type RecG record {| + *RecF; + int g?; +|}; + +type RecH record {| + *RecG; + int f; + int g; + string h; +|}; + +// optional with default value +type RecI record {| + *RecG; + int f = 10; + string i; + int g; +|}; + +type RecJ record {| + *http:Accepted; + RecK body; +|}; + +type RecK record {| + *RecA; + *RecF; + *RecL; + int k; + string a; + string first\-name; +|}; + +type RecL record {| + string first\-name?; + int id; +|}; + service /payloadV on new http:Listener(7080) { resource function get pods() returns Pod[] { return []; @@ -92,4 +143,29 @@ service /payloadV on new http:Listener(7080) { resource function post recD() returns RecD[] { return []; } + + resource function post recE() returns RecE[] { + return []; + } + + resource function post recH() returns RecH[] { + return []; + } + + resource function post recI() returns RecI[] { + return []; + } + + resource function post recJ() returns RecJ { + return { + body: + { + k: 10, + aa: "abc", + a: "abcd", + id: 11, + first\-name: "lnash" + } + }; + } } From 810d9a49176eda1d23d7a028578b11793af2fe2a Mon Sep 17 00:00:00 2001 From: lnash94 Date: Fri, 15 Nov 2024 14:43:31 +0530 Subject: [PATCH 8/9] Add optional field mapper for OAS to Ballerina code generation --- .../schema/AdvanceRecordTypeTests.java | 18 + .../schema/ballerina/type_inclusion.bal | 116 ++++++ .../schema/swagger/inclusion_types.yaml | 357 ++++++++++++++++++ .../generators/AllOfRecordTypeGenerator.java | 58 +++ 4 files changed, 549 insertions(+) create mode 100644 openapi-cli/src/test/resources/generators/schema/ballerina/type_inclusion.bal create mode 100644 openapi-cli/src/test/resources/generators/schema/swagger/inclusion_types.yaml diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AdvanceRecordTypeTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AdvanceRecordTypeTests.java index 108d776fe..db9322bf2 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AdvanceRecordTypeTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AdvanceRecordTypeTests.java @@ -108,4 +108,22 @@ public void generateForSchemaHasObjectTypeOnly() throws IOException, BallerinaOp serviceGenerationHandler.generateServiceFiles(oasServiceMetadata); syntaxTree = TypeHandler.getInstance().generateTypeSyntaxTree(); } + + @Test(description = "Generate record for schema has allOf types ") + public void generateTypeForTypeInclusions() throws IOException, BallerinaOpenApiException, + FormatterException { + Path definitionPath = RES_DIR.resolve("swagger/inclusion_types.yaml"); + OpenAPI openAPI = GeneratorUtils.normalizeOpenAPI(definitionPath, true, false); + TypeHandler.createInstance(openAPI, false); + ServiceGenerationHandler serviceGenerationHandler = new ServiceGenerationHandler(); + OASServiceMetadata oasServiceMetadata = new OASServiceMetadata.Builder() + .withOpenAPI(openAPI) + .withNullable(false) + .withFilters(FILTER) + .build(); + serviceGenerationHandler.generateServiceFiles(oasServiceMetadata); + syntaxTree = TypeHandler.getInstance().generateTypeSyntaxTree(); + assertGeneratedSyntaxTreeContainsExpectedSyntaxTree("schema/ballerina/type_inclusion.bal", + syntaxTree); + } } diff --git a/openapi-cli/src/test/resources/generators/schema/ballerina/type_inclusion.bal b/openapi-cli/src/test/resources/generators/schema/ballerina/type_inclusion.bal new file mode 100644 index 000000000..93e14cfb6 --- /dev/null +++ b/openapi-cli/src/test/resources/generators/schema/ballerina/type_inclusion.bal @@ -0,0 +1,116 @@ +import ballerina/http; + +public type Status record { + int observedGeneration; +}; + +public type Pod record { + *Resource; + ResourceKind kind = "Pod"; + PodSpec spec; +}; + +public type PodSpec record { + string nodeName; +}; + +public type ResourceBase record { + string group; + string version; + ResourceKind kind; + Metadata metadata; +}; + +public type Metadata record { + string name; + string displayName?; + string description?; +}; + +public type Resource record { + *ResourceBase; + record {} spec; + Status status?; +}; + +public type ServiceSpec record { + string clusterIP; +}; + +public type Service record { + *Resource; + ResourceKind kind = "Service"; + ServiceSpec spec; +}; + +public type RecF record {| + int f?; +|}; + +public type RecG record { + *RecF; + int g?; +}; + +public type RecD record { + *RecA; + string a = "aad"; + int d; +}; + +public type RecE record { + *RecA; + string a; + string e; +}; + +public type RecB record { + *RecA; + int b; +}; + +public type RecC record { + *RecA; + string aa = "aa"; + int c; +}; + +public type RecA record {| + string a = "a"; + string aa; +|}; + +public type RecL record {| + string first\-name?; + int id; +|}; + +public type ResourceKind "Service"|"Pod"; + +public type RecK record { + *RecA; + *RecF; + *RecL; + int k; + string a; + string first\-name; +}; + +public type RecH record { + *RecG; + string h; + int f; + int g; +}; + +public type RecI record { + *RecG; + int f = 10; + string i; + int g; +}; + +public type RecKAccepted record {| + *http:Accepted; + RecK body; +|}; diff --git a/openapi-cli/src/test/resources/generators/schema/swagger/inclusion_types.yaml b/openapi-cli/src/test/resources/generators/schema/swagger/inclusion_types.yaml new file mode 100644 index 000000000..219d84805 --- /dev/null +++ b/openapi-cli/src/test/resources/generators/schema/swagger/inclusion_types.yaml @@ -0,0 +1,357 @@ +openapi: 3.0.1 +info: + title: PayloadV + version: 0.0.0 +servers: + - url: "{server}:{port}/payloadV" + variables: + server: + default: http://localhost + port: + default: "7080" +paths: + /pods: + get: + operationId: getPods + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pod" + /services: + get: + operationId: getServices + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Service" + /recB: + get: + operationId: getRecb + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecB" + /recC: + get: + operationId: getRecc + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecC" + /recD: + post: + operationId: postRecd + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecD" + /recE: + post: + operationId: postRece + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecE" + /recH: + post: + operationId: postRech + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecH" + /recI: + post: + operationId: postReci + responses: + "201": + description: Created + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/RecI" + /recJ: + post: + operationId: postRecj + responses: + "202": + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/RecK" +components: + schemas: + Metadata: + required: + - name + type: object + properties: + name: + type: string + displayName: + type: string + description: + type: string + Pod: + type: object + allOf: + - $ref: "#/components/schemas/Resource" + - required: + - spec + type: object + properties: + kind: + allOf: + - $ref: "#/components/schemas/ResourceKind" + default: Pod + spec: + $ref: "#/components/schemas/PodSpec" + PodSpec: + required: + - nodeName + type: object + properties: + nodeName: + type: string + RecA: + required: + - aa + type: object + properties: + a: + type: string + default: a + aa: + type: string + additionalProperties: false + RecB: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - b + type: object + properties: + b: + type: integer + format: int64 + additionalProperties: false + RecC: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - c + type: object + properties: + aa: + type: string + default: aa + c: + type: integer + format: int64 + additionalProperties: false + RecD: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - d + type: object + properties: + a: + type: string + default: aad + d: + type: integer + format: int64 + additionalProperties: false + RecE: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - required: + - a + - e + type: object + properties: + a: + type: string + e: + type: string + additionalProperties: false + RecF: + type: object + properties: + f: + type: integer + format: int64 + additionalProperties: false + RecG: + type: object + allOf: + - $ref: "#/components/schemas/RecF" + - type: object + properties: + g: + type: integer + format: int64 + additionalProperties: false + RecH: + type: object + allOf: + - $ref: "#/components/schemas/RecG" + - required: + - f + - g + - h + type: object + properties: + h: + type: string + additionalProperties: false + RecI: + type: object + allOf: + - $ref: "#/components/schemas/RecG" + - required: + - g + - i + type: object + properties: + f: + type: integer + format: int64 + default: 10 + i: + type: string + additionalProperties: false + RecK: + type: object + allOf: + - $ref: "#/components/schemas/RecA" + - $ref: "#/components/schemas/RecF" + - $ref: "#/components/schemas/RecL" + - required: + - a + - first-name + - k + type: object + properties: + k: + type: integer + format: int64 + a: + type: string + additionalProperties: false + RecL: + required: + - id + type: object + properties: + first-name: + type: string + id: + type: integer + format: int64 + additionalProperties: false + Resource: + type: object + allOf: + - $ref: "#/components/schemas/ResourceBase" + - required: + - spec + type: object + properties: + spec: + type: object + properties: {} + status: + $ref: "#/components/schemas/Status" + ResourceBase: + required: + - group + - kind + - metadata + - version + type: object + properties: + group: + type: string + version: + type: string + kind: + $ref: "#/components/schemas/ResourceKind" + metadata: + $ref: "#/components/schemas/Metadata" + ResourceKind: + type: string + enum: + - Service + - Pod + Service: + type: object + allOf: + - $ref: "#/components/schemas/Resource" + - required: + - spec + type: object + properties: + kind: + allOf: + - $ref: "#/components/schemas/ResourceKind" + default: Service + spec: + $ref: "#/components/schemas/ServiceSpec" + ServiceSpec: + required: + - clusterIP + type: object + properties: + clusterIP: + type: string + Status: + required: + - observedGeneration + type: object + properties: + observedGeneration: + type: integer + format: int64 diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/AllOfRecordTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/AllOfRecordTypeGenerator.java index 94f01f892..020941af4 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/AllOfRecordTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/AllOfRecordTypeGenerator.java @@ -38,6 +38,7 @@ import io.ballerina.openapi.core.generators.type.model.GeneratorMetaData; import io.ballerina.openapi.core.generators.type.model.RecordMetadata; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -67,6 +68,7 @@ import static io.ballerina.compiler.syntax.tree.SyntaxKind.SEMICOLON_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.TYPE_KEYWORD; import static io.ballerina.openapi.core.generators.type.diagnostic.TypeGenerationDiagnosticMessages.OAS_TYPE_102; +import static io.ballerina.openapi.core.generators.type.diagnostic.TypeGenerationDiagnosticMessages.OAS_TYPE_103; /** * Generate TypeDefinitionNode and TypeDescriptorNode for allOf schemas. @@ -94,11 +96,13 @@ */ public class AllOfRecordTypeGenerator extends RecordTypeGenerator { private final List> restSchemas = new LinkedList<>(); + private final Map allProperties = new HashMap<>(); public AllOfRecordTypeGenerator(Schema schema, String typeName, boolean ignoreNullableFlag, HashMap subTypesMap, HashMap pregeneratedTypeMap) { super(schema, typeName, ignoreNullableFlag, subTypesMap, pregeneratedTypeMap); + allProperties.putAll(getAllPropertiesFromComposedSchema(schema)); } /** @@ -201,6 +205,7 @@ private ImmutablePair, List>> generateAllOfRecordFields(Lis if (Objects.isNull(required)) { required = new ArrayList<>(); } + updateRFieldsWithRequiredProperties(properties, required); required.addAll(requiredFields); recordFieldList.addAll(addRecordFields(required, properties.entrySet(), typeName)); addAdditionalSchemas(allOfSchema); @@ -230,6 +235,7 @@ private void handleCommonRequiredFields(List requiredFields, Schema refS List required = refSchema.getRequired(); if (Objects.nonNull(required)) { requiredFields.removeAll(required); + updateRFieldsWithRequiredProperties(properties, required); } Set>> fieldProperties = new HashSet<>(); for (Map.Entry> property : properties.entrySet()) { @@ -243,6 +249,14 @@ private void handleCommonRequiredFields(List requiredFields, Schema refS } } + private void updateRFieldsWithRequiredProperties(Map> properties, List required) { + for (String field: required) { + if (!properties.containsKey(field) && allProperties.containsKey(field)) { + properties.put(field, allProperties.get(field)); + } + } + } + private static Map> getPropertiesFromRefSchema(Schema schema) throws InvalidReferenceException { Map> properties = schema.getProperties(); if (Objects.isNull(properties)) { @@ -320,4 +334,48 @@ private void addAdditionalSchemas(Schema refSchema) { restSchemas.add((Schema) refSchema.getAdditionalProperties()); } } + + public Map getAllPropertiesFromComposedSchema(Schema schemaV) { + Map properties = new HashMap<>(); + if (!(schemaV instanceof ComposedSchema composedSchema)) { + return new HashMap<>(); + } + // Process allOf, anyOf, and oneOf schemas, including nested composed schemas + try { + addPropertiesFromSchemas(composedSchema.getAllOf(), properties); + addPropertiesFromSchemas(composedSchema.getAnyOf(), properties); + addPropertiesFromSchemas(composedSchema.getOneOf(), properties); + } catch (InvalidReferenceException e) { + diagnostics.add(new TypeGeneratorDiagnostic(OAS_TYPE_103, e.getMessage())); + } + return properties; + } + + private void addPropertiesFromSchemas(List schemas, Map properties) + throws InvalidReferenceException { + if (schemas != null) { + for (Schema schema : schemas) { + if (schema instanceof ComposedSchema composedSchema) { + // Recursively resolve nested composed schemas + properties.putAll(getAllPropertiesFromComposedSchema(composedSchema)); + } else { + // Add properties from standard schemas or resolved references + properties.putAll(resolveAndGetProperties(schema, GeneratorMetaData.getInstance().getOpenAPI())); + } + } + } + } + + private Map resolveAndGetProperties(Schema schema, OpenAPI openapi) + throws InvalidReferenceException { + if (schema.get$ref() != null) { + String refName; + refName = GeneratorUtils.extractReferenceType(schema.get$ref()); + schema = openapi.getComponents().getSchemas().get(refName); + } + if (schema instanceof ComposedSchema composedSchema) { + return getAllPropertiesFromComposedSchema(composedSchema); + } + return schema != null && schema.getProperties() != null ? schema.getProperties() : new HashMap<>(); + } } From 88c9bb94b80173b7daa7d7bdc77987dfacf65af0 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Fri, 15 Nov 2024 17:12:20 +0530 Subject: [PATCH 9/9] Sanitized functions parameters --- .../service/mapper/type/RecordTypeMapper.java | 81 +++++++++++++++---- .../service/mapper/type/TypeMapperImpl.java | 6 +- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java index dc3240037..8b6eb862d 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/RecordTypeMapper.java @@ -81,8 +81,11 @@ public static Schema getSchema(RecordTypeSymbol typeSymbol, Components component List allOfSchemaList = mapIncludedRecords(typeSymbol, components, recordFieldMap, additionalData, recordName, fieldsOnlyForRequiredList); - Map properties = mapRecordFields(recordFieldMap, components, requiredFields, - recordName, false, additionalData, fieldsOnlyForRequiredList); + RecordFieldMappingContext mappingContext = new RecordFieldMappingContext( + recordFieldMap, components, requiredFields, recordName, false, additionalData, + fieldsOnlyForRequiredList); + + Map properties = mapRecordFields(mappingContext); Optional restFieldType = typeSymbol.restTypeDescriptor(); if (restFieldType.isPresent()) { @@ -134,22 +137,24 @@ static List mapIncludedRecords(RecordTypeSymbol typeSymbol, Components c if (!includedRecordFieldValue.typeDescriptor().equals(recordFieldSymbol.typeDescriptor())) { continue; } - eliminateRedundantFields(recordFieldMap, additionalData, recordName, typeInclusion, - includedRecordField, recordFieldSymbol, includedRecordFieldValue, - fieldsOnlyForRequiredList); + IncludedFieldContext context = new IncludedFieldContext(recordFieldMap, recordName, + typeInclusion, includedRecordField, recordFieldSymbol, includedRecordFieldValue + ); + eliminateRedundantFields(context, additionalData, fieldsOnlyForRequiredList); } } } return allOfSchemaList; } - private static void eliminateRedundantFields(Map recordFieldMap, - AdditionalData additionalData, String recordName, - TypeSymbol typeInclusion, - Map.Entry includedRecordField, - RecordFieldSymbol recordFieldSymbol, - RecordFieldSymbol includedRecordFieldValue, + private static void eliminateRedundantFields(IncludedFieldContext context, AdditionalData additionalData, Set fieldsOnlyForRequiredList) { + Map recordFieldMap = context.recordFieldMap(); + String recordName = context.recordName(); + TypeSymbol typeInclusion = context.typeInclusion(); + Map.Entry includedRecordField = context.includedRecordField(); + RecordFieldSymbol recordFieldSymbol = context.recordFieldSymbol(); + RecordFieldSymbol includedRecordFieldValue = context.includedRecordFieldValue(); boolean recordHasDefault = recordFieldSymbol.hasDefaultValue(); boolean includedHasDefault = includedRecordFieldValue.hasDefaultValue(); @@ -213,12 +218,56 @@ private static void eliminateRedundantFields(Map reco } } - public static Map mapRecordFields(Map recordFieldMap, - Components components, Set requiredFields, - String recordName, boolean treatNilableAsOptional, - AdditionalData additionalData, - Set fieldsOnlyForRequiredList) { + /** + * Encapsulates the context of included fields in a record for processing. + * + * @param recordFieldMap A map containing record field symbols. + * @param recordName The name of the record being processed. + * @param typeInclusion The type symbol representing type inclusions in the record. + * @param includedRecordField An entry representing the included record field and its symbol. + * @param recordFieldSymbol The symbol of the current record field being processed. + * @param includedRecordFieldValue The symbol of the field in the included record. + */ + public record IncludedFieldContext( + Map recordFieldMap, + String recordName, + TypeSymbol typeInclusion, + Map.Entry includedRecordField, + RecordFieldSymbol recordFieldSymbol, + RecordFieldSymbol includedRecordFieldValue) { + } + + /** + * Encapsulates the context needed for mapping record fields to schemas. + * + * @param recordFieldMap A map containing record field symbols. + * @param components Components used for managing and storing schemas during mapping. + * @param requiredFields A set of field names that are required in the mapped schema. + * @param recordName The name of the record being processed. + * @param treatNilableAsOptional Flag indicating whether nilable fields should be treated as optional. + * @param additionalData Additional data required for schema generation and field processing. + * @param fieldsOnlyForRequiredList A set of fields that should be exclusively marked as required. + */ + public record RecordFieldMappingContext( + Map recordFieldMap, + Components components, + Set requiredFields, + String recordName, + boolean treatNilableAsOptional, + AdditionalData additionalData, + Set fieldsOnlyForRequiredList) { + } + + public static Map mapRecordFields(RecordFieldMappingContext context) { + Map recordFieldMap = context.recordFieldMap(); + Components components = context.components(); + Set requiredFields = context.requiredFields(); + String recordName = context.recordName(); + boolean treatNilableAsOptional = context.treatNilableAsOptional(); + AdditionalData additionalData = context.additionalData(); + Set fieldsOnlyForRequiredList = context.fieldsOnlyForRequiredList(); Map properties = new LinkedHashMap<>(); + for (Map.Entry recordField : recordFieldMap.entrySet()) { RecordFieldSymbol recordFieldSymbol = recordField.getValue(); String recordFieldName = MapperCommonUtils.unescapeIdentifier(recordField.getKey().trim()); diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java index e2f4dad62..943e0b812 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/type/TypeMapperImpl.java @@ -114,8 +114,10 @@ protected static void createComponentMapping(TypeReferenceTypeSymbol typeSymbol, public Map getSchemaForRecordFields(Map recordFieldMap, Set requiredFields, String recordName, boolean treatNilableAsOptional) { - return RecordTypeMapper.mapRecordFields(recordFieldMap, components, requiredFields, recordName, - treatNilableAsOptional, componentMapperData, new HashSet<>()); + RecordTypeMapper.RecordFieldMappingContext context = new RecordTypeMapper.RecordFieldMappingContext( + recordFieldMap, components, requiredFields, recordName, treatNilableAsOptional, componentMapperData, + new HashSet<>()); + return RecordTypeMapper.mapRecordFields(context); } public TypeSymbol getReferredType(TypeSymbol typeSymbol) {