From 4a7484a5b9f8117e08db50c8dc21aa41759d4997 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Fri, 15 Nov 2024 12:24:31 +0530 Subject: [PATCH] Add optional field mapper --- .../service/mapper/type/RecordTypeMapper.java | 36 +++-- .../service/mapper/type/TypeMapperImpl.java | 4 +- .../expected_gen/record/included_record.yaml | 135 ++++++++++++++++++ .../record/included_record.bal | 76 ++++++++++ 4 files changed, 240 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..ae83b465a 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,8 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.media.Schema; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -114,7 +116,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" + } + }; + } }