Add FlJsonMessageCodec (#18221)
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 8ce5b14..daf9e19 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -1194,6 +1194,8 @@
FILE: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_engine.cc
FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h
+FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc
+FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_message_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_renderer.cc
@@ -1213,6 +1215,7 @@
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_engine.h
+FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_message_codec.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h
diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn
index 5b3f865..aa2c35a 100644
--- a/shell/platform/linux/BUILD.gn
+++ b/shell/platform/linux/BUILD.gn
@@ -49,6 +49,7 @@
"public/flutter_linux/fl_binary_messenger.h",
"public/flutter_linux/fl_dart_project.h",
"public/flutter_linux/fl_engine.h",
+ "public/flutter_linux/fl_json_message_codec.h",
"public/flutter_linux/fl_message_codec.h",
"public/flutter_linux/fl_standard_message_codec.h",
"public/flutter_linux/fl_string_codec.h",
@@ -70,6 +71,7 @@
"fl_binary_messenger.cc",
"fl_dart_project.cc",
"fl_engine.cc",
+ "fl_json_message_codec.cc",
"fl_message_codec.cc",
"fl_renderer.cc",
"fl_renderer_x11.cc",
@@ -90,6 +92,7 @@
deps = [
"//flutter/shell/platform/embedder:embedder_with_symbol_prefix",
+ "//third_party/rapidjson",
]
}
@@ -103,6 +106,7 @@
sources = [
"fl_binary_codec_test.cc",
"fl_dart_project_test.cc",
+ "fl_json_message_codec_test.cc",
"fl_message_codec_test.cc",
"fl_standard_message_codec_test.cc",
"fl_string_codec_test.cc",
diff --git a/shell/platform/linux/fl_json_message_codec.cc b/shell/platform/linux/fl_json_message_codec.cc
new file mode 100644
index 0000000..ded2c20
--- /dev/null
+++ b/shell/platform/linux/fl_json_message_codec.cc
@@ -0,0 +1,320 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
+
+#include "rapidjson/reader.h"
+#include "rapidjson/writer.h"
+
+#include <gmodule.h>
+
+G_DEFINE_QUARK(fl_json_message_codec_error_quark, fl_json_message_codec_error)
+
+struct _FlJsonMessageCodec {
+ FlMessageCodec parent_instance;
+};
+
+G_DEFINE_TYPE(FlJsonMessageCodec,
+ fl_json_message_codec,
+ fl_message_codec_get_type())
+
+// Recursively writes #FlValue objects using rapidjson
+static gboolean write_value(rapidjson::Writer<rapidjson::StringBuffer>& writer,
+ FlValue* value,
+ GError** error) {
+ if (value == nullptr) {
+ writer.Null();
+ return TRUE;
+ }
+
+ switch (fl_value_get_type(value)) {
+ case FL_VALUE_TYPE_NULL:
+ writer.Null();
+ break;
+ case FL_VALUE_TYPE_BOOL:
+ writer.Bool(fl_value_get_bool(value));
+ break;
+ case FL_VALUE_TYPE_INT:
+ writer.Int64(fl_value_get_int(value));
+ break;
+ case FL_VALUE_TYPE_FLOAT:
+ writer.Double(fl_value_get_float(value));
+ break;
+ case FL_VALUE_TYPE_STRING:
+ writer.String(fl_value_get_string(value));
+ break;
+ case FL_VALUE_TYPE_UINT8_LIST: {
+ writer.StartArray();
+ const uint8_t* data = fl_value_get_uint8_list(value);
+ for (size_t i = 0; i < fl_value_get_length(value); i++)
+ writer.Int(data[i]);
+ writer.EndArray();
+ break;
+ }
+ case FL_VALUE_TYPE_INT32_LIST: {
+ writer.StartArray();
+ const int32_t* data = fl_value_get_int32_list(value);
+ for (size_t i = 0; i < fl_value_get_length(value); i++)
+ writer.Int(data[i]);
+ writer.EndArray();
+ break;
+ }
+ case FL_VALUE_TYPE_INT64_LIST: {
+ writer.StartArray();
+ const int64_t* data = fl_value_get_int64_list(value);
+ for (size_t i = 0; i < fl_value_get_length(value); i++)
+ writer.Int64(data[i]);
+ writer.EndArray();
+ break;
+ }
+ case FL_VALUE_TYPE_FLOAT_LIST: {
+ writer.StartArray();
+ const double* data = fl_value_get_float_list(value);
+ for (size_t i = 0; i < fl_value_get_length(value); i++)
+ writer.Double(data[i]);
+ writer.EndArray();
+ break;
+ }
+ case FL_VALUE_TYPE_LIST: {
+ writer.StartArray();
+ for (size_t i = 0; i < fl_value_get_length(value); i++)
+ if (!write_value(writer, fl_value_get_list_value(value, i), error))
+ return FALSE;
+ writer.EndArray();
+ break;
+ }
+ case FL_VALUE_TYPE_MAP: {
+ writer.StartObject();
+ for (size_t i = 0; i < fl_value_get_length(value); i++) {
+ FlValue* key = fl_value_get_map_key(value, i);
+ if (fl_value_get_type(key) != FL_VALUE_TYPE_STRING) {
+ g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE,
+ "Invalid object key type");
+ return FALSE;
+ }
+ writer.Key(fl_value_get_string(key));
+ if (!write_value(writer, fl_value_get_map_value(value, i), error))
+ return FALSE;
+ }
+ writer.EndObject();
+ break;
+ }
+ default:
+ g_set_error(error, FL_MESSAGE_CODEC_ERROR,
+ FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE,
+ "Unexpected FlValue type %d", fl_value_get_type(value));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+// Handler to parse JSON using rapidjson in SAX mode
+struct FlValueHandler {
+ GPtrArray* stack;
+ FlValue* key;
+ GError* error;
+
+ FlValueHandler() {
+ stack = g_ptr_array_new_with_free_func(
+ reinterpret_cast<GDestroyNotify>(fl_value_unref));
+ key = nullptr;
+ error = nullptr;
+ }
+
+ ~FlValueHandler() {
+ g_ptr_array_unref(stack);
+ if (key != nullptr)
+ fl_value_unref(key);
+ if (error != nullptr)
+ g_error_free(error);
+ }
+
+ // Gets the current head of the stack
+ FlValue* get_head() {
+ if (stack->len == 0)
+ return nullptr;
+ return static_cast<FlValue*>(g_ptr_array_index(stack, stack->len - 1));
+ }
+
+ // Pushes a value onto the stack
+ void push(FlValue* value) { g_ptr_array_add(stack, fl_value_ref(value)); }
+
+ // Pops the stack
+ void pop() { g_ptr_array_remove_index(stack, stack->len - 1); }
+
+ // Adds a new value to the stack
+ bool add(FlValue* value) {
+ g_autoptr(FlValue) owned_value = value;
+ FlValue* head = get_head();
+ if (head == nullptr)
+ push(owned_value);
+ else if (fl_value_get_type(head) == FL_VALUE_TYPE_LIST)
+ fl_value_append(head, owned_value);
+ else if (fl_value_get_type(head) == FL_VALUE_TYPE_MAP) {
+ fl_value_set_take(head, key, fl_value_ref(owned_value));
+ key = nullptr;
+ } else {
+ g_set_error(&error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
+ "Can't add value to non container");
+ return false;
+ }
+
+ if (fl_value_get_type(owned_value) == FL_VALUE_TYPE_LIST ||
+ fl_value_get_type(owned_value) == FL_VALUE_TYPE_MAP)
+ push(value);
+
+ return true;
+ }
+
+ // The following implements the rapidjson SAX API
+
+ bool Null() { return add(fl_value_new_null()); }
+
+ bool Bool(bool b) { return add(fl_value_new_bool(b)); }
+
+ bool Int(int i) { return add(fl_value_new_int(i)); }
+
+ bool Uint(unsigned i) { return add(fl_value_new_int(i)); }
+
+ bool Int64(int64_t i) { return add(fl_value_new_int(i)); }
+
+ bool Uint64(uint64_t i) {
+ // For some reason (bug in rapidjson?) this is not returned in Int64
+ if (i == G_MAXINT64)
+ return add(fl_value_new_int(i));
+ else
+ return add(fl_value_new_float(i));
+ }
+
+ bool Double(double d) { return add(fl_value_new_float(d)); }
+
+ bool RawNumber(const char* str, rapidjson::SizeType length, bool copy) {
+ g_set_error(&error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
+ "RawNumber not supported");
+ return false;
+ }
+
+ bool String(const char* str, rapidjson::SizeType length, bool copy) {
+ FlValue* v = fl_value_new_string_sized(str, length);
+ return add(v);
+ }
+
+ bool StartObject() { return add(fl_value_new_map()); }
+
+ bool Key(const char* str, rapidjson::SizeType length, bool copy) {
+ if (key != nullptr)
+ fl_value_unref(key);
+ key = fl_value_new_string_sized(str, length);
+ return true;
+ }
+
+ bool EndObject(rapidjson::SizeType memberCount) {
+ pop();
+ return true;
+ }
+
+ bool StartArray() { return add(fl_value_new_list()); }
+
+ bool EndArray(rapidjson::SizeType elementCount) {
+ pop();
+ return true;
+ }
+};
+
+// Implements FlMessageCodec:encode_message
+static GBytes* fl_json_message_codec_encode_message(FlMessageCodec* codec,
+ FlValue* message,
+ GError** error) {
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+
+ if (!write_value(writer, message, error))
+ return nullptr;
+
+ const gchar* text = buffer.GetString();
+ return g_bytes_new(text, strlen(text));
+}
+
+// Implements FlMessageCodec:decode_message
+static FlValue* fl_json_message_codec_decode_message(FlMessageCodec* codec,
+ GBytes* message,
+ GError** error) {
+ gsize data_length;
+ const gchar* data =
+ static_cast<const char*>(g_bytes_get_data(message, &data_length));
+ if (!g_utf8_validate(data, data_length, nullptr)) {
+ g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8,
+ "Message is not valid UTF8");
+ return nullptr;
+ }
+
+ FlValueHandler handler;
+ rapidjson::Reader reader;
+ rapidjson::MemoryStream ss(data, data_length);
+ if (!reader.Parse(ss, handler)) {
+ if (handler.error != nullptr) {
+ g_propagate_error(error, handler.error);
+ handler.error = nullptr;
+ } else
+ g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON,
+ "Message is not valid JSON");
+ return nullptr;
+ }
+
+ FlValue* value = handler.get_head();
+ if (value == nullptr) {
+ g_set_error(error, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON,
+ "Message is not valid JSON");
+ return nullptr;
+ }
+
+ return fl_value_ref(value);
+}
+
+static void fl_json_message_codec_class_init(FlJsonMessageCodecClass* klass) {
+ FL_MESSAGE_CODEC_CLASS(klass)->encode_message =
+ fl_json_message_codec_encode_message;
+ FL_MESSAGE_CODEC_CLASS(klass)->decode_message =
+ fl_json_message_codec_decode_message;
+}
+
+static void fl_json_message_codec_init(FlJsonMessageCodec* self) {}
+
+G_MODULE_EXPORT FlJsonMessageCodec* fl_json_message_codec_new() {
+ return static_cast<FlJsonMessageCodec*>(
+ g_object_new(fl_json_message_codec_get_type(), nullptr));
+}
+
+G_MODULE_EXPORT gchar* fl_json_message_codec_encode(FlJsonMessageCodec* codec,
+ FlValue* value,
+ GError** error) {
+ g_return_val_if_fail(FL_IS_JSON_CODEC(codec), nullptr);
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+
+ if (!write_value(writer, value, error))
+ return nullptr;
+
+ return g_strdup(buffer.GetString());
+}
+
+G_MODULE_EXPORT FlValue* fl_json_message_codec_decode(FlJsonMessageCodec* codec,
+ const gchar* text,
+ GError** error) {
+ g_return_val_if_fail(FL_IS_JSON_CODEC(codec), nullptr);
+
+ g_autoptr(GBytes) data = g_bytes_new_static(text, strlen(text));
+ g_autoptr(FlValue) value = fl_json_message_codec_decode_message(
+ FL_MESSAGE_CODEC(codec), data, error);
+ if (value == nullptr)
+ return nullptr;
+
+ return fl_value_ref(value);
+}
diff --git a/shell/platform/linux/fl_json_message_codec_test.cc b/shell/platform/linux/fl_json_message_codec_test.cc
new file mode 100644
index 0000000..43a19d3
--- /dev/null
+++ b/shell/platform/linux/fl_json_message_codec_test.cc
@@ -0,0 +1,783 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
+#include "gtest/gtest.h"
+
+#include <math.h>
+
+// Encodes a message using FlJsonMessageCodec to a UTF-8 string.
+static gchar* encode_message(FlValue* value) {
+ g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+ g_autoptr(GError) error = nullptr;
+ g_autofree gchar* result = fl_json_message_codec_encode(codec, value, &error);
+ EXPECT_EQ(error, nullptr);
+ return static_cast<gchar*>(g_steal_pointer(&result));
+}
+
+// Encodes a message using FlJsonMessageCodec to a UTF-8 string. Expect the
+// given error.
+static void encode_error_message(FlValue* value, GQuark domain, gint code) {
+ g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+ g_autoptr(GError) error = nullptr;
+ g_autofree gchar* result = fl_json_message_codec_encode(codec, value, &error);
+ EXPECT_TRUE(g_error_matches(error, domain, code));
+ EXPECT_EQ(result, nullptr);
+}
+
+// Decodes a message using FlJsonMessageCodec from UTF-8 string.
+static FlValue* decode_message(const char* text) {
+ g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+ g_autoptr(GError) error = nullptr;
+ g_autoptr(FlValue) value = fl_json_message_codec_decode(codec, text, &error);
+ EXPECT_EQ(error, nullptr);
+ EXPECT_NE(value, nullptr);
+ return fl_value_ref(value);
+}
+
+// Decodes a message using FlJsonMessageCodec from UTF-8 string. Expect the
+// given error.
+static void decode_error_message(const char* text, GQuark domain, gint code) {
+ g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+ g_autoptr(GError) error = nullptr;
+ g_autoptr(FlValue) value = fl_json_message_codec_decode(codec, text, &error);
+ EXPECT_TRUE(g_error_matches(error, domain, code));
+ EXPECT_EQ(value, nullptr);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeNullptr) {
+ g_autofree gchar* text = encode_message(nullptr);
+ EXPECT_STREQ(text, "null");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeNull) {
+ g_autoptr(FlValue) value = fl_value_new_null();
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "null");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeNull) {
+ g_autoptr(FlValue) value = decode_message("null");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_NULL);
+}
+
+static gchar* encode_bool(gboolean value) {
+ g_autoptr(FlValue) v = fl_value_new_bool(value);
+ return encode_message(v);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeBoolFalse) {
+ g_autofree gchar* text = encode_bool(FALSE);
+ EXPECT_STREQ(text, "false");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeBoolTrue) {
+ g_autofree gchar* text = encode_bool(TRUE);
+ EXPECT_STREQ(text, "true");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeBoolFalse) {
+ g_autoptr(FlValue) value = decode_message("false");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_BOOL);
+ EXPECT_FALSE(fl_value_get_bool(value));
+}
+
+TEST(FlJsonMessageCodecTest, DecodeBoolTrue) {
+ g_autoptr(FlValue) value = decode_message("true");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_BOOL);
+ EXPECT_TRUE(fl_value_get_bool(value));
+}
+
+static gchar* encode_int(int64_t value) {
+ g_autoptr(FlValue) v = fl_value_new_int(value);
+ return encode_message(v);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeIntZero) {
+ g_autofree gchar* text = encode_int(0);
+ EXPECT_STREQ(text, "0");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeIntOne) {
+ g_autofree gchar* text = encode_int(1);
+ EXPECT_STREQ(text, "1");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeInt12345) {
+ g_autofree gchar* text = encode_int(12345);
+ EXPECT_STREQ(text, "12345");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeIntMin) {
+ g_autofree gchar* text = encode_int(G_MININT64);
+ EXPECT_STREQ(text, "-9223372036854775808");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeIntMax) {
+ g_autofree gchar* text = encode_int(G_MAXINT64);
+ EXPECT_STREQ(text, "9223372036854775807");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntZero) {
+ g_autoptr(FlValue) value = decode_message("0");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT);
+ EXPECT_EQ(fl_value_get_int(value), 0);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntOne) {
+ g_autoptr(FlValue) value = decode_message("1");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT);
+ EXPECT_EQ(fl_value_get_int(value), 1);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeInt12345) {
+ g_autoptr(FlValue) value = decode_message("12345");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT);
+ EXPECT_EQ(fl_value_get_int(value), 12345);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntMin) {
+ g_autoptr(FlValue) value = decode_message("-9223372036854775808");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT);
+ EXPECT_EQ(fl_value_get_int(value), G_MININT64);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntMax) {
+ g_autoptr(FlValue) value = decode_message("9223372036854775807");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_INT);
+ EXPECT_EQ(fl_value_get_int(value), G_MAXINT64);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeUintMax) {
+ // This is bigger than an signed 64 bit integer, so we expect it to be
+ // represented as a double
+ g_autoptr(FlValue) value = decode_message("18446744073709551615");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), 1.8446744073709551615e+19);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeHugeNumber) {
+ // This is bigger than an unsigned 64 bit integer, so we expect it to be
+ // represented as a double
+ g_autoptr(FlValue) value = decode_message("184467440737095516150");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), 1.84467440737095516150e+20);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntLeadingZero1) {
+ decode_error_message("00", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntLeadingZero2) {
+ decode_error_message("01", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntDoubleNegative) {
+ decode_error_message("--1", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntPositiveSign) {
+ decode_error_message("+1", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeIntHexChar) {
+ decode_error_message("0a", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+static gchar* encode_float(double value) {
+ g_autoptr(FlValue) v = fl_value_new_float(value);
+ return encode_message(v);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatZero) {
+ g_autofree gchar* text = encode_float(0);
+ EXPECT_STREQ(text, "0.0");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatOne) {
+ g_autofree gchar* text = encode_float(1);
+ EXPECT_STREQ(text, "1.0");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatMinusOne) {
+ g_autofree gchar* text = encode_float(-1);
+ EXPECT_STREQ(text, "-1.0");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatHalf) {
+ g_autofree gchar* text = encode_float(0.5);
+ EXPECT_STREQ(text, "0.5");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatPi) {
+ g_autofree gchar* text = encode_float(M_PI);
+ EXPECT_STREQ(text, "3.141592653589793");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatMinusZero) {
+ g_autofree gchar* text = encode_float(-0.0);
+ EXPECT_STREQ(text, "-0.0");
+}
+
+// NOTE(robert-ancell): JSON doesn't support encoding of NAN and INFINITY, but
+// rapidjson doesn't seem to either encode them or treat them as an error.
+
+TEST(FlJsonMessageCodecTest, DecodeFloatZero) {
+ g_autoptr(FlValue) value = decode_message("0.0");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), 0.0);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatOne) {
+ g_autoptr(FlValue) value = decode_message("1.0");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), 1.0);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatMinusOne) {
+ g_autoptr(FlValue) value = decode_message("-1.0");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), -1.0);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatHalf) {
+ g_autoptr(FlValue) value = decode_message("0.5");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), 0.5);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatPi) {
+ g_autoptr(FlValue) value = decode_message("3.1415926535897931");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), M_PI);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatMinusZero) {
+ g_autoptr(FlValue) value = decode_message("-0.0");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_FLOAT);
+ EXPECT_EQ(fl_value_get_float(value), -0.0);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatMissingFraction) {
+ decode_error_message("0.", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeFloatInvalidFraction) {
+ decode_error_message("0.a", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+static gchar* encode_string(const gchar* value) {
+ g_autoptr(FlValue) v = fl_value_new_string(value);
+ return encode_message(v);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEmpty) {
+ g_autofree gchar* text = encode_string("");
+ EXPECT_STREQ(text, "\"\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringHello) {
+ g_autofree gchar* text = encode_string("hello");
+ EXPECT_STREQ(text, "\"hello\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEmptySized) {
+ g_autoptr(FlValue) value = fl_value_new_string_sized(nullptr, 0);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "\"\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringHelloSized) {
+ g_autoptr(FlValue) value = fl_value_new_string_sized("Hello World", 5);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "\"Hello\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeQuote) {
+ g_autofree gchar* text = encode_string("\"");
+ EXPECT_STREQ(text, "\"\\\"\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeBackslash) {
+ g_autofree gchar* text = encode_string("\\");
+ EXPECT_STREQ(text, "\"\\\\\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeBackspace) {
+ g_autofree gchar* text = encode_string("\b");
+ EXPECT_STREQ(text, "\"\\b\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeFormFeed) {
+ g_autofree gchar* text = encode_string("\f");
+ EXPECT_STREQ(text, "\"\\f\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeNewline) {
+ g_autofree gchar* text = encode_string("\n");
+ EXPECT_STREQ(text, "\"\\n\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeCarriageReturn) {
+ g_autofree gchar* text = encode_string("\r");
+ EXPECT_STREQ(text, "\"\\r\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeTab) {
+ g_autofree gchar* text = encode_string("\t");
+ EXPECT_STREQ(text, "\"\\t\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEscapeUnicode) {
+ g_autofree gchar* text = encode_string("\u0001");
+ EXPECT_STREQ(text, "\"\\u0001\"");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeStringEmoji) {
+ g_autofree gchar* text = encode_string("😀");
+ EXPECT_STREQ(text, "\"😀\"");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEmpty) {
+ g_autoptr(FlValue) value = decode_message("\"\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringHello) {
+ g_autoptr(FlValue) value = decode_message("\"hello\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "hello");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeQuote) {
+ g_autoptr(FlValue) value = decode_message("\"\\\"\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\"");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeBackslash) {
+ g_autoptr(FlValue) value = decode_message("\"\\\\\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\\");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeSlash) {
+ g_autoptr(FlValue) value = decode_message("\"\\/\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "/");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeBackspace) {
+ g_autoptr(FlValue) value = decode_message("\"\\b\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\b");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeFormFeed) {
+ g_autoptr(FlValue) value = decode_message("\"\\f\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\f");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeNewline) {
+ g_autoptr(FlValue) value = decode_message("\"\\n\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\n");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeCarriageReturn) {
+ g_autoptr(FlValue) value = decode_message("\"\\r\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\r");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeTab) {
+ g_autoptr(FlValue) value = decode_message("\"\\t\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\t");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeUnicode) {
+ g_autoptr(FlValue) value = decode_message("\"\\u0001\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "\u0001");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEmoji) {
+ g_autoptr(FlValue) value = decode_message("\"😀\"");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
+ EXPECT_STREQ(fl_value_get_string(value), "😀");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeInvalidUTF8) {
+ decode_error_message("\xff", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringInvalidUTF8) {
+ decode_error_message("\"\xff\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringBinary) {
+ decode_error_message("\"Hello\x01World\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringNewline) {
+ decode_error_message("\"Hello\nWorld\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringCarriageReturn) {
+ decode_error_message("\"Hello\rWorld\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringTab) {
+ decode_error_message("\"Hello\tWorld\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringUnterminatedEmpty) {
+ decode_error_message("\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringExtraQuote) {
+ decode_error_message("\"\"\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapedClosingQuote) {
+ decode_error_message("\"\\\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringUnknownEscape) {
+ decode_error_message("\"\\z\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringInvalidEscapeUnicode) {
+ decode_error_message("\"\\uxxxx\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeUnicodeNoData) {
+ decode_error_message("\"\\u\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeStringEscapeUnicodeShortData) {
+ decode_error_message("\"\\uxx\"", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeUint8ListEmpty) {
+ g_autoptr(FlValue) value = fl_value_new_uint8_list(nullptr, 0);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeUint8List) {
+ uint8_t data[] = {0, 1, 2, 3, 4};
+ g_autoptr(FlValue) value = fl_value_new_uint8_list(data, 5);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[0,1,2,3,4]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeInt32ListEmpty) {
+ g_autoptr(FlValue) value = fl_value_new_int32_list(nullptr, 0);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeInt32List) {
+ int32_t data[] = {0, -1, 2, -3, 4};
+ g_autoptr(FlValue) value = fl_value_new_int32_list(data, 5);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[0,-1,2,-3,4]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeInt64ListEmpty) {
+ g_autoptr(FlValue) value = fl_value_new_int64_list(nullptr, 0);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeInt64List) {
+ int64_t data[] = {0, -1, 2, -3, 4};
+ g_autoptr(FlValue) value = fl_value_new_int64_list(data, 5);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[0,-1,2,-3,4]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatListEmpty) {
+ g_autoptr(FlValue) value = fl_value_new_float_list(nullptr, 0);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeFloatList) {
+ double data[] = {0, -0.5, 0.25, -0.125, 0.0625};
+ g_autoptr(FlValue) value = fl_value_new_float_list(data, 5);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[0.0,-0.5,0.25,-0.125,0.0625]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeListEmpty) {
+ g_autoptr(FlValue) value = fl_value_new_list();
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeListTypes) {
+ g_autoptr(FlValue) value = fl_value_new_list();
+ fl_value_append_take(value, fl_value_new_null());
+ fl_value_append_take(value, fl_value_new_bool(TRUE));
+ fl_value_append_take(value, fl_value_new_int(42));
+ fl_value_append_take(value, fl_value_new_float(-1.5));
+ fl_value_append_take(value, fl_value_new_string("hello"));
+ fl_value_append_take(value, fl_value_new_list());
+ fl_value_append_take(value, fl_value_new_map());
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[null,true,42,-1.5,\"hello\",[],{}]");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeListNested) {
+ g_autoptr(FlValue) even_numbers = fl_value_new_list();
+ g_autoptr(FlValue) odd_numbers = fl_value_new_list();
+ for (int i = 0; i < 10; i++) {
+ if (i % 2 == 0)
+ fl_value_append_take(even_numbers, fl_value_new_int(i));
+ else
+ fl_value_append_take(odd_numbers, fl_value_new_int(i));
+ }
+ g_autoptr(FlValue) value = fl_value_new_list();
+ fl_value_append(value, even_numbers);
+ fl_value_append(value, odd_numbers);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "[[0,2,4,6,8],[1,3,5,7,9]]");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeListEmpty) {
+ g_autoptr(FlValue) value = decode_message("[]");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_LIST);
+ EXPECT_EQ(fl_value_get_length(value), static_cast<size_t>(0));
+}
+
+TEST(FlJsonMessageCodecTest, DecodeListNoComma) {
+ decode_error_message("[0,1,2,3 4]", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeListUnterminatedEmpty) {
+ decode_error_message("[", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeListStartUnterminate) {
+ decode_error_message("]", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeListUnterminated) {
+ decode_error_message("[0,1,2,3,4", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeListDoubleTerminated) {
+ decode_error_message("[0,1,2,3,4]]", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapEmpty) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text, "{}");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapNullKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_null(), fl_value_new_string("null"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapBoolKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_bool(TRUE),
+ fl_value_new_string("bool"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapIntKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_int(42), fl_value_new_string("int"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapFloatKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_float(M_PI),
+ fl_value_new_string("float"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapUint8ListKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_uint8_list(nullptr, 0),
+ fl_value_new_string("uint8_list"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapInt32ListKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_int32_list(nullptr, 0),
+ fl_value_new_string("int32_list"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapInt64ListKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_int64_list(nullptr, 0),
+ fl_value_new_string("int64_list"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapFloatListKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_float_list(nullptr, 0),
+ fl_value_new_string("float_list"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapListKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_list(), fl_value_new_string("list"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapMapKey) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_map(), fl_value_new_string("map"));
+ encode_error_message(value, FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapValueTypes) {
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_take(value, fl_value_new_string("null"), fl_value_new_null());
+ fl_value_set_take(value, fl_value_new_string("bool"),
+ fl_value_new_bool(TRUE));
+ fl_value_set_take(value, fl_value_new_string("int"), fl_value_new_int(42));
+ fl_value_set_take(value, fl_value_new_string("float"),
+ fl_value_new_float(-1.5));
+ fl_value_set_take(value, fl_value_new_string("string"),
+ fl_value_new_string("hello"));
+ fl_value_set_take(value, fl_value_new_string("list"), fl_value_new_list());
+ fl_value_set_take(value, fl_value_new_string("map"), fl_value_new_map());
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text,
+ "{\"null\":null,\"bool\":true,\"int\":42,\"float\":-"
+ "1.5,\"string\":\"hello\",\"list\":[],\"map\":{}}");
+}
+
+TEST(FlJsonMessageCodecTest, EncodeMapNested) {
+ g_autoptr(FlValue) str_to_int = fl_value_new_map();
+ const char* numbers[] = {"zero", "one", "two", "three", nullptr};
+ for (int i = 0; numbers[i] != nullptr; i++) {
+ fl_value_set_take(str_to_int, fl_value_new_string(numbers[i]),
+ fl_value_new_int(i));
+ }
+ g_autoptr(FlValue) value = fl_value_new_map();
+ fl_value_set_string(value, "str-to-int", str_to_int);
+ g_autofree gchar* text = encode_message(value);
+ EXPECT_STREQ(text,
+ "{\"str-to-int\":{\"zero\":0,\"one\":1,\"two\":2,\"three\":3}}");
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapEmpty) {
+ g_autoptr(FlValue) value = decode_message("{}");
+ ASSERT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
+ EXPECT_EQ(fl_value_get_length(value), static_cast<size_t>(0));
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapUnterminatedEmpty) {
+ decode_error_message("{", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapStartUnterminate) {
+ decode_error_message("}", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapNoComma) {
+ decode_error_message("{\"zero\":0 \"one\":1}", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapNoColon) {
+ decode_error_message("{\"zero\" 0,\"one\":1}", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapUnterminated) {
+ decode_error_message("{\"zero\":0,\"one\":1", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeMapDoubleTerminated) {
+ decode_error_message("{\"zero\":0,\"one\":1}}", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, DecodeUnknownWord) {
+ decode_error_message("foo", FL_JSON_MESSAGE_CODEC_ERROR,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON);
+}
+
+TEST(FlJsonMessageCodecTest, EncodeDecode) {
+ g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
+
+ g_autoptr(FlValue) input = fl_value_new_list();
+ fl_value_append_take(input, fl_value_new_null());
+ fl_value_append_take(input, fl_value_new_bool(TRUE));
+ fl_value_append_take(input, fl_value_new_int(42));
+ fl_value_append_take(input, fl_value_new_float(M_PI));
+ fl_value_append_take(input, fl_value_new_string("hello"));
+ fl_value_append_take(input, fl_value_new_list());
+ fl_value_append_take(input, fl_value_new_map());
+
+ g_autoptr(GError) error = nullptr;
+ g_autofree gchar* message =
+ fl_json_message_codec_encode(codec, input, &error);
+ ASSERT_NE(message, nullptr);
+ EXPECT_EQ(error, nullptr);
+
+ g_autoptr(FlValue) output =
+ fl_json_message_codec_decode(codec, message, &error);
+ EXPECT_EQ(error, nullptr);
+ EXPECT_NE(output, nullptr);
+
+ EXPECT_TRUE(fl_value_equal(input, output));
+}
diff --git a/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h b/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h
new file mode 100644
index 0000000..5796ffe
--- /dev/null
+++ b/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h
@@ -0,0 +1,93 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_
+
+#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION)
+#error "Only <flutter_linux/flutter_linux.h> can be included directly."
+#endif
+
+#include "fl_message_codec.h"
+
+G_BEGIN_DECLS
+
+/**
+ * FlJsonMessageCodecError:
+ * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8: Message is not valid UTF-8.
+ * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON: Message is not valid JSON.
+ * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE: Invalid object key type
+ *
+ * Errors for #FlJsonMessageCodec objects to set on failures.
+ */
+#define FL_JSON_MESSAGE_CODEC_ERROR fl_json_message_codec_error_quark()
+
+typedef enum {
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON,
+ FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE,
+} FlJsonMessageCodecError;
+
+GQuark fl_json_message_codec_error_quark(void) G_GNUC_CONST;
+
+G_DECLARE_FINAL_TYPE(FlJsonMessageCodec,
+ fl_json_message_codec,
+ FL,
+ JSON_CODEC,
+ FlMessageCodec)
+
+/**
+ * FlJsonMessageCodec:
+ *
+ * #FlJsonMessageCodec is an #FlMessageCodec that implements the Flutter
+ * standard message encoding. This encodes and decodes #FlValue of type
+ * #FL_VALUE_TYPE_NULL, #FL_VALUE_TYPE_BOOL, #FL_VALUE_TYPE_INT,
+ * #FL_VALUE_TYPE_FLOAT, #FL_VALUE_TYPE_STRING, #FL_VALUE_TYPE_UINT8_LIST,
+ * #FL_VALUE_TYPE_INT32_LIST, #FL_VALUE_TYPE_INT64_LIST,
+ * #FL_VALUE_TYPE_FLOAT_LIST, #FL_VALUE_TYPE_LIST, and #FL_VALUE_TYPE_MAP
+ *
+ * #FlJsonMessageCodec matches the JSONMessageCodec class in the Flutter
+ * services library.
+ */
+
+/**
+ * fl_json_message_codec_new:
+ *
+ * Creates a #FlJsonMessageCodec.
+ *
+ * Returns: a new #FlJsonMessageCodec
+ */
+FlJsonMessageCodec* fl_json_message_codec_new();
+
+/**
+ * fl_json_message_codec_encode:
+ * @codec: a #FlJsonMessageCodec
+ * @value: value to encode
+ * @error: (allow-none): #GError location to store the error occurring, or %NULL
+ *
+ * Encode a value to a JSON string.
+ *
+ * Returns: a JSON representation of this value or %NULL on error.
+ */
+gchar* fl_json_message_codec_encode(FlJsonMessageCodec* codec,
+ FlValue* value,
+ GError** error);
+
+/**
+ * fl_json_message_codec_decode:
+ * @codec: a #FlJsonMessageCodec
+ * @text: UTF-8 text in JSON format
+ * @error: (allow-none): #GError location to store the error occurring, or %NULL
+ *
+ * Decode a value from a JSON string.
+ *
+ * Returns: a #FlValue or %NULL on error
+ */
+FlValue* fl_json_message_codec_decode(FlJsonMessageCodec* codec,
+ const gchar* text,
+ GError** error);
+
+G_END_DECLS
+
+#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_
diff --git a/shell/platform/linux/public/flutter_linux/flutter_linux.h b/shell/platform/linux/public/flutter_linux/flutter_linux.h
index cb869b0..b26e9ab 100644
--- a/shell/platform/linux/public/flutter_linux/flutter_linux.h
+++ b/shell/platform/linux/public/flutter_linux/flutter_linux.h
@@ -12,6 +12,7 @@
#include <flutter_linux/fl_binary_messenger.h>
#include <flutter_linux/fl_dart_project.h>
#include <flutter_linux/fl_engine.h>
+#include <flutter_linux/fl_json_message_codec.h>
#include <flutter_linux/fl_message_codec.h>
#include <flutter_linux/fl_standard_message_codec.h>
#include <flutter_linux/fl_string_codec.h>