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..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); } @@ -156,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); @@ -163,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; @@ -172,6 +180,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 +268,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 +444,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..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 @@ -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(); @@ -155,6 +168,10 @@ public static Schema getOpenApiSchema(String type) { 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: default: schema = new Schema<>(); 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..a35a0e93a --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/data_type/built_in_sub_types_in_record.bal @@ -0,0 +1,79 @@ +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; +|}; + +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 path1() 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` + } + }; + } + + 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 new file mode 100644 index 000000000..6d5e24c57 --- /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,130 @@ +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: + /path1: + get: + operationId: getPath1 + responses: + "200": + description: Ok + content: + 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 + - ints + - xmls + type: object + properties: + char: + type: string + ints: + $ref: '#/components/schemas/BalInts' + xmls: + $ref: '#/components/schemas/BalXmls' + Unsigned8: + type: integer + Signed16: + type: integer + BalUnsignedInts: + required: + - unsigned16 + - unsigned32 + - unsigned8 + type: object + properties: + unsigned32: + type: integer + unsigned16: + type: integer + unsigned8: + type: integer + XmlProcessingInstruction: + type: object + Unsigned32: + type: integer + BalXmls: + required: + - comment + - element + - processingInstruction + - text + type: object + properties: + comment: {} + element: {} + processingInstruction: {} + text: {} + Char: + type: string + XmlComment: + type: object + XmlElement: + type: object + BalSignedInts: + required: + - signed16 + - signed32 + - signed8 + type: object + properties: + signed32: + type: integer + format: int32 + signed16: + type: integer + signed8: + type: integer + Unsigned16: + type: integer + XmlText: + type: object