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

[BallerinaToOpenAPI]Fix mapping to application/x-www-form-urlencoded when it has response and request body in resources #926

Merged
merged 4 commits into from
Mar 29, 2022
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 @@ -23,17 +23,13 @@
import io.ballerina.compiler.api.symbols.TypeSymbol;
import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.ArrayTypeDescriptorNode;
import io.ballerina.compiler.syntax.tree.BasicLiteralNode;
import io.ballerina.compiler.syntax.tree.ExpressionNode;
import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode;
import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode;
import io.ballerina.compiler.syntax.tree.MappingFieldNode;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode;
import io.ballerina.compiler.syntax.tree.RequiredParameterNode;
import io.ballerina.compiler.syntax.tree.SeparatedNodeList;
import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode;
import io.ballerina.compiler.syntax.tree.SpecificFieldNode;
import io.ballerina.compiler.syntax.tree.SyntaxKind;
import io.ballerina.compiler.syntax.tree.TypeDescriptorNode;
import io.ballerina.openapi.converter.Constants;
Expand All @@ -46,20 +42,22 @@
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.RequestBody;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import javax.ws.rs.core.MediaType;

import static io.ballerina.openapi.converter.Constants.APPLICATION_PREFIX;
import static io.ballerina.openapi.converter.Constants.HTTP_PAYLOAD;
import static io.ballerina.openapi.converter.Constants.JSON_POSTFIX;
import static io.ballerina.openapi.converter.Constants.MEDIA_TYPE;
import static io.ballerina.openapi.converter.Constants.OCTECT_STREAM_POSTFIX;
import static io.ballerina.openapi.converter.Constants.TEXT_POSTFIX;
import static io.ballerina.openapi.converter.Constants.TEXT_PREFIX;
import static io.ballerina.openapi.converter.Constants.XML_POSTFIX;
import static io.ballerina.openapi.converter.Constants.X_WWW_FORM_URLENCODED_POSTFIX;
import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.extractAnnotationFieldDetails;

/**
* OpenAPIRequestBodyMapper provides functionality for converting ballerina payload to OAS request body model.
Expand Down Expand Up @@ -123,8 +121,16 @@ public void handlePayloadAnnotation(RequiredParameterNode payloadNode, Map<Strin
if (mapMime != null) {
fields = mapMime.fields();
}
if (fields != null && (!fields.isEmpty() || fields.size() != 0)) {
handleMultipleMIMETypes(bodyParameter, fields, payloadNode, schema);

// Handle multiple mime type when attached with request payload annotation.
// ex: <pre> @http:Payload { mediaType:["application/json", "application/xml"] } Pet payload </pre>
if (fields != null && !fields.isEmpty()) {
List<String> mimeTypes = extractAnnotationFieldDetails(HTTP_PAYLOAD, MEDIA_TYPE, annotation,
semanticModel);
RequestBody requestBody = new RequestBody();
for (String mimeType: mimeTypes) {
createRequestBody(bodyParameter, payloadNode, schema, requestBody, mimeType);
}
} else {
//TODO : fill with rest of media types
handleSinglePayloadType(payloadNode, schema, bodyParameter, customMediaType);
Expand Down Expand Up @@ -165,8 +171,8 @@ private void handleSinglePayloadType(RequiredParameterNode payloadNode, Map<Stri
addConsumes(operationAdaptor, bodyParameter, mediaTypeString);
break;
case Constants.MAP_STRING:
mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_FORM_URLENCODED :
APPLICATION_PREFIX + customMediaPrefix + X_WWW_FORM_URLENCODED_POSTFIX;
mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_JSON :
APPLICATION_PREFIX + customMediaPrefix + JSON_POSTFIX;
Schema objectSchema = new ObjectSchema();
objectSchema.additionalProperties(new StringSchema());
io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType();
Expand Down Expand Up @@ -235,41 +241,6 @@ private void handleArrayTypePayload(Map<String, Schema> schema, ArrayTypeDescrip
}
}

/**
* Handle multiple mime type when attached with request payload annotation.
* ex: <pre> @http:Payload { mediaType:["application/json", "application/xml"] } Pet payload </pre>
*/
private void handleMultipleMIMETypes(RequestBody bodyParameter,
SeparatedNodeList<MappingFieldNode> fields,
RequiredParameterNode payloadNode, Map<String, Schema> schema) {

for (MappingFieldNode fieldNode: fields) {
if (!(fieldNode instanceof SpecificFieldNode) ||
!((SpecificFieldNode) fieldNode).fieldName().toString().equals(MEDIA_TYPE) ||
fieldNode.children() == null) {
continue;
}
RequestBody requestBody = new RequestBody();
SpecificFieldNode field = (SpecificFieldNode) fieldNode;
if (field.valueExpr().isEmpty()) {
continue;
}
ExpressionNode valueNode = field.valueExpr().get();
if (valueNode instanceof ListConstructorExpressionNode) {
SeparatedNodeList mimeList = ((ListConstructorExpressionNode) valueNode).expressions();
for (Object mime : mimeList) {
if (mime instanceof BasicLiteralNode) {
createRequestBody(bodyParameter, payloadNode, schema, requestBody,
((BasicLiteralNode) mime).literalToken().text());
}
}
} else {
createRequestBody(bodyParameter, payloadNode, schema, requestBody,
valueNode.toString());
}
}
}

private void createRequestBody(RequestBody bodyParameter, RequiredParameterNode payloadNode,
Map<String, Schema> schema, RequestBody requestBody, String mime) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode;
import io.ballerina.compiler.syntax.tree.FunctionSignatureNode;
import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode;
import io.ballerina.compiler.syntax.tree.MapTypeDescriptorNode;
import io.ballerina.compiler.syntax.tree.MappingFieldNode;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.NodeList;
Expand Down Expand Up @@ -123,6 +124,7 @@
import static io.ballerina.openapi.converter.Constants.X_WWW_FORM_URLENCODED_POSTFIX;
import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.extractAnnotationFieldDetails;
import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.extractCustomMediaType;
import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.getOpenApiSchema;

/**
* This class uses to map the Ballerina return details to the OAS response.
Expand Down Expand Up @@ -478,6 +480,19 @@ private Optional<ApiResponses> getAPIResponses(OperationAdaptor operationAdaptor
case OPTIONAL_TYPE_DESC:
return getAPIResponses(operationAdaptor, apiResponses,
((OptionalTypeDescriptorNode) typeNode).typeDescriptor(), customMediaPrefix, headers);
case MAP_TYPE_DESC:
apiResponse = setCacheHeader(headers, apiResponse, HTTP_200);
NipunaRanasinghe marked this conversation as resolved.
Show resolved Hide resolved
MapTypeDescriptorNode mapNode = (MapTypeDescriptorNode) typeNode;
ObjectSchema objectSchema = new ObjectSchema();
Schema<?> apiSchema = getOpenApiSchema(mapNode.mapTypeParamsNode().typeNode().kind());
objectSchema.additionalProperties(apiSchema);
mediaType.setSchema(objectSchema);
mediaTypeString = customMediaPrefix.isPresent() ? APPLICATION_PREFIX + customMediaPrefix.get() +
JSON_POSTFIX : MediaType.APPLICATION_JSON;
apiResponse.content(new Content().addMediaType(mediaTypeString, mediaType));
apiResponse.description(HTTP_200_DESCRIPTION);
apiResponses.put(HTTP_200, apiResponse);
return Optional.of(apiResponses);
default:
DiagnosticMessages errorMessage = DiagnosticMessages.OAS_CONVERTOR_101;
IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic(errorMessage, this.location,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ paths:
operationId: operation_post_/pets
requestBody:
content:
application/x-www-form-urlencoded:
application/json:
schema:
type: object
additionalProperties:
Expand All @@ -28,7 +28,7 @@ paths:
operationId: operation_post_/pets02
requestBody:
content:
application/x-www-form-urlencoded:
application/json:
schema:
type: object
additionalProperties:
Expand All @@ -49,4 +49,17 @@ paths:
responses:
"200":
description: Ok
/pets04:
post:
operationId: operation_post_/pets04
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
additionalProperties:
type: string
responses:
"200":
description: Ok
components: {}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,41 @@ paths:
type: object
additionalProperties:
type: string
/foo:
get:
operationId: operation_get_/foo
responses:
"200":
description: Ok
content:
application/x-www-form-urlencoded:
schema:
type: object
additionalProperties:
type: string
/bar:
get:
operationId: operation_get_/bar
responses:
"200":
description: Ok
content:
application/json:
schema:
type: object
additionalProperties:
type: string
/barint:
get:
operationId: operation_get_/barint
responses:
"200":
description: Ok
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int64
components: {}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ballerina/http;
import ballerina/mime;

listener http:Listener helloEp = new (9090);

Expand All @@ -17,4 +18,10 @@ service /payloadV on helloEp {
http:Ok ok = {body: ()};
return ok;
}

resource function post pets04(@http:Payload{mediaType: mime:APPLICATION_FORM_URLENCODED} map<string> req) returns
http:Ok {
http:Ok ok = {body: ()};
return ok;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,25 @@ service /payloadV on helloEp {
}
};
}
resource function get foo() returns @http:Payload {mediaType: mime:APPLICATION_FORM_URLENCODED} map<string> {
map<string> ms = {
"x": "abc",
"y": "cdf"
};
return ms;
}
resource function get bar() returns @http:Payload map<string> {
map<string> ms = {
"x": "abc",
"y": "cdf"
};
return ms;
}
resource function get barint() returns @http:Payload map<int> {
map<int> ms = {
"x": 1,
"y": 2
};
return ms;
}
}