Skip to content
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
8 changes: 4 additions & 4 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "mime"
version = "2.12.0"
version = "2.12.1"
authors = ["Ballerina"]
keywords = ["mime", "multipart", "entity"]
repository = "https://github.com/ballerina-platform/module-ballerina-mime"
Expand All @@ -15,11 +15,11 @@ graalvmCompatible = true
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "mime-native"
version = "2.12.0"
path = "../native/build/libs/mime-native-2.12.0.jar"
version = "2.12.1"
path = "../native/build/libs/mime-native-2.12.1-SNAPSHOT.jar"

[[platform.java21.dependency]]
path = "../test-utils/build/libs/mime-test-utils-2.12.0.jar"
path = "../test-utils/build/libs/mime-test-utils-2.12.1-SNAPSHOT.jar"
scope = "testOnly"

[[platform.java21.dependency]]
Expand Down
4 changes: 2 additions & 2 deletions ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ modules = [
[[package]]
org = "ballerina"
name = "mime"
version = "2.12.0"
version = "2.12.1"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
Expand All @@ -136,7 +136,7 @@ modules = [
[[package]]
org = "ballerina"
name = "observe"
version = "1.5.0"
version = "1.5.1"
dependencies = [
{org = "ballerina", name = "jballerina.java"}
]
Expand Down
65 changes: 65 additions & 0 deletions ballerina/tests/mime_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,71 @@ isolated function getPartsFromInvalidChannel() {
}
}

@test:Config {}
function testEmptyJson1() {
Entity entity = new;
byte[][] emptyPayload = [];
entity.setByteStream(emptyPayload.toStream());
json|error content = entity.getJson();
if content is json {
test:assertFail("Expected an error but got JSON content: " + content.toString());
} else {
test:assertTrue(content is ParserError);
error? cause = content.cause();
if cause is () {
test:assertFail("Expected a cause for the ParserError but got none.");
}
test:assertTrue(cause is NoContentError);
test:assertEquals(cause.message(), "empty JSON document");
}
}

@test:Config {}
function testEmptyJson2() {
Entity entity = new;
entity.setText("");
json|error content = entity.getJson();
if content is json {
test:assertFail("Expected an error but got JSON content: " + content.toString());
} else {
test:assertTrue(content is ParserError);
error? cause = content.cause();
if cause is () {
test:assertFail("Expected a cause for the ParserError but got none.");
}
test:assertTrue(cause is NoContentError);
test:assertEquals(cause.message(), "empty JSON document");
}
}

@test:Config {}
function testEmptyXml1() {
Entity entity = new;
byte[][] emptyPayload = [];
entity.setByteStream(emptyPayload.toStream());
xml|error content = entity.getXml();
if content is xml {
test:assertFail("Expected an error but got XML content: " + content.toString());
} else {
test:assertTrue(content is ParserError);
error? cause = content.cause();
if cause is () {
test:assertFail("Expected a cause for the ParserError but got none.");
}
test:assertTrue(cause is NoContentError);
test:assertTrue(cause.message().includes("Empty xml payload"));
}
}

@test:Config {}
function testEmptyXml2() {
Entity entity = new;
entity.setText("");
xml|error content = entity.getXml();
// XML parser in Ballerina returns empty XML for empty string input
test:assertEquals(content, xml ``);
}

isolated function assertByteArray(byte[]|error returnResult, string expectValue) {
if returnResult is byte[] {
var value = strings:fromBytes(returnResult);
Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ This file contains all the notable changes done to the Ballerina MIME package th
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- [Add no content error for runtime parser no content exception](https://github.com/ballerina-platform/ballerina-library/issues/8475)

## [2.11.0] - 2025-02-11
- This version maintains the latest dependency versions.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@

import io.ballerina.runtime.api.creators.ValueCreator;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.utils.JsonUtils;
import io.ballerina.runtime.api.utils.StringUtils;
import io.ballerina.runtime.api.utils.TypeUtils;
import io.ballerina.runtime.api.utils.XmlUtils;
import io.ballerina.runtime.api.values.BArray;
import io.ballerina.runtime.api.values.BError;
import io.ballerina.runtime.api.values.BObject;
Expand All @@ -43,6 +41,8 @@
import static io.ballerina.stdlib.mime.util.MimeConstants.ENTITY_BYTE_CHANNEL;
import static io.ballerina.stdlib.mime.util.MimeConstants.PARSER_ERROR;
import static io.ballerina.stdlib.mime.util.MimeUtil.isNotNullAndEmpty;
import static io.ballerina.stdlib.mime.util.MimeUtil.parseAsJson;
import static io.ballerina.stdlib.mime.util.MimeUtil.parseAsXml;

/**
* Utilities related to MIME entity body that can be built as a data source.
Expand Down Expand Up @@ -106,7 +106,7 @@ protected static Object getAlreadyBuiltJson(Object dataSource) {
} else {
// Else, build the JSON from the string representation of the payload.
String payload = MimeUtil.getMessageAsString(dataSource);
result = (BRefValue) JsonUtils.parse(payload);
result = (BRefValue) parseAsJson(payload);
}
return result;
}
Expand Down Expand Up @@ -155,7 +155,7 @@ protected static Object getAlreadyBuiltXml(Object dataSource) {
}
// Build the XML from string representation of the payload.
String payload = MimeUtil.getMessageAsString(dataSource);
return XmlUtils.parse(payload);
return parseAsXml(payload);
}

protected static void updateDataSource(BObject entityObj, Object result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@
import io.ballerina.runtime.api.types.ArrayType;
import io.ballerina.runtime.api.types.ObjectType;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.utils.JsonUtils;
import io.ballerina.runtime.api.utils.StringUtils;
import io.ballerina.runtime.api.utils.TypeUtils;
import io.ballerina.runtime.api.utils.XmlUtils;
import io.ballerina.runtime.api.values.BArray;
import io.ballerina.runtime.api.values.BError;
import io.ballerina.runtime.api.values.BMap;
Expand Down Expand Up @@ -60,6 +58,10 @@
import static io.ballerina.stdlib.mime.util.MimeConstants.MESSAGE_DATA_SOURCE;
import static io.ballerina.stdlib.mime.util.MimeConstants.MULTIPART_AS_PRIMARY_TYPE;
import static io.ballerina.stdlib.mime.util.MimeConstants.TEXT_EVENT_STREAM;
import static io.ballerina.stdlib.mime.util.MimeUtil.EMPTY_JSON_DOCUMENT;
import static io.ballerina.stdlib.mime.util.MimeUtil.EMPTY_XML_PAYLOAD;
import static io.ballerina.stdlib.mime.util.MimeUtil.parseAsJson;
import static io.ballerina.stdlib.mime.util.MimeUtil.parseAsXml;

/**
* Entity body related operations are included here.
Expand Down Expand Up @@ -170,7 +172,7 @@ public static BArray constructBlobDataSource(InputStream inputStream) {
public static Object constructJsonDataSource(BObject entityObj) {
Channel byteChannel = getByteChannel(entityObj);
if (byteChannel == null) {
throw MimeUtil.createError(MimeConstants.NO_CONTENT_ERROR, "empty JSON document");
throw MimeUtil.createError(MimeConstants.NO_CONTENT_ERROR, EMPTY_JSON_DOCUMENT);
}
try {
return constructJsonDataSource(entityObj, byteChannel.getInputStream());
Expand All @@ -194,12 +196,12 @@ public static Object constructJsonDataSource(BObject entity, InputStream inputSt
if (MimeUtil.isNotNullAndEmpty(contentTypeValue)) {
String charsetValue = MimeUtil.getContentTypeParamValue(contentTypeValue, CHARSET);
if (MimeUtil.isNotNullAndEmpty(charsetValue)) {
jsonData = JsonUtils.parse(inputStream, charsetValue);
jsonData = parseAsJson(inputStream, charsetValue);
} else {
jsonData = JsonUtils.parse(inputStream);
jsonData = parseAsJson(inputStream);
}
} else {
jsonData = JsonUtils.parse(inputStream);
jsonData = parseAsJson(inputStream);
}
return jsonData;
}
Expand All @@ -213,7 +215,7 @@ public static Object constructJsonDataSource(BObject entity, InputStream inputSt
public static BXml constructXmlDataSource(BObject entityObj) {
Channel byteChannel = getByteChannel(entityObj);
if (byteChannel == null) {
throw MimeUtil.createError(MimeConstants.NO_CONTENT_ERROR, "Empty xml payload");
throw MimeUtil.createError(MimeConstants.NO_CONTENT_ERROR, EMPTY_XML_PAYLOAD);
}
try {
return constructXmlDataSource(entityObj, byteChannel.getInputStream());
Expand All @@ -237,12 +239,12 @@ public static BXml constructXmlDataSource(BObject entityObj, InputStream inputSt
if (MimeUtil.isNotNullAndEmpty(contentTypeValue)) {
String charsetValue = MimeUtil.getContentTypeParamValue(contentTypeValue, CHARSET);
if (MimeUtil.isNotNullAndEmpty(charsetValue)) {
xmlContent = XmlUtils.parse(inputStream, charsetValue);
xmlContent = parseAsXml(inputStream, charsetValue);
} else {
xmlContent = XmlUtils.parse(inputStream);
xmlContent = parseAsXml(inputStream);
}
} else {
xmlContent = XmlUtils.parse(inputStream);
xmlContent = parseAsXml(inputStream);
}
return xmlContent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@
import io.ballerina.runtime.api.types.PredefinedTypes;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.types.TypeTags;
import io.ballerina.runtime.api.utils.JsonUtils;
import io.ballerina.runtime.api.utils.StringUtils;
import io.ballerina.runtime.api.utils.TypeUtils;
import io.ballerina.runtime.api.utils.XmlUtils;
import io.ballerina.runtime.api.values.BArray;
import io.ballerina.runtime.api.values.BError;
import io.ballerina.runtime.api.values.BMap;
import io.ballerina.runtime.api.values.BObject;
import io.ballerina.runtime.api.values.BStreamingJson;
import io.ballerina.runtime.api.values.BString;
import io.ballerina.runtime.api.values.BXml;
import io.ballerina.stdlib.mime.nativeimpl.ModuleUtils;
import jakarta.activation.MimeType;
import jakarta.activation.MimeTypeParameterList;
Expand All @@ -44,6 +47,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Random;
Expand Down Expand Up @@ -86,6 +90,10 @@
public class MimeUtil {
private static final Logger LOG = LoggerFactory.getLogger(MimeUtil.class);

public static final String EMPTY_JSON_DOCUMENT = "empty JSON document";
public static final String EMPTY_XML_PAYLOAD = "Empty xml payload";
public static final String UNEXPECTED_EOF_IN_PROLOG = "Unexpected EOF in prolog";

/**
* Given a ballerina entity, get the content-type as a base type.
*
Expand Down Expand Up @@ -518,6 +526,70 @@ private static boolean parseAsJson(BObject entity) {
return parseAsJson != null && (boolean) entity.getNativeData(MimeConstants.PARSE_AS_JSON);
}

public static Object parseAsJson(InputStream inputStream) {
return parseAsJson(inputStream, Charset.defaultCharset().name());
}

public static Object parseAsJson(InputStream inputStream, String charset) {
try {
return JsonUtils.parse(inputStream, charset);
} catch (BError parserError) {
handleJsonParseError(parserError);
throw parserError;
}
}

public static Object parseAsJson(String jsonString) {
try {
return JsonUtils.parse(jsonString);
} catch (BError parserError) {
handleJsonParseError(parserError);
throw parserError;
}
}

public static BXml parseAsXml(InputStream inputStream) {
return parseAsXml(inputStream, Charset.defaultCharset().name());
}

public static BXml parseAsXml(InputStream inputStream, String charsetValue) {
try {
return XmlUtils.parse(inputStream, charsetValue);
} catch (BError parserError) {
handleXmlParseError(parserError);
throw parserError;
}
}

public static BXml parseAsXml(String xmlString) {
try {
return XmlUtils.parse(xmlString);
} catch (BError parserError) {
handleXmlParseError(parserError);
throw parserError;
}
}

private static void handleJsonParseError(BError parserError) {
String errorMsg = parserError.getMessage();
// This EMPTY_JSON_DOCUMENT error occurs when the JSON payload is empty.
// Currently, the lang lib JSON parser does not have a specific error for empty JSON payloads.
// Therefore, we are checking for this specific error message to identify empty JSON payloads.
if (errorMsg != null && errorMsg.startsWith(EMPTY_JSON_DOCUMENT)) {
throw MimeUtil.createError(MimeConstants.NO_CONTENT_ERROR, EMPTY_JSON_DOCUMENT, parserError);
}
}

private static void handleXmlParseError(BError parserError) {
String errorMsg = parserError.getMessage();
// This UNEXPECTED_EOF_IN_PROLOG error occurs when the XML payload is empty.
// Currently, the lang lib XML parser does not have a specific error for empty XML payloads.
// Therefore, we are checking for this specific error message to identify empty XML payloads.
if (errorMsg != null && errorMsg.contains(UNEXPECTED_EOF_IN_PROLOG)) {
throw MimeUtil.createError(MimeConstants.NO_CONTENT_ERROR, EMPTY_XML_PAYLOAD, parserError);
}
}

/**
* Validate the given Content-Type.
*
Expand Down