Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Master] Add built-in subtype support in record fields for OAS generation #1582

Merged
merged 5 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ public void createComponentSchema(Map<String, Schema> 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);
}
Expand Down Expand Up @@ -156,13 +159,18 @@ public void createComponentSchema(Map<String, Schema> schema, TypeSymbol typeSym
}
break;
case STRING:
case STRING_CHAR:
Schema stringSchema = new StringSchema().description(typeDoc);
setConstraintValueToSchema(constraintAnnot, stringSchema);
schema.put(componentName, stringSchema);
components.setSchemas(schema);
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;
Expand All @@ -172,6 +180,22 @@ public void createComponentSchema(Map<String, Schema> 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);
Expand Down Expand Up @@ -244,6 +268,17 @@ public void createComponentSchema(Map<String, Schema> 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.
*
Expand Down Expand Up @@ -409,34 +444,39 @@ private ObjectSchema generateObjectSchemaFromRecordFields(Map<String, Schema> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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`<!-- comment -->`,
element: xml`<element>element</element>`,
processingInstruction: xml`<?processing instruction?>`,
text: xml`text`
}
};
}

resource function get path2() returns Unsigned8|Unsigned16|Unsigned32|
Signed8|Signed16|Signed32|
XmlElement|XmlComment|XmlText|XmlProcessingInstruction|
Char {

return 32;
}
}
Original file line number Diff line number Diff line change
@@ -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
Loading