Skip to content

Commit

Permalink
Merge pull request #926 from lnash94/url_form_encode
Browse files Browse the repository at this point in the history
[BallerinaToOpenAPI]Fix mapping to `application/x-www-form-urlencoded` when it has response and request body in resources
  • Loading branch information
lnash94 authored Mar 29, 2022
2 parents 0b1ce13 + 84e9b8b commit 5603f6d
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 46 deletions.
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);
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;
}
}

0 comments on commit 5603f6d

Please sign in to comment.