blob: 7a632ebab8317fe8a085fca8ae42cc7d65a319ac [file] [log] [blame]
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "json.h"
#include <capnp/test-util.h>
#include <capnp/compat/json.capnp.h>
#include <capnp/compat/json-test.capnp.h>
#include <kj/debug.h>
#include <kj/string.h>
#include <kj/test.h>
namespace capnp {
namespace _ { // private
namespace {
KJ_TEST("basic json encoding") {
JsonCodec json;
KJ_EXPECT(json.encode(VOID) == "null");
KJ_EXPECT(json.encode(true) == "true");
KJ_EXPECT(json.encode(false) == "false");
KJ_EXPECT(json.encode(123) == "123");
KJ_EXPECT(json.encode(-5.5) == "-5.5");
KJ_EXPECT(json.encode(Text::Reader("foo")) == "\"foo\"");
KJ_EXPECT(json.encode(Text::Reader("ab\"cd\\ef\x03")) == "\"ab\\\"cd\\\\ef\\u0003\"");
KJ_EXPECT(json.encode(test::TestEnum::CORGE) == "\"corge\"");
byte bytes[] = {12, 34, 56};
KJ_EXPECT(json.encode(Data::Reader(bytes, 3)) == "[12,34,56]");
json.setPrettyPrint(true);
KJ_EXPECT(json.encode(Data::Reader(bytes, 3)) == "[12, 34, 56]");
}
const char ALL_TYPES_JSON[] =
"{ \"voidField\": null,\n"
" \"boolField\": true,\n"
" \"int8Field\": -123,\n"
" \"int16Field\": -12345,\n"
" \"int32Field\": -12345678,\n"
" \"int64Field\": \"-123456789012345\",\n"
" \"uInt8Field\": 234,\n"
" \"uInt16Field\": 45678,\n"
" \"uInt32Field\": 3456789012,\n"
" \"uInt64Field\": \"12345678901234567890\",\n"
" \"float32Field\": 1234.5,\n"
" \"float64Field\": -1.23e47,\n"
" \"textField\": \"foo\",\n"
" \"dataField\": [98, 97, 114],\n"
" \"structField\": {\n"
" \"voidField\": null,\n"
" \"boolField\": true,\n"
" \"int8Field\": -12,\n"
" \"int16Field\": 3456,\n"
" \"int32Field\": -78901234,\n"
" \"int64Field\": \"56789012345678\",\n"
" \"uInt8Field\": 90,\n"
" \"uInt16Field\": 1234,\n"
" \"uInt32Field\": 56789012,\n"
" \"uInt64Field\": \"345678901234567890\",\n"
" \"float32Field\": -1.2499999646475857e-10,\n"
" \"float64Field\": 345,\n"
" \"textField\": \"baz\",\n"
" \"dataField\": [113, 117, 120],\n"
" \"structField\": {\n"
" \"voidField\": null,\n"
" \"boolField\": false,\n"
" \"int8Field\": 0,\n"
" \"int16Field\": 0,\n"
" \"int32Field\": 0,\n"
" \"int64Field\": \"0\",\n"
" \"uInt8Field\": 0,\n"
" \"uInt16Field\": 0,\n"
" \"uInt32Field\": 0,\n"
" \"uInt64Field\": \"0\",\n"
" \"float32Field\": 0,\n"
" \"float64Field\": 0,\n"
" \"textField\": \"nested\",\n"
" \"structField\": {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"really nested\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
" \"enumField\": \"foo\",\n"
" \"interfaceField\": null },\n"
" \"enumField\": \"baz\",\n"
" \"interfaceField\": null,\n"
" \"voidList\": [null, null, null],\n"
" \"boolList\": [false, true, false, true, true],\n"
" \"int8List\": [12, -34, -128, 127],\n"
" \"int16List\": [1234, -5678, -32768, 32767],\n"
" \"int32List\": [12345678, -90123456, -2147483648, 2147483647],\n"
" \"int64List\": [\"123456789012345\", \"-678901234567890\", \"-9223372036854775808\", \"9223372036854775807\"],\n"
" \"uInt8List\": [12, 34, 0, 255],\n"
" \"uInt16List\": [1234, 5678, 0, 65535],\n"
" \"uInt32List\": [12345678, 90123456, 0, 4294967295],\n"
" \"uInt64List\": [\"123456789012345\", \"678901234567890\", \"0\", \"18446744073709551615\"],\n"
" \"float32List\": [0, 1234567, 9.9999999338158125e36, -9.9999999338158125e36, 9.99999991097579e-38, -9.99999991097579e-38],\n"
" \"float64List\": [0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306],\n"
" \"textList\": [\"quux\", \"corge\", \"grault\"],\n"
" \"dataList\": [[103, 97, 114, 112, 108, 121], [119, 97, 108, 100, 111], [102, 114, 101, 100]],\n"
" \"structList\": [\n"
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"x structlist 1\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"x structlist 2\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"x structlist 3\", \"enumField\": \"foo\", \"interfaceField\": null} ],\n"
" \"enumList\": [\"qux\", \"bar\", \"grault\"] },\n"
" \"enumField\": \"corge\",\n"
" \"interfaceField\": null,\n"
" \"voidList\": [null, null, null, null, null, null],\n"
" \"boolList\": [true, false, false, true],\n"
" \"int8List\": [111, -111],\n"
" \"int16List\": [11111, -11111],\n"
" \"int32List\": [111111111, -111111111],\n"
" \"int64List\": [\"1111111111111111111\", \"-1111111111111111111\"],\n"
" \"uInt8List\": [111, 222],\n"
" \"uInt16List\": [33333, 44444],\n"
" \"uInt32List\": [3333333333],\n"
" \"uInt64List\": [\"11111111111111111111\"],\n"
" \"float32List\": [5555.5, \"Infinity\", \"-Infinity\", \"NaN\"],\n"
" \"float64List\": [7777.75, \"Infinity\", \"-Infinity\", \"NaN\"],\n"
" \"textList\": [\"plugh\", \"xyzzy\", \"thud\"],\n"
" \"dataList\": [[111, 111, 112, 115], [101, 120, 104, 97, 117, 115, 116, 101, 100], [114, 102, 99, 51, 48, 57, 50]],\n"
" \"structList\": [\n"
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"structlist 1\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"structlist 2\", \"enumField\": \"foo\", \"interfaceField\": null},\n"
" {\"voidField\": null, \"boolField\": false, \"int8Field\": 0, \"int16Field\": 0, \"int32Field\": 0, \"int64Field\": \"0\", \"uInt8Field\": 0, \"uInt16Field\": 0, \"uInt32Field\": 0, \"uInt64Field\": \"0\", \"float32Field\": 0, \"float64Field\": 0, \"textField\": \"structlist 3\", \"enumField\": \"foo\", \"interfaceField\": null} ],\n"
" \"enumList\": [\"foo\", \"garply\"] }";
KJ_TEST("encode all types") {
MallocMessageBuilder message;
auto root = message.getRoot<TestAllTypes>();
initTestMessage(root);
JsonCodec json;
json.setPrettyPrint(true);
KJ_EXPECT(json.encode(root) == ALL_TYPES_JSON);
// Verify that if we strip out the non-string spaces, we get the non-pretty-print version.
kj::Vector<char> chars;
bool inQuotes = false;
for (char c: ALL_TYPES_JSON) {
if (c == '\"') inQuotes = !inQuotes;
if ((c == '\n' || c == ' ') && !inQuotes) {
// skip space
} else {
chars.add(c);
}
}
kj::String nospaces(chars.releaseAsArray());
json.setPrettyPrint(false);
KJ_EXPECT(json.encode(root) == nospaces);
}
KJ_TEST("encode union") {
MallocMessageBuilder message;
auto root = message.getRoot<test::TestUnnamedUnion>();
root.setBefore("a");
root.setMiddle(44);
root.setAfter("c");
JsonCodec json;
root.setFoo(123);
KJ_EXPECT(json.encode(root) == "{\"before\":\"a\",\"foo\":123,\"middle\":44,\"after\":\"c\"}");
root.setBar(321);
KJ_EXPECT(json.encode(root) == "{\"before\":\"a\",\"middle\":44,\"bar\":321,\"after\":\"c\"}");
}
KJ_TEST("decode all types") {
JsonCodec json;
json.setHasMode(HasMode::NON_DEFAULT);
#define CASE_MAYBE_ROUNDTRIP(s, f, roundtrip) \
{ \
MallocMessageBuilder message; \
auto root = message.initRoot<TestAllTypes>(); \
kj::StringPtr input = s; \
json.decode(input, root); \
KJ_EXPECT((f), input, root); \
auto reencoded = json.encode(root); \
KJ_EXPECT(roundtrip == (input == reencoded), roundtrip, input, reencoded); \
}
#define CASE_NO_ROUNDTRIP(s, f) CASE_MAYBE_ROUNDTRIP(s, f, false)
#define CASE(s, f) CASE_MAYBE_ROUNDTRIP(s, f, true)
#define CASE_THROW(s, errorMessage) \
{ \
MallocMessageBuilder message; \
auto root = message.initRoot<TestAllTypes>(); \
KJ_EXPECT_THROW_MESSAGE(errorMessage, json.decode(s, root)); \
}
#define CASE_THROW_RECOVERABLE(s, errorMessage) \
{ \
MallocMessageBuilder message; \
auto root = message.initRoot<TestAllTypes>(); \
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE(errorMessage, json.decode(s, root)); \
}
CASE(R"({})", root.getBoolField() == false);
CASE_NO_ROUNDTRIP(R"({"unknownField":7})", root.getBoolField() == false);
CASE(R"({"boolField":true})", root.getBoolField() == true);
CASE(R"({"int8Field":-128})", root.getInt8Field() == -128);
CASE_NO_ROUNDTRIP(R"({"int8Field":"127"})", root.getInt8Field() == 127);
CASE_THROW_RECOVERABLE(R"({"int8Field":"-129"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int8Field":128})", "Value out-of-range");
CASE(R"({"int16Field":-32768})", root.getInt16Field() == -32768);
CASE_NO_ROUNDTRIP(R"({"int16Field":"32767"})", root.getInt16Field() == 32767);
CASE_THROW_RECOVERABLE(R"({"int16Field":"-32769"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int16Field":32768})", "Value out-of-range");
CASE(R"({"int32Field":-2147483648})", root.getInt32Field() == -2147483648);
CASE_NO_ROUNDTRIP(R"({"int32Field":"2147483647"})", root.getInt32Field() == 2147483647);
CASE_NO_ROUNDTRIP(R"({"int64Field":-9007199254740992})", root.getInt64Field() == -9007199254740992LL);
CASE_NO_ROUNDTRIP(R"({"int64Field":9007199254740991})", root.getInt64Field() == 9007199254740991LL);
CASE(R"({"int64Field":"-9223372036854775808"})", root.getInt64Field() == -9223372036854775808ULL);
CASE(R"({"int64Field":"9223372036854775807"})", root.getInt64Field() == 9223372036854775807LL);
CASE_THROW_RECOVERABLE(R"({"int64Field":"-9223372036854775809"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int64Field":"9223372036854775808"})", "Value out-of-range");
CASE(R"({"uInt8Field":255})", root.getUInt8Field() == 255);
CASE_NO_ROUNDTRIP(R"({"uInt8Field":"0"})", root.getUInt8Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt8Field":"256"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt8Field":-1})", "Value out-of-range");
CASE(R"({"uInt16Field":65535})", root.getUInt16Field() == 65535);
CASE_NO_ROUNDTRIP(R"({"uInt16Field":"0"})", root.getUInt16Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt16Field":"655356"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt16Field":-1})", "Value out-of-range");
CASE(R"({"uInt32Field":4294967295})", root.getUInt32Field() == 4294967295);
CASE_NO_ROUNDTRIP(R"({"uInt32Field":"0"})", root.getUInt32Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt32Field":"42949672956"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt32Field":-1})", "Value out-of-range");
CASE_NO_ROUNDTRIP(R"({"uInt64Field":9007199254740991})", root.getUInt64Field() == 9007199254740991ULL);
CASE(R"({"uInt64Field":"18446744073709551615"})", root.getUInt64Field() == 18446744073709551615ULL);
CASE_NO_ROUNDTRIP(R"({"uInt64Field":"0"})", root.getUInt64Field() == 0);
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"18446744073709551616"})", "Value out-of-range");
CASE_NO_ROUNDTRIP(R"({"float32Field":0})", root.getFloat32Field() == 0);
CASE(R"({"float32Field":4.5})", root.getFloat32Field() == 4.5);
CASE_NO_ROUNDTRIP(R"({"float32Field":null})", kj::isNaN(root.getFloat32Field()));
CASE(R"({"float32Field":"NaN"})", kj::isNaN(root.getFloat32Field()));
CASE_NO_ROUNDTRIP(R"({"float32Field":"nan"})", kj::isNaN(root.getFloat32Field()));
CASE(R"({"float32Field":"Infinity"})", root.getFloat32Field() == kj::inf());
CASE(R"({"float32Field":"-Infinity"})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"infinity"})", root.getFloat32Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"-infinity"})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"INF"})", root.getFloat32Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":"-INF"})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":1e39})", root.getFloat32Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float32Field":-1e39})", root.getFloat32Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":0})", root.getFloat64Field() == 0);
CASE(R"({"float64Field":4.5})", root.getFloat64Field() == 4.5);
CASE_NO_ROUNDTRIP(R"({"float64Field":null})", kj::isNaN(root.getFloat64Field()));
CASE(R"({"float64Field":"NaN"})", kj::isNaN(root.getFloat64Field()));
CASE_NO_ROUNDTRIP(R"({"float64Field":"nan"})", kj::isNaN(root.getFloat64Field()));
CASE(R"({"float64Field":"Infinity"})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"infinity"})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"-infinity"})", root.getFloat64Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"INF"})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":"-INF"})", root.getFloat64Field() == -kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":1e309})", root.getFloat64Field() == kj::inf());
CASE_NO_ROUNDTRIP(R"({"float64Field":-1e309})", root.getFloat64Field() == -kj::inf());
CASE(R"({"textField":"hello"})", kj::str("hello") == root.getTextField());
CASE(R"({"dataField":[7,0,122]})",
kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataField());
CASE(R"({"structField":{}})", root.hasStructField() == true);
CASE(R"({"structField":{}})", root.getStructField().getBoolField() == false);
CASE_NO_ROUNDTRIP(R"({"structField":{"boolField":false}})", root.getStructField().getBoolField() == false);
CASE(R"({"structField":{"boolField":true}})", root.getStructField().getBoolField() == true);
CASE(R"({"enumField":"bar"})", root.getEnumField() == TestEnum::BAR);
CASE_NO_ROUNDTRIP(R"({"textField":"foo\u1234bar"})",
kj::str(u8"foo\u1234bar") == root.getTextField());
CASE_THROW_RECOVERABLE(R"({"structField":null})", "Expected object value");
CASE_THROW_RECOVERABLE(R"({"structList":null})", "Expected list value");
CASE_THROW_RECOVERABLE(R"({"boolList":null})", "Expected list value");
CASE_THROW_RECOVERABLE(R"({"structList":[null]})", "Expected object value");
CASE_THROW_RECOVERABLE(R"({"int64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"float64Field":"177a"})", "String does not contain valid");
CASE(R"({})", root.hasBoolList() == false);
CASE(R"({"boolList":[]})", root.hasBoolList() == true);
CASE(R"({"boolList":[]})", root.getBoolList().size() == 0);
CASE(R"({"boolList":[false]})", root.getBoolList().size() == 1);
CASE(R"({"boolList":[false]})", root.getBoolList()[0] == false);
CASE(R"({"boolList":[true]})", root.getBoolList()[0] == true);
CASE(R"({"int8List":[7]})", root.getInt8List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"int8List":["7"]})", root.getInt8List()[0] == 7);
CASE(R"({"int16List":[7]})", root.getInt16List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"int16List":["7"]})", root.getInt16List()[0] == 7);
CASE(R"({"int32List":[7]})", root.getInt32List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"int32List":["7"]})", root.getInt32List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"int64List":[7]})", root.getInt64List()[0] == 7);
CASE(R"({"int64List":["7"]})", root.getInt64List()[0] == 7);
CASE(R"({"uInt8List":[7]})", root.getUInt8List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"uInt8List":["7"]})", root.getUInt8List()[0] == 7);
CASE(R"({"uInt16List":[7]})", root.getUInt16List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"uInt16List":["7"]})", root.getUInt16List()[0] == 7);
CASE(R"({"uInt32List":[7]})", root.getUInt32List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"uInt32List":["7"]})", root.getUInt32List()[0] == 7);
CASE_NO_ROUNDTRIP(R"({"uInt64List":[7]})", root.getUInt64List()[0] == 7);
CASE(R"({"uInt64List":["7"]})", root.getUInt64List()[0] == 7);
CASE(R"({"float32List":[4.5]})", root.getFloat32List()[0] == 4.5);
CASE_NO_ROUNDTRIP(R"({"float32List":["4.5"]})", root.getFloat32List()[0] == 4.5);
CASE_NO_ROUNDTRIP(R"({"float32List":[null]})", kj::isNaN(root.getFloat32List()[0]));
CASE(R"({"float32List":["NaN"]})", kj::isNaN(root.getFloat32List()[0]));
CASE(R"({"float32List":["Infinity"]})", root.getFloat32List()[0] == kj::inf());
CASE(R"({"float32List":["-Infinity"]})", root.getFloat32List()[0] == -kj::inf());
CASE(R"({"float64List":[4.5]})", root.getFloat64List()[0] == 4.5);
CASE_NO_ROUNDTRIP(R"({"float64List":["4.5"]})", root.getFloat64List()[0] == 4.5);
CASE_NO_ROUNDTRIP(R"({"float64List":[null]})", kj::isNaN(root.getFloat64List()[0]));
CASE(R"({"float64List":["NaN"]})", kj::isNaN(root.getFloat64List()[0]));
CASE(R"({"float64List":["Infinity"]})", root.getFloat64List()[0] == kj::inf());
CASE(R"({"float64List":["-Infinity"]})", root.getFloat64List()[0] == -kj::inf());
CASE(R"({"textList":["hello"]})", kj::str("hello") == root.getTextList()[0]);
CASE(R"({"dataList":[[7,0,122]]})",
kj::heapArray<byte>({7,0,122}).asPtr() == root.getDataList()[0]);
CASE(R"({"structList":[{}]})", root.hasStructList() == true);
CASE(R"({"structList":[{}]})", root.getStructList()[0].getBoolField() == false);
CASE_NO_ROUNDTRIP(R"({"structList":[{"boolField":false}]})", root.getStructList()[0].getBoolField() == false);
CASE(R"({"structList":[{"boolField":true}]})", root.getStructList()[0].getBoolField() == true);
CASE(R"({"enumList":["bar"]})", root.getEnumList()[0] == TestEnum::BAR);
#undef CASE_MAYBE_ROUNDTRIP
#undef CASE_NO_ROUNDTRIP
#undef CASE
#undef CASE_THROW
#undef CASE_THROW_RECOVERABLE
}
KJ_TEST("decode test message") {
MallocMessageBuilder message;
auto root = message.getRoot<TestAllTypes>();
initTestMessage(root);
JsonCodec json;
auto encoded = json.encode(root);
MallocMessageBuilder decodedMessage;
auto decodedRoot = decodedMessage.initRoot<TestAllTypes>();
json.decode(encoded, decodedRoot);
KJ_EXPECT(root.toString().flatten() == decodedRoot.toString().flatten());
}
KJ_TEST("basic json decoding") {
// TODO(cleanup): this test is a mess!
JsonCodec json;
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("null", root);
KJ_EXPECT(root.which() == JsonValue::NULL_);
KJ_EXPECT(root.getNull() == VOID);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("false", root);
KJ_EXPECT(root.which() == JsonValue::BOOLEAN);
KJ_EXPECT(root.getBoolean() == false);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("true", root);
KJ_EXPECT(root.which() == JsonValue::BOOLEAN);
KJ_EXPECT(root.getBoolean() == true);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("\"foo\"", root);
KJ_EXPECT(root.which() == JsonValue::STRING);
KJ_EXPECT(kj::str("foo") == root.getString());
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(R"("\"")", root);
KJ_EXPECT(root.which() == JsonValue::STRING);
KJ_EXPECT(kj::str("\"") == root.getString());
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(R"("\\abc\"d\\e")", root);
KJ_EXPECT(root.which() == JsonValue::STRING);
KJ_EXPECT(kj::str("\\abc\"d\\e") == root.getString());
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(R"("\"\\\/\b\f\n\r\t\u0003abc\u0064\u0065f")", root);
KJ_EXPECT(root.which() == JsonValue::STRING);
KJ_EXPECT(kj::str("\"\\/\b\f\n\r\t\x03""abcdef") == root.getString(), root.getString());
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("[]", root);
KJ_EXPECT(root.which() == JsonValue::ARRAY, (uint)root.which());
KJ_EXPECT(root.getArray().size() == 0);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("[true]", root);
KJ_EXPECT(root.which() == JsonValue::ARRAY);
auto array = root.getArray();
KJ_EXPECT(array.size() == 1, array.size());
KJ_EXPECT(root.getArray()[0].which() == JsonValue::BOOLEAN);
KJ_EXPECT(root.getArray()[0].getBoolean() == true);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(" [ true , false\t\n , null]", root);
KJ_EXPECT(root.which() == JsonValue::ARRAY);
auto array = root.getArray();
KJ_EXPECT(array.size() == 3);
KJ_EXPECT(array[0].which() == JsonValue::BOOLEAN);
KJ_EXPECT(array[0].getBoolean() == true);
KJ_EXPECT(array[1].which() == JsonValue::BOOLEAN);
KJ_EXPECT(array[1].getBoolean() == false);
KJ_EXPECT(array[2].which() == JsonValue::NULL_);
KJ_EXPECT(array[2].getNull() == VOID);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("{}", root);
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
KJ_EXPECT(root.getObject().size() == 0);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("\r\n\t {\r\n\t }\r\n\t ", root);
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
KJ_EXPECT(root.getObject().size() == 0);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(R"({"some": null})", root);
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
auto object = root.getObject();
KJ_EXPECT(object.size() == 1);
KJ_EXPECT(kj::str("some") == object[0].getName());
KJ_EXPECT(object[0].getValue().which() == JsonValue::NULL_);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(R"({"foo\n\tbaz": "a val", "bar": ["a", -5.5e11, { "z": {}}]})", root);
KJ_EXPECT(root.which() == JsonValue::OBJECT, (uint)root.which());
auto object = root.getObject();
KJ_EXPECT(object.size() == 2);
KJ_EXPECT(kj::str("foo\n\tbaz") == object[0].getName());
KJ_EXPECT(object[0].getValue().which() == JsonValue::STRING);
KJ_EXPECT(kj::str("a val") == object[0].getValue().getString());
KJ_EXPECT(kj::str("bar") == object[1].getName());
KJ_EXPECT(object[1].getValue().which() == JsonValue::ARRAY);
auto array = object[1].getValue().getArray();
KJ_EXPECT(array.size() == 3, array.size());
KJ_EXPECT(array[0].which() == JsonValue::STRING);
KJ_EXPECT(kj::str("a") == array[0].getString());
KJ_EXPECT(array[1].which() == JsonValue::NUMBER);
KJ_EXPECT(array[1].getNumber() == -5.5e11);
KJ_EXPECT(array[2].which() == JsonValue::OBJECT);
KJ_EXPECT(array[2].getObject().size() == 1);
KJ_EXPECT(array[2].getObject()[0].getValue().which() == JsonValue::OBJECT);
KJ_EXPECT(array[2].getObject()[0].getValue().getObject().size() == 0);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("123", root);
KJ_EXPECT(root.which() == JsonValue::NUMBER);
KJ_EXPECT(root.getNumber() == 123);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("input", json.decodeRaw("z", root));
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
// Leading + not allowed in numbers.
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("+123", root));
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("[00]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected", json.decodeRaw("[01]", root));
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("-", root));
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("-5", root);
KJ_EXPECT(root.which() == JsonValue::NUMBER);
KJ_EXPECT(root.getNumber() == -5);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw("-5.5", root);
KJ_EXPECT(root.which() == JsonValue::NUMBER);
KJ_EXPECT(root.getNumber() == -5.5);
}
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("a", root));
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("[", root));
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("{", root));
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("\"\\u\"", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[}", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("{]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[}]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[1, , ]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[,]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[true,]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[, 1]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[1\"\"]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("[1,, \"\"]", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("{\"a\"1: 0}", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw(R"({"some": null,})", root));
KJ_EXPECT_THROW_MESSAGE("Input remains", json.decodeRaw("11a", root));
KJ_EXPECT_THROW_MESSAGE("Invalid escape", json.decodeRaw(R"("\z")", root));
KJ_EXPECT_THROW_MESSAGE("Invalid escape", json.decodeRaw(R"("\z")", root));
{
// TODO(msvc): This raw string literal currently confuses MSVC's preprocessor, so I hoisted
// it outside the macro.
auto f = [&] { json.decodeRaw(R"(["\n\", 3])", root); };
KJ_EXPECT_THROW_MESSAGE("ends prematurely", f());
}
KJ_EXPECT_THROW_MESSAGE("Invalid hex", json.decodeRaw(R"("\u12zz")", root));
KJ_EXPECT_THROW_MESSAGE("ends prematurely", json.decodeRaw("-", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("--", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("\f{}", root));
KJ_EXPECT_THROW_MESSAGE("Unexpected input", json.decodeRaw("{\v}", root));
}
}
KJ_TEST("maximum nesting depth") {
JsonCodec json;
auto input = kj::str(R"({"foo": "a", "bar": ["b", { "baz": [-5.5e11] }, [ [ 1 ], { "z": 2 }]]})");
// `input` has a maximum nesting depth of 4, reached 3 times.
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(input, root);
}
{
json.setMaxNestingDepth(0);
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("nest",
json.decodeRaw(input, root));
}
{
json.setMaxNestingDepth(3);
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("nest",
json.decodeRaw(input, root));
}
{
json.setMaxNestingDepth(4);
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(input, root);
}
}
KJ_TEST("unknown fields") {
JsonCodec json;
MallocMessageBuilder message;
auto root = message.initRoot<TestJsonAnnotations2>();
auto valid = R"({"foo": "a"})"_kj;
auto unknown = R"({"foo": "a", "unknown-field": "b"})"_kj;
json.decode(valid, root);
json.decode(unknown, root);
json.setRejectUnknownFields(true);
json.decode(valid, root);
KJ_EXPECT_THROW_MESSAGE("Unknown field", json.decode(unknown, root));
// Verify unknown field rejection still works when handling by annotation.
json.handleByAnnotation<TestJsonAnnotations2>();
KJ_EXPECT_THROW_MESSAGE("Unknown field", json.decode(unknown, root));
}
class TestCallHandler: public JsonCodec::Handler<Text> {
public:
void encode(const JsonCodec& codec, Text::Reader input,
JsonValue::Builder output) const override {
auto call = output.initCall();
call.setFunction("Frob");
auto params = call.initParams(2);
params[0].setNumber(123);
params[1].setString(input);
}
Orphan<Text> decode(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const override {
KJ_UNIMPLEMENTED("TestHandler::decode");
}
};
class TestDynamicStructHandler: public JsonCodec::Handler<DynamicStruct> {
public:
void encode(const JsonCodec& codec, DynamicStruct::Reader input,
JsonValue::Builder output) const override {
auto fields = input.getSchema().getFields();
auto items = output.initArray(fields.size());
for (auto field: fields) {
KJ_REQUIRE(field.getIndex() < items.size());
auto item = items[field.getIndex()];
if (input.has(field)) {
codec.encode(input.get(field), field.getType(), item);
} else {
item.setNull();
}
}
}
void decode(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const override {
auto orphanage = Orphanage::getForMessageContaining(output);
auto fields = output.getSchema().getFields();
auto items = input.getArray();
for (auto field: fields) {
KJ_REQUIRE(field.getIndex() < items.size());
auto item = items[field.getIndex()];
if (!item.isNull()) {
output.adopt(field, codec.decode(item, field.getType(), orphanage));
}
}
}
};
class TestStructHandler: public JsonCodec::Handler<test::TestOldVersion> {
public:
void encode(const JsonCodec& codec, test::TestOldVersion::Reader input, JsonValue::Builder output) const override {
dynamicHandler.encode(codec, input, output);
}
void decode(const JsonCodec& codec, JsonValue::Reader input, test::TestOldVersion::Builder output) const override {
dynamicHandler.decode(codec, input, output);
}
private:
TestDynamicStructHandler dynamicHandler;
};
KJ_TEST("register custom encoding handlers") {
JsonCodec json;
TestStructHandler structHandler;
json.addTypeHandler(structHandler);
// JSON decoder can't parse calls back, so test only encoder here
TestCallHandler callHandler;
json.addTypeHandler(callHandler);
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
root.setOld1(123);
root.setOld2("foo");
KJ_EXPECT(json.encode(root) == "[\"123\",Frob(123,\"foo\"),null]");
}
KJ_TEST("register custom roundtrip handler") {
for (auto i = 1; i <= 2; i++) {
JsonCodec json;
TestStructHandler staticHandler;
TestDynamicStructHandler dynamicHandler;
kj::String encoded;
if (i == 1) {
// first iteration: test with explicit struct handler
json.addTypeHandler(staticHandler);
} else {
// second iteration: same checks, but with DynamicStruct handler
json.addTypeHandler(StructSchema::from<test::TestOldVersion>(), dynamicHandler);
}
{
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
root.setOld1(123);
root.initOld3().setOld2("foo");
encoded = json.encode(root);
KJ_EXPECT(encoded == "[\"123\",null,[\"0\",\"foo\",null]]");
}
{
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
json.decode(encoded, root);
KJ_EXPECT(root.getOld1() == 123);
KJ_EXPECT(!root.hasOld2());
auto nested = root.getOld3();
KJ_EXPECT(nested.getOld1() == 0);
KJ_EXPECT("foo" == nested.getOld2());
KJ_EXPECT(!nested.hasOld3());
}
}
}
KJ_TEST("register field handler") {
TestStructHandler handler;
JsonCodec json;
json.addFieldHandler(StructSchema::from<test::TestOldVersion>().getFieldByName("old3"),
handler);
kj::String encoded;
{
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
root.setOld1(123);
root.setOld2("foo");
auto nested = root.initOld3();
nested.setOld2("bar");
encoded = json.encode(root);
KJ_EXPECT(encoded == "{\"old1\":\"123\",\"old2\":\"foo\",\"old3\":[\"0\",\"bar\",null]}")
}
{
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
json.decode(encoded, root);
KJ_EXPECT(root.getOld1() == 123);
KJ_EXPECT("foo" == root.getOld2());
auto nested = root.getOld3();
KJ_EXPECT(nested.getOld1() == 0);
KJ_EXPECT("bar" == nested.getOld2());
KJ_EXPECT(!nested.hasOld3());
}
}
class TestCapabilityHandler: public JsonCodec::Handler<test::TestInterface> {
public:
void encode(const JsonCodec& codec, test::TestInterface::Client input,
JsonValue::Builder output) const override {
KJ_UNIMPLEMENTED("TestCapabilityHandler::encode");
}
test::TestInterface::Client decode(
const JsonCodec& codec, JsonValue::Reader input) const override {
return nullptr;
}
};
KJ_TEST("register capability handler") {
// This test currently only checks that this compiles, which at one point wasn't the caes.
// TODO(test): Actually run some code here.
TestCapabilityHandler handler;
JsonCodec json;
json.addTypeHandler(handler);
}
static constexpr kj::StringPtr GOLDEN_ANNOTATED =
R"({ "names-can_contain!anything Really": "foo",
"flatFoo": 123,
"flatBar": "abc",
"renamed-flatBaz": {"hello": true},
"flatQux": "cba",
"pfx.foo": "this is a long string in order to force multi-line pretty printing",
"pfx.renamed-bar": 321,
"pfx.baz": {"hello": true},
"pfx.xfp.qux": "fed",
"union-type": "renamed-bar",
"barMember": 789,
"multiMember": "ghi",
"dependency": {"renamed-foo": "corge"},
"simpleGroup": {"renamed-grault": "garply"},
"enums": ["qux", "renamed-bar", "foo", "renamed-baz"],
"innerJson": [123, "hello", {"object": true}],
"customFieldHandler": "add-prefix-waldo",
"testBase64": "ZnJlZA==",
"testHex": "706c756768",
"bUnion": "renamed-bar",
"bValue": 678,
"externalUnion": {"type": "bar", "value": "cba"},
"unionWithVoid": {"type": "voidValue"} })"_kj;
static constexpr kj::StringPtr GOLDEN_ANNOTATED_REVERSE =
R"({
"unionWithVoid": {"type": "voidValue"},
"externalUnion": {"type": "bar", "value": "cba"},
"bValue": 678,
"bUnion": "renamed-bar",
"testHex": "706c756768",
"testBase64": "ZnJlZA==",
"customFieldHandler": "add-prefix-waldo",
"innerJson": [123, "hello", {"object": true}],
"enums": ["qux", "renamed-bar", "foo", "renamed-baz"],
"simpleGroup": { "renamed-grault": "garply" },
"dependency": { "renamed-foo": "corge" },
"multiMember": "ghi",
"barMember": 789,
"union-type": "renamed-bar",
"pfx.xfp.qux": "fed",
"pfx.baz": {"hello": true},
"pfx.renamed-bar": 321,
"pfx.foo": "this is a long string in order to force multi-line pretty printing",
"flatQux": "cba",
"renamed-flatBaz": {"hello": true},
"flatBar": "abc",
"flatFoo": 123,
"names-can_contain!anything Really": "foo"
})"_kj;
class PrefixAdder: public JsonCodec::Handler<capnp::Text> {
public:
void encode(const JsonCodec& codec, capnp::Text::Reader input, JsonValue::Builder output) const {
output.setString(kj::str("add-prefix-", input));
}
Orphan<capnp::Text> decode(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const {
return orphanage.newOrphanCopy(capnp::Text::Reader(input.getString().slice(11)));
}
};
KJ_TEST("rename fields") {
JsonCodec json;
json.handleByAnnotation<TestJsonAnnotations>();
json.setPrettyPrint(true);
PrefixAdder customHandler;
json.addFieldHandler(Schema::from<TestJsonAnnotations>().getFieldByName("customFieldHandler"),
customHandler);
kj::String goldenText;
{
MallocMessageBuilder message;
auto root = message.getRoot<TestJsonAnnotations>();
root.setSomeField("foo");
auto aGroup = root.getAGroup();
aGroup.setFlatFoo(123);
aGroup.setFlatBar("abc");
aGroup.getFlatBaz().setHello(true);
aGroup.getDoubleFlat().setFlatQux("cba");
auto prefixedGroup = root.getPrefixedGroup();
prefixedGroup.setFoo("this is a long string in order to force multi-line pretty printing");
prefixedGroup.setBar(321);
prefixedGroup.getBaz().setHello(true);
prefixedGroup.getMorePrefix().setQux("fed");
auto unionBar = root.getAUnion().initBar();
unionBar.setBarMember(789);
unionBar.setMultiMember("ghi");
root.initDependency().setFoo("corge");
root.initSimpleGroup().setGrault("garply");
root.setEnums({
TestJsonAnnotatedEnum::QUX,
TestJsonAnnotatedEnum::BAR,
TestJsonAnnotatedEnum::FOO,
TestJsonAnnotatedEnum::BAZ
});
auto val = root.initInnerJson();
auto arr = val.initArray(3);
arr[0].setNumber(123);
arr[1].setString("hello");
auto field = arr[2].initObject(1)[0];
field.setName("object");
field.initValue().setBoolean(true);
root.setCustomFieldHandler("waldo");
root.setTestBase64("fred"_kj.asBytes());
root.setTestHex("plugh"_kj.asBytes());
root.getBUnion().setBar(678);
root.initExternalUnion().initBar().setValue("cba");
root.initUnionWithVoid().setVoidValue();
auto encoded = json.encode(root.asReader());
KJ_EXPECT(encoded == GOLDEN_ANNOTATED, encoded);
goldenText = kj::str(root);
}
{
MallocMessageBuilder message;
auto root = message.getRoot<TestJsonAnnotations>();
json.decode(GOLDEN_ANNOTATED, root);
KJ_EXPECT(kj::str(root) == goldenText, root, goldenText);
}
{
// Try parsing in reverse, mostly to test that union tags can come after content.
MallocMessageBuilder message;
auto root = message.getRoot<TestJsonAnnotations>();
json.decode(GOLDEN_ANNOTATED_REVERSE, root);
KJ_EXPECT(kj::str(root) == goldenText, root, goldenText);
}
}
KJ_TEST("base64 union encoded correctly") {
// At one point field handlers were not correctly applied when the field was a member of a union
// in a type that was handled by annotation.
JsonCodec json;
json.handleByAnnotation<TestBase64Union>();
json.setPrettyPrint(true);
MallocMessageBuilder message;
auto root = message.getRoot<TestBase64Union>();
root.initFoo(5);
KJ_EXPECT(json.encode(root) == "{\"foo\": \"AAAAAAA=\"}", json.encode(root));
}
} // namespace
} // namespace _ (private)
} // namespace capnp