From a39ad2d7cc170d7cf3e37ea98e575af32e64e7a3 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 15 Dec 2023 14:58:46 +0530 Subject: [PATCH 1/5] Add built-in subtype support in record fields --- .../openapi/converter/Constants.java | 11 +++++ .../service/OpenAPIComponentMapper.java | 48 ++++++++++++++++--- .../converter/utils/ConverterCommonUtils.java | 23 ++++++++- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java index aeb3e1bdf..42e21a093 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java @@ -28,6 +28,17 @@ public class Constants { public static final String ATTR_HOST = "host"; public static final String INT = "int"; + public static final String INT_SIGNED8 = "int_signed8"; + public static final String INT_SIGNED16 = "int_signed16"; + public static final String INT_SIGNED32 = "int_signed32"; + public static final String INT_UNSIGNED8 = "int_unsigned8"; + public static final String INT_UNSIGNED16 = "int_unsigned16"; + public static final String INT_UNSIGNED32 = "int_unsigned32"; + public static final String XML_COMMENT = "xml_comment"; + public static final String XML_ELEMENT = "xml_element"; + public static final String XML_PROCESSING_INSTRUCTION = "xml_processing_instruction"; + public static final String XML_TEXT = "xml_text"; + public static final String STRING_CHAR = "string_char"; public static final String INTEGER = "integer"; public static final String NUMBER = "number"; public static final String STRING = "string"; diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java index a0e075dbf..cb8451698 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java @@ -146,6 +146,10 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym handleRecordTypeSymbol((RecordTypeSymbol) type, schema, componentName, apiDocs); break; case TYPE_REFERENCE: + if (isBuiltInSubTypes((TypeReferenceTypeSymbol) type)) { + createComponentSchema(schema, ((TypeReferenceTypeSymbol) type).typeDescriptor()); + break; + } schema.put(componentName, new ObjectSchema().$ref(ConverterCommonUtils.unescapeIdentifier( type.getName().orElseThrow().trim()))); components.setSchemas(schema); @@ -172,6 +176,22 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym schema.put(componentName, intSchema); components.setSchemas(schema); break; + case INT_SIGNED32: + Schema int32Schema = new IntegerSchema().description(typeDoc).format("int32"); + setConstraintValueToSchema(constraintAnnot, int32Schema); + schema.put(componentName, int32Schema); + components.setSchemas(schema); + break; + case INT_UNSIGNED32: + case INT_UNSIGNED16: + case INT_SIGNED16: + case INT_UNSIGNED8: + case INT_SIGNED8: + Schema subIntSchema = new IntegerSchema().description(typeDoc).format(null); + setConstraintValueToSchema(constraintAnnot, subIntSchema); + schema.put(componentName, subIntSchema); + components.setSchemas(schema); + break; case DECIMAL: Schema decimalSchema = new NumberSchema().format(DOUBLE).description(typeDoc); setConstraintValueToSchema(constraintAnnot, decimalSchema); @@ -244,6 +264,17 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym } } + public static boolean isBuiltInSubTypes(TypeReferenceTypeSymbol typeSymbol) { + TypeSymbol referredType = typeSymbol.typeDescriptor(); + return switch (referredType.typeKind()) { + case INT_SIGNED8, INT_SIGNED16, INT_SIGNED32, INT_UNSIGNED8, INT_UNSIGNED16, INT_UNSIGNED32, + XML_COMMENT, XML_ELEMENT, XML_PROCESSING_INSTRUCTION, XML_TEXT, + STRING_CHAR-> + true; + default -> false; + }; + } + /** * Remove readonly from the type symbol. * @@ -409,34 +440,39 @@ private ObjectSchema generateObjectSchemaFromRecordFields(Map sc if (!field.getValue().isOptional()) { required.add(fieldName); } - TypeDescKind fieldTypeKind = field.getValue().typeDescriptor().typeKind(); + TypeSymbol fieldType = field.getValue().typeDescriptor(); + if (fieldType instanceof TypeReferenceTypeSymbol && + isBuiltInSubTypes((TypeReferenceTypeSymbol) fieldType)) { + fieldType = ((TypeReferenceTypeSymbol) fieldType).typeDescriptor(); + } + TypeDescKind fieldTypeKind = fieldType.typeKind(); String type = fieldTypeKind.toString().toLowerCase(Locale.ENGLISH); if (fieldTypeKind == TypeDescKind.INTERSECTION) { - TypeSymbol readOnlyExcludedType = excludeReadonlyIfPresent(field.getValue().typeDescriptor()); + TypeSymbol readOnlyExcludedType = excludeReadonlyIfPresent(fieldType); type = readOnlyExcludedType.typeKind().toString().toLowerCase(Locale.ENGLISH); } Schema property = ConverterCommonUtils.getOpenApiSchema(type); if (fieldTypeKind == TypeDescKind.TYPE_REFERENCE) { - TypeReferenceTypeSymbol typeReference = (TypeReferenceTypeSymbol) field.getValue().typeDescriptor(); + TypeReferenceTypeSymbol typeReference = (TypeReferenceTypeSymbol) fieldType; property = handleTypeReference(schema, typeReference, property, isSameRecord(ConverterCommonUtils.unescapeIdentifier( typeReference.definition().getName().get()), typeReference)); schema = components.getSchemas(); } else if (fieldTypeKind == TypeDescKind.UNION) { - property = handleUnionType((UnionTypeSymbol) field.getValue().typeDescriptor(), property, + property = handleUnionType((UnionTypeSymbol) fieldType, property, componentName); schema = components.getSchemas(); } else if (fieldTypeKind == TypeDescKind.MAP) { - MapTypeSymbol mapTypeSymbol = (MapTypeSymbol) field.getValue().typeDescriptor(); + MapTypeSymbol mapTypeSymbol = (MapTypeSymbol) fieldType; property = handleMapType(schema, componentName, property, mapTypeSymbol); schema = components.getSchemas(); } if (property instanceof ArraySchema && !(((ArraySchema) property).getItems() instanceof ComposedSchema)) { Boolean nullable = property.getNullable(); - property = mapArrayToArraySchema(schema, field.getValue().typeDescriptor(), componentName); + property = mapArrayToArraySchema(schema, fieldType, componentName); property.setNullable(nullable); schema = components.getSchemas(); } diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java index 9a516c70c..7666c00af 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java @@ -114,6 +114,7 @@ public static Schema getOpenApiSchema(String type) { Schema schema; switch (type) { case Constants.STRING: + case Constants.STRING_CHAR: case Constants.PLAIN: schema = new StringSchema(); break; @@ -129,6 +130,18 @@ public static Schema getOpenApiSchema(String type) { schema = new IntegerSchema(); schema.setFormat("int64"); break; + case Constants.INT_SIGNED32: + schema = new IntegerSchema(); + schema.setFormat("int32"); + break; + case Constants.INT_UNSIGNED32: + case Constants.INT_UNSIGNED16: + case Constants.INT_SIGNED16: + case Constants.INT_UNSIGNED8: + case Constants.INT_SIGNED8: + schema = new IntegerSchema(); + schema.setFormat(null); + break; case Constants.BYTE_ARRAY: case Constants.OCTET_STREAM: schema = new StringSchema(); @@ -152,10 +165,16 @@ public static Schema getOpenApiSchema(String type) { schema = new ObjectSchema(); schema.setAdditionalProperties(new StringSchema()); break; - case Constants.TYPE_REFERENCE: - case Constants.TYPEREFERENCE: case Constants.XML: + case Constants.XML_ELEMENT: + case Constants.XML_PROCESSING_INSTRUCTION: + case Constants.XML_TEXT: + case Constants.XML_COMMENT: case Constants.JSON: + schema = new ObjectSchema(); + break; + case Constants.TYPE_REFERENCE: + case Constants.TYPEREFERENCE: default: schema = new Schema<>(); break; From 2f88fab6bcbfd829e2a95165c6af9149313df46b Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 15 Dec 2023 14:58:56 +0530 Subject: [PATCH 2/5] Add test case --- .../generators/openapi/DataTypeTests.java | 6 ++ .../built_in_sub_types_in_record.bal | 58 ++++++++++++ .../built_in_sub_types_in_record.yaml | 90 +++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/DataTypeTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/DataTypeTests.java index b5d0caa4d..b1f652ae1 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/DataTypeTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/DataTypeTests.java @@ -59,6 +59,12 @@ public void testForTupleType() throws IOException { TestUtils.compareWithGeneratedFile(ballerinaFilePath, "data_type/tuple_type.yaml"); } + @Test(description = "test for Ballerina built-in subtypes as record fields") + public void testForBuiltInSubTypes() throws IOException { + Path ballerinaFilePath = RES_DIR.resolve("data_type/built_in_sub_types_in_record.bal"); + TestUtils.compareWithGeneratedFile(ballerinaFilePath, "data_type/built_in_sub_types_in_record.yaml"); + } + @AfterMethod public void cleanUp() { TestUtils.deleteDirectory(this.tempDir); diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal new file mode 100644 index 000000000..eec64f131 --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal @@ -0,0 +1,58 @@ +import ballerina/http; + +public type BalSignedInts record {| + int:Signed32 signed32; + int:Signed16 signed16; + int:Signed8 signed8; +|}; + +public type BalUnsignedInts record {| + int:Unsigned32 unsigned32; + int:Unsigned16 unsigned16; + int:Unsigned8 unsigned8; +|}; + +public type BalInts record {| + BalSignedInts signed; + BalUnsignedInts unsigned; +|}; + +public type BalXmls record {| + xml:Comment comment; + xml:Element element; + xml:ProcessingInstruction processingInstruction; + xml:Text text; +|}; + +public type BalSubTypes record {| + string:Char char; + BalInts ints; + BalXmls xmls; +|}; + +service /payloadV on new http:Listener(9090) { + + resource function get path() returns BalSubTypes { + return { + char: "a", + ints: { + signed: { + signed32: 32, + signed16: 16, + signed8: 8 + }, + unsigned: { + unsigned32: 32, + unsigned16: 16, + unsigned8: 8 + } + }, + xmls: { + comment: xml``, + element: xml`element`, + processingInstruction: xml``, + text: xml`text` + } + }; + } +} diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml new file mode 100644 index 000000000..ae4379fd6 --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml @@ -0,0 +1,90 @@ +openapi: 3.0.1 +info: + title: PayloadV + version: 0.0.0 +servers: + - url: "{server}:{port}/payloadV" + variables: + server: + default: http://localhost + port: + default: "9090" +paths: + /path: + get: + operationId: getPath + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BalSubTypes' +components: + schemas: + BalSubTypes: + required: + - char + - ints + - xmls + type: object + properties: + char: + type: string + ints: + $ref: '#/components/schemas/BalInts' + xmls: + $ref: '#/components/schemas/BalXmls' + BalUnsignedInts: + required: + - unsigned16 + - unsigned32 + - unsigned8 + type: object + properties: + unsigned32: + type: integer + unsigned16: + type: integer + unsigned8: + type: integer + BalXmls: + required: + - comment + - element + - processingInstruction + - text + type: object + properties: + comment: + type: object + element: + type: object + processingInstruction: + type: object + text: + type: object + BalInts: + required: + - signed + - unsigned + type: object + properties: + signed: + $ref: '#/components/schemas/BalSignedInts' + unsigned: + $ref: '#/components/schemas/BalUnsignedInts' + BalSignedInts: + required: + - signed16 + - signed32 + - signed8 + type: object + properties: + signed32: + type: integer + format: int32 + signed16: + type: integer + signed8: + type: integer From 2144454dacb687a717a7529430043e8e5f2e1430 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 15 Dec 2023 15:06:49 +0530 Subject: [PATCH 3/5] Fix test failures --- .../converter/utils/ConverterCommonUtils.java | 6 ++---- .../data_type/built_in_sub_types_in_record.yaml | 12 ++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java index 7666c00af..ed7485556 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/utils/ConverterCommonUtils.java @@ -165,16 +165,14 @@ public static Schema getOpenApiSchema(String type) { schema = new ObjectSchema(); schema.setAdditionalProperties(new StringSchema()); break; + case Constants.TYPE_REFERENCE: + case Constants.TYPEREFERENCE: case Constants.XML: case Constants.XML_ELEMENT: case Constants.XML_PROCESSING_INSTRUCTION: case Constants.XML_TEXT: case Constants.XML_COMMENT: case Constants.JSON: - schema = new ObjectSchema(); - break; - case Constants.TYPE_REFERENCE: - case Constants.TYPEREFERENCE: default: schema = new Schema<>(); break; diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml index ae4379fd6..6d5b23773 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml @@ -56,14 +56,10 @@ components: - text type: object properties: - comment: - type: object - element: - type: object - processingInstruction: - type: object - text: - type: object + comment: {} + element: {} + processingInstruction: {} + text: {} BalInts: required: - signed From 59556c8b09b050176400801af0585d2dd524926b Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 15 Dec 2023 16:26:46 +0530 Subject: [PATCH 4/5] Fix bad sad error --- .../converter/service/OpenAPIComponentMapper.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java index cb8451698..f539d2371 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIComponentMapper.java @@ -124,6 +124,9 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym } TypeReferenceTypeSymbol typeRef = (TypeReferenceTypeSymbol) typeSymbol; TypeSymbol type = typeRef.typeDescriptor(); + if (type instanceof TypeReferenceTypeSymbol && isBuiltInSubTypes((TypeReferenceTypeSymbol) type)) { + type = ((TypeReferenceTypeSymbol) type).typeDescriptor(); + } if (type.typeKind() == TypeDescKind.INTERSECTION) { type = excludeReadonlyIfPresent(type); } @@ -146,10 +149,6 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym handleRecordTypeSymbol((RecordTypeSymbol) type, schema, componentName, apiDocs); break; case TYPE_REFERENCE: - if (isBuiltInSubTypes((TypeReferenceTypeSymbol) type)) { - createComponentSchema(schema, ((TypeReferenceTypeSymbol) type).typeDescriptor()); - break; - } schema.put(componentName, new ObjectSchema().$ref(ConverterCommonUtils.unescapeIdentifier( type.getName().orElseThrow().trim()))); components.setSchemas(schema); @@ -160,6 +159,7 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym } break; case STRING: + case STRING_CHAR: Schema stringSchema = new StringSchema().description(typeDoc); setConstraintValueToSchema(constraintAnnot, stringSchema); schema.put(componentName, stringSchema); @@ -167,6 +167,10 @@ public void createComponentSchema(Map schema, TypeSymbol typeSym break; case JSON: case XML: + case XML_ELEMENT: + case XML_PROCESSING_INSTRUCTION: + case XML_TEXT: + case XML_COMMENT: schema.put(componentName, new ObjectSchema().description(typeDoc)); components.setSchemas(schema); break; From e45165789804d6f0c1bdff5e835304f6d4a51a1a Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 15 Dec 2023 16:26:53 +0530 Subject: [PATCH 5/5] Update test case --- .../built_in_sub_types_in_record.bal | 23 ++++++- .../built_in_sub_types_in_record.yaml | 66 +++++++++++++++---- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal index eec64f131..a35a0e93a 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal @@ -30,9 +30,22 @@ public type BalSubTypes record {| BalXmls xmls; |}; +type Unsigned8 int:Unsigned8; +type Unsigned16 int:Unsigned16; +type Unsigned32 int:Unsigned32; +type Signed8 int:Signed8; +type Signed16 int:Signed16; +type Signed32 int:Signed32; +type Char string:Char; +type XmlElement xml:Element; +type XmlComment xml:Comment; +type XmlText xml:Text; +type XmlProcessingInstruction xml:ProcessingInstruction; + + service /payloadV on new http:Listener(9090) { - resource function get path() returns BalSubTypes { + resource function get path1() returns BalSubTypes { return { char: "a", ints: { @@ -55,4 +68,12 @@ service /payloadV on new http:Listener(9090) { } }; } + + resource function get path2() returns Unsigned8|Unsigned16|Unsigned32| + Signed8|Signed16|Signed32| + XmlElement|XmlComment|XmlText|XmlProcessingInstruction| + Char { + + return 32; + } } diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml index 6d5b23773..6d5e24c57 100644 --- a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/data_type/built_in_sub_types_in_record.yaml @@ -10,9 +10,9 @@ servers: port: default: "9090" paths: - /path: + /path1: get: - operationId: getPath + operationId: getPath1 responses: "200": description: Ok @@ -20,8 +20,44 @@ paths: application/json: schema: $ref: '#/components/schemas/BalSubTypes' + /path2: + get: + operationId: getPath2 + responses: + "200": + description: Ok + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Unsigned8' + - $ref: '#/components/schemas/Unsigned16' + - $ref: '#/components/schemas/Unsigned32' + - $ref: '#/components/schemas/Signed8' + - $ref: '#/components/schemas/Signed16' + - $ref: '#/components/schemas/Signed32' + - $ref: '#/components/schemas/XmlElement' + - $ref: '#/components/schemas/XmlComment' + - $ref: '#/components/schemas/XmlText' + - $ref: '#/components/schemas/XmlProcessingInstruction' + - $ref: '#/components/schemas/Char' components: schemas: + BalInts: + required: + - signed + - unsigned + type: object + properties: + signed: + $ref: '#/components/schemas/BalSignedInts' + unsigned: + $ref: '#/components/schemas/BalUnsignedInts' + Signed32: + type: integer + format: int32 + Signed8: + type: integer BalSubTypes: required: - char @@ -35,6 +71,10 @@ components: $ref: '#/components/schemas/BalInts' xmls: $ref: '#/components/schemas/BalXmls' + Unsigned8: + type: integer + Signed16: + type: integer BalUnsignedInts: required: - unsigned16 @@ -48,6 +88,10 @@ components: type: integer unsigned8: type: integer + XmlProcessingInstruction: + type: object + Unsigned32: + type: integer BalXmls: required: - comment @@ -60,16 +104,12 @@ components: element: {} processingInstruction: {} text: {} - BalInts: - required: - - signed - - unsigned + Char: + type: string + XmlComment: + type: object + XmlElement: type: object - properties: - signed: - $ref: '#/components/schemas/BalSignedInts' - unsigned: - $ref: '#/components/schemas/BalUnsignedInts' BalSignedInts: required: - signed16 @@ -84,3 +124,7 @@ components: type: integer signed8: type: integer + Unsigned16: + type: integer + XmlText: + type: object