Skip to content

Commit 02843ba

Browse files
committed
Add nullable support
1 parent fc7b9b6 commit 02843ba

File tree

3 files changed

+149
-2
lines changed

3 files changed

+149
-2
lines changed

asyncapi-cli/src/main/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateModuleMemberDeclarationNode.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package io.ballerina.asyncapi.codegenerator.usecase;
2020

2121
import com.fasterxml.jackson.databind.node.TextNode;
22+
import io.apicurio.datamodels.core.models.Extension;
2223
import io.ballerina.asyncapi.codegenerator.configuration.BallerinaAsyncApiException;
2324
import io.ballerina.asyncapi.codegenerator.configuration.Constants;
2425
import io.ballerina.asyncapi.codegenerator.entity.Schema;
@@ -177,12 +178,14 @@ private void addRecordField(List<String> required, List<Node> recordFieldList, M
177178
private TypeDescriptorNode getTypeDescriptorNode(Schema schema)
178179
throws BallerinaAsyncApiException {
179180
if (schema.getType() != null || schema.getSchemaProperties() != null) {
180-
return getTypeDescriptorNodeForObjects(schema);
181+
TypeDescriptorNode originalTypeDesc = getTypeDescriptorNodeForObjects(schema);
182+
return addNullableType(schema, originalTypeDesc);
181183
} else if (schema.getRef() != null) {
182184
String type = codegenUtils.extractReferenceType(schema.getRef());
183185
type = codegenUtils.getValidName(type, true);
184186
Token typeName = AbstractNodeFactory.createIdentifierToken(type);
185-
return createBuiltinSimpleNameReferenceNode(null, typeName);
187+
TypeDescriptorNode originalTypeDesc = createBuiltinSimpleNameReferenceNode(null, typeName);
188+
return addNullableType(schema, originalTypeDesc);
186189
} else {
187190
// This contains a fallback to Ballerina common type `anydata` if the Async Api specification type is not
188191
// defined.
@@ -329,4 +332,13 @@ public TypeDescriptorNode getTypeDescriptorNodeForArraySchema(Schema schema) thr
329332
}
330333
}
331334

335+
private TypeDescriptorNode addNullableType(Schema schema, TypeDescriptorNode originalTypeDesc) {
336+
if (schema.getExtension("x-nullable") != null) {
337+
Extension identifier = (Extension) schema.getExtension("x-nullable");
338+
if (identifier.value.equals(true)) {
339+
return createOptionalTypeDescriptorNode(originalTypeDesc, createToken(QUESTION_MARK_TOKEN));
340+
}
341+
}
342+
return originalTypeDesc;
343+
}
332344
}

asyncapi-cli/src/test/java/io/ballerina/asyncapi/codegenerator/usecase/GenerateModuleMemberDeclarationNodeTest.java

+58
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,62 @@ public void testGenerateWithInvalidNumberFormat() throws BallerinaAsyncApiExcept
171171
Generator generateRecordNode = new GenerateModuleMemberDeclarationNode(entry);
172172
generateRecordNode.generate();
173173
}
174+
175+
@Test(description = "Test the functionality of the generate function " +
176+
"when there are nullable fields in the schema")
177+
public void testGenerateWithNullables() throws BallerinaAsyncApiException {
178+
String asyncApiSpecStr = fileRepository
179+
.getFileContentFromResources("specs/spec-single-schema-with-x-nullable.yml");
180+
String asyncApiSpecJson = fileRepository.convertYamlToJson(asyncApiSpecStr);
181+
AaiDocument asyncApiSpec = (Aai20Document) Library.readDocumentFromJSONString(asyncApiSpecJson);
182+
Extractor extractSchemasFromSpec = new ExtractSchemasFromSpec(asyncApiSpec);
183+
Map<String, Schema> schemas = extractSchemasFromSpec.extract();
184+
185+
Iterator<Map.Entry<String, Schema>> iterator = schemas.entrySet().iterator();
186+
Map.Entry<String, Schema> firstEntry = iterator.next();
187+
Generator generateRecordNode1 = new GenerateModuleMemberDeclarationNode(firstEntry);
188+
TypeDefinitionNode typeDefinitionNode1 = generateRecordNode1.generate();
189+
Assert.assertEquals(typeDefinitionNode1.typeName().text(), "TotalPriceSet");
190+
191+
Map.Entry<String, Schema> secondEntry = iterator.next();
192+
Generator generateRecordNode2 = new GenerateModuleMemberDeclarationNode(secondEntry);
193+
TypeDefinitionNode typeDefinitionNode2 = generateRecordNode2.generate();
194+
Assert.assertEquals(typeDefinitionNode2.typeName().text(), "Price");
195+
196+
Map.Entry<String, Schema> thirdEntry = iterator.next();
197+
Generator generateRecordNode3 = new GenerateModuleMemberDeclarationNode(thirdEntry);
198+
TypeDefinitionNode typeDefinitionNode3 = generateRecordNode3.generate();
199+
Assert.assertEquals(typeDefinitionNode3.typeName().text(), "OrderEvent");
200+
201+
Map.Entry<String, Schema> forthEntry = iterator.next();
202+
Generator generateRecordNode4 = new GenerateModuleMemberDeclarationNode(forthEntry);
203+
TypeDefinitionNode typeDefinitionNode4 = generateRecordNode4.generate();
204+
Assert.assertEquals(typeDefinitionNode4.typeName().text(), "TaxLine");
205+
206+
Assert.assertTrue(typeDefinitionNode3.typeDescriptor() instanceof RecordTypeDescriptorNode);
207+
RecordTypeDescriptorNode recordTypeDescriptorNode3 =
208+
(RecordTypeDescriptorNode) typeDefinitionNode3.typeDescriptor();
209+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(0)).typeName().toSourceCode(),
210+
"TotalPriceSet?");
211+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(1)).typeName().toSourceCode(),
212+
"TaxLine[]?");
213+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(2)).typeName().toSourceCode(),
214+
"decimal?");
215+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(3)).typeName().toSourceCode(),
216+
"record { Priceshop_money?;Price?presentment_money?;} ?");
217+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(4)).typeName().toSourceCode(),
218+
"int?");
219+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(5)).typeName().toSourceCode(),
220+
"boolean?");
221+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode3.fields().get(6)).typeName().toSourceCode(),
222+
"string?");
223+
224+
Assert.assertTrue(typeDefinitionNode4.typeDescriptor() instanceof RecordTypeDescriptorNode);
225+
RecordTypeDescriptorNode recordTypeDescriptorNode4 =
226+
(RecordTypeDescriptorNode) typeDefinitionNode4.typeDescriptor();
227+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode4.fields().get(2)).typeName().toSourceCode(),
228+
"string");
229+
Assert.assertEquals(((RecordFieldNode) recordTypeDescriptorNode4.fields().get(3)).typeName().toSourceCode(),
230+
"string?");
231+
}
174232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
asyncapi: 2.1.0
2+
components:
3+
schemas:
4+
OrderEvent:
5+
properties:
6+
id:
7+
type: integer
8+
description: The ID of the order, used for API purposes. This is different from the order_number property, which is the ID used by the shop owner and customer.
9+
x-nullable: true
10+
email:
11+
type: string
12+
description: The customer's email address.
13+
x-nullable: true
14+
confirmed:
15+
type: boolean
16+
description: Confirmation status
17+
x-nullable: true
18+
rate:
19+
type: number
20+
description: The rate of tax to be applied.
21+
x-nullable: true
22+
tax_lines:
23+
type: array
24+
items:
25+
$ref: '#/components/schemas/TaxLine'
26+
description: An array of tax line objects, each of which details a tax applicable to the order. When creating an order through the API, tax lines can be specified on the order or the line items but not both. Tax lines specified on the order are split across the taxable line items in the created order.
27+
x-nullable: true
28+
total_price_set:
29+
$ref: '#/components/schemas/TotalPriceSet'
30+
x-nullable: true
31+
total_tax_set:
32+
type: object
33+
properties:
34+
shop_money:
35+
$ref: '#/components/schemas/Price'
36+
presentment_money:
37+
$ref: '#/components/schemas/Price'
38+
x-nullable: true
39+
description: The total tax applied to the order in shop and presentment currencies.
40+
x-nullable: true
41+
TaxLine:
42+
type: object
43+
properties:
44+
price:
45+
type: string
46+
description: The amount of tax to be charged in the shop currency.
47+
x-nullable: false
48+
rate:
49+
type: number
50+
description: The rate of tax to be applied.
51+
title:
52+
type: string
53+
description: The name of the tax.
54+
x-nullable: true
55+
channel_liable:
56+
type: boolean
57+
description: Whether the channel that submitted the tax line is liable for remitting. A value of null indicates unknown liability for the tax line.
58+
description: Tax line object, which details a tax applicable to the order.
59+
TotalPriceSet:
60+
type: object
61+
properties:
62+
shop_money:
63+
$ref: '#/components/schemas/Price'
64+
presentment_money:
65+
$ref: '#/components/schemas/Price'
66+
description: The total price of the order in shop and presentment currencies.
67+
Price:
68+
type: object
69+
properties:
70+
amount:
71+
type: string
72+
description: The variant's price or compare-at price in the presentment currency.
73+
currency_code:
74+
type: string
75+
description: The three-letter code (ISO 4217 format) for one of the shop's enabled presentment currencies.
76+
x-nullable: true
77+
description: The price object

0 commit comments

Comments
 (0)