Commit e3e9ce8d authored by Kenton Varda's avatar Kenton Varda

WIP JSON encoder/decoder library. Currently only encodes.

The library allows for registering special handlers for specific types or fields. This is particluarly useful for overriding the way `Data` is encoded (since many approaches exist) or supporting encodings like EJSON or Q which extend JSON with special types encoded as objects with field names perfixed by dollar signs.

Not integrated into build system yet (but builds nicely with Ekam). I think this is going to need to be a separate library, e.g. libcapnp-json, because clearly a lot of Cap'n Proto users don't need it at all.

For the moment, this was written for use inside Sandstorm. There is no current need for a decoder, so I have not written that yet and have no immediate plans to do so. But it will be added before any official Cap'n Proto release, certainly. A simple recursive descent parser should be easy...
parent 33ebc2a4
......@@ -23,6 +23,7 @@
#include <kj/compat/gtest.h>
#include <kj/string.h>
#include <kj/debug.h>
#include <capnp/test.capnp.h>
#if HAVE_CONFIG_H
#include "config.h"
......@@ -60,5 +61,25 @@ struct ExampleInterface {
static_assert(_::Kind_<ExampleStruct>::kind == Kind::STRUCT, "Kind SFINAE failed.");
static_assert(_::Kind_<ExampleInterface>::kind == Kind::INTERFACE, "Kind SFINAE failed.");
// Test FromAnay<>
template <typename T, typename U>
struct EqualTypes_ { static constexpr bool value = false; };
template <typename T>
struct EqualTypes_<T, T> { static constexpr bool value = true; };
template <typename T, typename U>
inline constexpr bool equalTypes() { return EqualTypes_<T, U>::value; }
using capnproto_test::capnp::test::TestAllTypes;
using capnproto_test::capnp::test::TestInterface;
static_assert(equalTypes<FromAny<int>, int>(), "");
static_assert(equalTypes<FromAny<TestAllTypes::Reader>, TestAllTypes>(), "");
static_assert(equalTypes<FromAny<TestAllTypes::Builder>, TestAllTypes>(), "");
static_assert(equalTypes<FromAny<TestAllTypes::Pipeline>, TestAllTypes>(), "");
static_assert(equalTypes<FromAny<TestInterface::Client>, TestInterface>(), "");
static_assert(equalTypes<FromAny<kj::Own<TestInterface::Server>>, TestInterface>(), "");
} // namespace
} // namespace capnp
......@@ -33,6 +33,7 @@
#include <kj/units.h>
#include <inttypes.h>
#include <kj/string.h>
#include <kj/memory.h>
namespace capnp {
......@@ -84,6 +85,13 @@ enum class Kind: uint8_t {
// special handling. This includes types like AnyPointer, Dynamic*, etc.
};
enum class Style: uint8_t {
PRIMITIVE,
POINTER, // other than struct
STRUCT,
CAPABILITY
};
enum class ElementSize: uint8_t {
// Size of a list element.
......@@ -170,6 +178,13 @@ inline constexpr Kind kind() {
#define CAPNP_KIND(T) ::capnp::kind<T>()
// Use this macro rather than kind<T>() in any code which must work in lite mode.
template <typename T, Kind k = kind<T>()>
inline constexpr Style style() {
return k == Kind::PRIMITIVE || k == Kind::ENUM ? Style::PRIMITIVE
: k == Kind::STRUCT ? Style::STRUCT
: k == Kind::INTERFACE ? Style::CAPABILITY : Style::POINTER;
}
#endif // CAPNP_LITE, else
template <typename T, Kind k = CAPNP_KIND(T)>
......@@ -240,6 +255,27 @@ template <typename T>
using FromServer = typename kj::Decay<T>::Serves;
// FromBuilder<MyType::Server> = MyType (for any Cap'n Proto interface type).
struct FromAny_ {
template <typename T, typename X = FromReader<T>> static X apply(T*, int);
template <typename T, typename X = FromBuilder<T>> static X apply(T*, char);
template <typename T, typename X = FromPipeline<T>> static X apply(T*, long);
// note that ::Client is covered by FromReader
template <typename T, typename X = FromServer<T>> static X apply(kj::Own<T>*, short);
template <typename T, typename = kj::EnableIf<style<T>() == Style::PRIMITIVE>>
static T apply(T*, unsigned int);
};
template <typename T>
using FromAny = kj::Decay<decltype(FromAny_::apply(kj::instance<T*>(), 0))>;
// Given any Cap'n Proto value type as an input, return the Cap'n Proto base type. That is:
//
// Foo::Reader -> Foo
// Foo::Builder -> Foo
// Foo::Pipeline -> Foo
// Foo::Client -> Foo
// Own<Foo::Server> -> Foo
// uint32_t -> uint32_t
namespace _ { // private
template <typename T, Kind k = CAPNP_KIND(T)>
......
// 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 <kj/debug.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, inf, -inf, nan],\n"
" \"float64List\": [7777.75, inf, -inf, 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\"}");
}
class TestHandler: 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");
}
};
KJ_TEST("register handler") {
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOldVersion>();
TestHandler handler;
JsonCodec json;
json.addTypeHandler(handler);
root.setOld1(123);
root.setOld2("foo");
KJ_EXPECT(json.encode(root) == "{\"old1\":\"123\",\"old2\":Frob(123,\"foo\")}");
}
KJ_TEST("register field handler") {
MallocMessageBuilder message;
auto root = message.getRoot<test::TestOutOfOrder>();
TestHandler handler;
JsonCodec json;
json.addFieldHandler(StructSchema::from<test::TestOutOfOrder>().getFieldByName("corge"),
handler);
root.setBaz("abcd");
root.setCorge("efg");
KJ_EXPECT(json.encode(root) == "{\"corge\":Frob(123,\"efg\"),\"baz\":\"abcd\"}");
}
} // namespace
} // namespace _ (private)
} // namespace capnp
// 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 <unordered_map>
#include <kj/debug.h>
namespace capnp {
namespace {
struct TypeHash {
size_t operator()(const Type& type) const {
return type.hashCode();
}
};
struct FieldHash {
size_t operator()(const StructSchema::Field& field) const {
return field.getIndex() ^ field.getContainingStruct().getProto().getId();
}
};
} // namespace
struct JsonCodec::Impl {
bool prettyPrint = false;
std::unordered_map<Type, HandlerBase*, TypeHash> typeHandlers;
std::unordered_map<StructSchema::Field, HandlerBase*, FieldHash> fieldHandlers;
kj::StringTree encodeRaw(JsonValue::Reader value, uint indent, bool& multiline,
bool hasPrefix) const {
switch (value.which()) {
case JsonValue::NULL_:
return kj::strTree("null");
case JsonValue::BOOLEAN:
return kj::strTree(value.getBoolean());
case JsonValue::NUMBER:
return kj::strTree(value.getNumber());
case JsonValue::STRING:
return kj::strTree(encodeString(value.getString()));
case JsonValue::ARRAY: {
auto array = value.getArray();
uint subIndent = indent + (array.size() > 1);
bool childMultiline = false;
auto encodedElements = KJ_MAP(element, array) {
return encodeRaw(element, subIndent, childMultiline, false);
};
return kj::strTree('[', encodeList(
kj::mv(encodedElements), childMultiline, indent, multiline, hasPrefix), ']');
}
case JsonValue::OBJECT: {
auto object = value.getObject();
uint subIndent = indent + (object.size() > 1);
bool childMultiline = false;
kj::StringPtr colon = prettyPrint ? ": " : ":";
auto encodedElements = KJ_MAP(field, object) {
return kj::strTree(
encodeString(field.getName()), colon,
encodeRaw(field.getValue(), subIndent, childMultiline, true));
};
return kj::strTree('{', encodeList(
kj::mv(encodedElements), childMultiline, indent, multiline, hasPrefix), '}');
}
case JsonValue::CALL: {
auto call = value.getCall();
auto params = call.getParams();
uint subIndent = indent + (params.size() > 1);
bool childMultiline = false;
auto encodedElements = KJ_MAP(element, params) {
return encodeRaw(element, subIndent, childMultiline, false);
};
return kj::strTree(call.getFunction(), '(', encodeList(
kj::mv(encodedElements), childMultiline, indent, multiline, true), ')');
}
}
KJ_FAIL_ASSERT("unknown JsonValue type", static_cast<uint>(value.which()));
}
kj::String encodeString(kj::StringPtr chars) const {
static const char HEXDIGITS[] = "0123456789abcdef";
kj::Vector<char> escaped(chars.size() + 3);
escaped.add('"');
for (char c: chars) {
switch (c) {
case '\"': escaped.addAll(kj::StringPtr("\\\"")); break;
case '\\': escaped.addAll(kj::StringPtr("\\\\")); break;
case '/' : escaped.addAll(kj::StringPtr("\\/" )); break;
case '\b': escaped.addAll(kj::StringPtr("\\b")); break;
case '\f': escaped.addAll(kj::StringPtr("\\f")); break;
case '\n': escaped.addAll(kj::StringPtr("\\n")); break;
case '\r': escaped.addAll(kj::StringPtr("\\r")); break;
case '\t': escaped.addAll(kj::StringPtr("\\t")); break;
default:
if (c < 0x20) {
escaped.addAll(kj::StringPtr("\\u00"));
uint8_t c2 = c;
escaped.add(HEXDIGITS[c2 / 16]);
escaped.add(HEXDIGITS[c2 % 16]);
} else {
escaped.add(c);
}
break;
}
}
escaped.add('"');
escaped.add('\0');
return kj::String(escaped.releaseAsArray());
}
kj::StringTree encodeList(kj::Array<kj::StringTree> elements,
bool hasMultilineElement, uint indent, bool& multiline,
bool hasPrefix) const {
size_t maxChildSize = 0;
for (auto& e: elements) maxChildSize = kj::max(maxChildSize, e.size());
kj::StringPtr prefix;
kj::StringPtr delim;
kj::StringPtr suffix;
kj::String ownPrefix;
kj::String ownDelim;
if (!prettyPrint) {
// No whitespace.
delim = ",";
prefix = "";
suffix = "";
} else if ((elements.size() > 1) && (hasMultilineElement || maxChildSize > 50)) {
// If the array contained any multi-line elements, OR it contained sufficiently long
// elements, then put each element on its own line.
auto indentSpace = kj::repeat(' ', (indent + 1) * 2);
delim = ownDelim = kj::str(",\n", indentSpace);
multiline = true;
if (hasPrefix) {
// We're producing a multi-line list, and the first line has some garbage in front of it.
// Therefore, move the first element to the next line.
prefix = ownPrefix = kj::str("\n", indentSpace);
} else {
prefix = " ";
}
suffix = " ";
} else {
// Put everything on one line, but add spacing between elements for legibility.
delim = ", ";
prefix = "";
suffix = "";
}
return kj::strTree(prefix, kj::StringTree(kj::mv(elements), delim), suffix);
}
};
JsonCodec::JsonCodec()
: impl(kj::heap<Impl>()) {}
JsonCodec::~JsonCodec() noexcept(false) {}
void JsonCodec::setPrettyPrint(bool enabled) { impl->prettyPrint = enabled; }
kj::String JsonCodec::encode(DynamicValue::Reader value, Type type) const {
MallocMessageBuilder message;
auto json = message.getRoot<JsonValue>();
encode(value, type, json);
return encodeRaw(json);
}
void JsonCodec::decode(kj::ArrayPtr<const char> input, DynamicStruct::Builder output) const {
MallocMessageBuilder message;
auto json = message.getRoot<JsonValue>();
decodeRaw(input, json);
decode(json, output);
}
Orphan<DynamicValue> JsonCodec::decode(
kj::ArrayPtr<const char> input, Type type, Orphanage orphanage) const {
MallocMessageBuilder message;
auto json = message.getRoot<JsonValue>();
decodeRaw(input, json);
return decode(json, type, orphanage);
}
kj::String JsonCodec::encodeRaw(JsonValue::Reader value) const {
bool multiline = false;
return impl->encodeRaw(value, 0, multiline, false).flatten();
}
void JsonCodec::decodeRaw(kj::ArrayPtr<const char> input, JsonValue::Builder output) const {
KJ_FAIL_ASSERT("JSON decode not implement yet. :(");
}
void JsonCodec::encode(DynamicValue::Reader input, Type type, JsonValue::Builder output) const {
// TODO(soon): For interfaces, check for handlers on superclasses, per documentation...
// TODO(soon): For branded types, should we check for handlers on the generic?
auto iter = impl->typeHandlers.find(type);
if (iter != impl->typeHandlers.end()) {
iter->second->encodeBase(*this, input, output);
return;
}
switch (type.which()) {
case schema::Type::VOID:
output.setNull();
break;
case schema::Type::BOOL:
output.setBoolean(input.as<bool>());
break;
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
output.setNumber(input.as<double>());
break;
case schema::Type::INT64:
output.setString(kj::str(input.as<int64_t>()));
break;
case schema::Type::UINT64:
output.setString(kj::str(input.as<uint64_t>()));
break;
case schema::Type::TEXT:
output.setString(kj::str(input.as<Text>()));
break;
case schema::Type::DATA: {
// Turn into array of byte values. Yep, this is pretty ugly. People really need to override
// this with a handler.
auto bytes = input.as<Data>();
auto array = output.initArray(bytes.size());
for (auto i: kj::indices(bytes)) {
array[i].setNumber(bytes[i]);
}
break;
}
case schema::Type::LIST: {
auto list = input.as<DynamicList>();
auto elementType = type.asList().getElementType();
auto array = output.initArray(list.size());
for (auto i: kj::indices(list)) {
encode(list[i], elementType, array[i]);
}
break;
}
case schema::Type::ENUM: {
auto e = input.as<DynamicEnum>();
KJ_IF_MAYBE(symbol, e.getEnumerant()) {
output.setString(symbol->getProto().getName());
} else {
output.setNumber(e.getRaw());
}
break;
}
case schema::Type::STRUCT: {
auto structValue = input.as<capnp::DynamicStruct>();
auto nonUnionFields = structValue.getSchema().getNonUnionFields();
KJ_STACK_ARRAY(bool, hasField, nonUnionFields.size(), 32, 128);
uint fieldCount = 0;
for (auto i: kj::indices(nonUnionFields)) {
fieldCount += (hasField[i] = structValue.has(nonUnionFields[i]));
}
// We try to write the union field, if any, in proper order with the rest.
auto which = structValue.which();
bool unionFieldIsNull = false;
KJ_IF_MAYBE(field, which) {
// Even if the union field is null, if it is not the default field of the union then we
// have to print it anyway.
unionFieldIsNull = !structValue.has(*field);
if (field->getProto().getDiscriminantValue() != 0 || !unionFieldIsNull) {
++fieldCount;
} else {
which = nullptr;
}
}
auto object = output.initObject(fieldCount);
size_t pos = 0;
for (auto i: kj::indices(nonUnionFields)) {
auto field = nonUnionFields[i];
KJ_IF_MAYBE(unionField, which) {
if (unionField->getIndex() < field.getIndex()) {
auto outField = object[pos++];
outField.setName(unionField->getProto().getName());
if (unionFieldIsNull) {
outField.initValue().setNull();
} else {
encodeField(*unionField, structValue.get(*unionField), outField.initValue());
}
which = nullptr;
}
}
if (hasField[i]) {
auto outField = object[pos++];
outField.setName(field.getProto().getName());
encodeField(field, structValue.get(field), outField.initValue());
}
}
if (which != nullptr) {
// Union field not printed yet; must be last.
auto unionField = KJ_ASSERT_NONNULL(which);
auto outField = object[pos++];
outField.setName(unionField.getProto().getName());
if (unionFieldIsNull) {
outField.initValue().setNull();
} else {
encodeField(unionField, structValue.get(unionField), outField.initValue());
}
}
KJ_ASSERT(pos == fieldCount);
break;
}
case schema::Type::INTERFACE:
KJ_FAIL_REQUIRE("don't know how to JSON-encode capabilities; "
"please register a JsonCodec::Handler for this");
case schema::Type::ANY_POINTER:
KJ_FAIL_REQUIRE("don't know how to JSON-encode AnyPointer; "
"please register a JsonCodec::Handler for this");
}
}
void JsonCodec::encodeField(StructSchema::Field field, DynamicValue::Reader input,
JsonValue::Builder output) const {
auto iter = impl->fieldHandlers.find(field);
if (iter != impl->fieldHandlers.end()) {
iter->second->encodeBase(*this, input, output);
return;
}
encode(input, field.getType(), output);
}
void JsonCodec::decode(JsonValue::Reader input, DynamicStruct::Builder output) const {
KJ_FAIL_ASSERT("JSON decode not implement yet. :(");
}
Orphan<DynamicValue> JsonCodec::decode(
JsonValue::Reader input, Type type, Orphanage orphanage) const {
KJ_FAIL_ASSERT("JSON decode not implement yet. :(");
}
// -----------------------------------------------------------------------------
Orphan<DynamicValue> JsonCodec::HandlerBase::decodeBase(
const JsonCodec& codec, JsonValue::Reader input, Orphanage orphanage) const {
KJ_FAIL_ASSERT("JSON decoder handler type / value type mismatch");
}
void JsonCodec::HandlerBase::decodeStructBase(
const JsonCodec& codec, JsonValue::Reader input, DynamicStruct::Builder output) const {
KJ_FAIL_ASSERT("JSON decoder handler type / value type mismatch");
}
void JsonCodec::addTypeHandlerImpl(Type type, HandlerBase& handler) {
impl->typeHandlers[type] = &handler;
}
void JsonCodec::addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler) {
KJ_REQUIRE(type == field.getType(),
"handler type did not match field type for addFieldHandler()");
impl->fieldHandlers[field] = &handler;
}
} // namespace capnp
# 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.
@0x8ef99297a43a5e34;
$import "/capnp/c++.capnp".namespace("capnp");
struct JsonValue {
union {
null @0 :Void;
boolean @1 :Bool;
number @2 :Float64;
string @3 :Text;
array @4 :List(JsonValue);
object @5 :List(Field);
# Standard JSON values.
call @6 :Call;
# Non-standard: A "function call", applying a named function (named by a single identifier)
# to a parameter list. Examples:
#
# BinData(0, "Zm9vCg==")
# ISODate("2015-04-15T08:44:50.218Z")
#
# Mongo DB users will recognize the above as exactly the syntax Mongo uses to represent BSON
# "binary" and "date" types in text, since JSON has no analog of these. This is basically the
# reason this extension exists. We do NOT recommend using `call` unless you specifically need
# to be compatible with some silly format that uses this syntax.
}
struct Field {
name @0 :Text;
value @1 :JsonValue;
}
struct Call {
function @0 :Text;
params @1 :List(JsonValue);
}
}
// 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.
#ifndef CAPNP_COMPAT_JSON_H_
#define CAPNP_COMPAT_JSON_H_
#include <capnp/schema.h>
#include <capnp/dynamic.h>
#include <capnp/compat/json.capnp.h>
namespace capnp {
class JsonCodec {
// Flexible class for encoding Cap'n Proto types as JSON, and decoding JSON back to Cap'n Proto.
//
// Typical usage:
//
// JsonCodec json;
//
// // encode
// kj::String encoded = json.encode(someStructReader);
//
// // decode
// json.decode(encoded, someStructBuilder);
//
// Advanced users can do fancy things like override the way certain types or fields are
// represented in JSON by registering handlers. See the unit test for an example.
//
// Notes:
// - When encoding, all primitive fields are always encoded, even if default-valued. Pointer
// fields are only encoded if they are non-null.
// - 64-bit integers are encoded as strings, since JSON "numbers" are double-precision floating
// points which cannot store a 64-bit integer without losing data.
// - Data is encoded as an array of numbers in the range [0,255]. You probably want to register
// a handler that does something better, like maybe base64 encoding, but there are a zillion
// different ways people do this.
// - Encoding/decoding capabilities and AnyPointers requires registering a Handler, since there's
// no obvious default behavior.
// - When decoding, unrecognized field names are ignored. Note: This means that JSON is NOT a
// good format for receiving input from a human. Consider `capnp eval` or the SchemaParser
// library for human input.
public:
JsonCodec();
~JsonCodec() noexcept(false);
// ---------------------------------------------------------------------------
// standard API
void setPrettyPrint(bool enabled);
// Enable to insert newlines, indentation, and other extra spacing into the output. The default
// is to use minimal whitespace.
template <typename T>
kj::String encode(T&& value);
// Encode any Cap'n Proto value to JSON, including primitives and
// Dynamic{Enum,Struct,List,Capability}, but not DynamicValue (see below).
kj::String encode(DynamicValue::Reader value, Type type) const;
// Encode a DynamicValue to JSON. `type` is needed because `DynamicValue` itself does
// not distinguish between e.g. int32 and int64, which in JSON are handled differently. Most
// of the time, though, you can use the single-argument templated version of `encode()` instead.
void decode(kj::ArrayPtr<const char> input, DynamicStruct::Builder output) const;
// Decode JSON text directly into a struct builder. This only works for structs since lists
// need to be allocated with the correct size in advance.
//
// (Remember that any Cap'n Proto struct reader type can be implicitly cast to
// DynamicStruct::Reader.)
template <typename T>
Orphan<T> decode(kj::ArrayPtr<const char> input, Orphanage orphanage) const;
// Decode JSON text to any Cap'n Proto object (pointer value), allocated using the given
// orphanage. T must be specified explicitly and cannot be dynamic, e.g.:
//
// Orphan<MyType> orphan = json.decode<MyType>(text, orphanage);
template <typename T>
ReaderFor<T> decode(kj::ArrayPtr<const char> input) const;
// Decode JSON text into a primitive or capability value. T must be specified explicitly and
// cannot be dynamic, e.g.:
//
// uint32_t n = json.decode<uint32_t>(text);
Orphan<DynamicValue> decode(kj::ArrayPtr<const char> input, Type type, Orphanage orphanage) const;
Orphan<DynamicList> decode(
kj::ArrayPtr<const char> input, ListSchema type, Orphanage orphanage) const;
Orphan<DynamicStruct> decode(
kj::ArrayPtr<const char> input, StructSchema type, Orphanage orphanage) const;
DynamicCapability::Client decode(kj::ArrayPtr<const char> input, InterfaceSchema type) const;
DynamicEnum decode(kj::ArrayPtr<const char> input, EnumSchema type) const;
// Decode to a dynamic value, specifying the type schema.
// ---------------------------------------------------------------------------
// layered API
//
// You can separate text <-> JsonValue from JsonValue <-> T. These are particularly useful
// for calling from Handler implementations.
kj::String encodeRaw(JsonValue::Reader value) const;
void decodeRaw(kj::ArrayPtr<const char> input, JsonValue::Builder output) const;
// Translate JsonValue <-> text.
template <typename T>
void encode(T&& value, JsonValue::Builder output);
void encode(DynamicValue::Reader input, Type type, JsonValue::Builder output) const;
void decode(JsonValue::Reader input, DynamicStruct::Builder output) const;
template <typename T>
Orphan<T> decode(JsonValue::Reader input, Orphanage orphanage) const;
template <typename T>
ReaderFor<T> decode(JsonValue::Reader input) const;
Orphan<DynamicValue> decode(JsonValue::Reader input, Type type, Orphanage orphanage) const;
Orphan<DynamicList> decode(JsonValue::Reader input, ListSchema type, Orphanage orphanage) const;
Orphan<DynamicStruct> decode(
JsonValue::Reader input, StructSchema type, Orphanage orphanage) const;
DynamicCapability::Client decode(JsonValue::Reader input, InterfaceSchema type) const;
DynamicEnum decode(JsonValue::Reader input, EnumSchema type) const;
// ---------------------------------------------------------------------------
// specializing particular types
template <typename T, Style s = style<T>()>
class Handler;
// Implement this interface to specify a special encoding for a particular type or field.
//
// The templates are a bit ugly, but subclasses of this type essentially implement two methods,
// one to encode values of this type and one to decode values of this type. `encode()` is simple:
//
// void encode(const JsonCodec& codec, ReaderFor<T> input, JsonValue::Builder output) const;
//
// `decode()` is a bit trickier. When T is a struct (including DynamicStruct), it is:
//
// void decode(const JsonCodec& codec, JsonValue::Reader input, BuilderFor<T> output) const;
//
// However, when T is a primitive, decode() is:
//
// T decode(const JsonCodec& codec, JsonValue::Reader input) const;
//
// Or when T is any non-struct object (list, blob), decode() is:
//
// Orphan<T> decode(const JsonCodec& codec, JsonValue::Reader input, Orphanage orphanage) const;
//
// Or when T is an interface:
//
// T::Client decode(const JsonCodec& codec, JsonValue::Reader input) const;
//
// Additionally, when T is a struct you can *optionally* also implement the orphan-returning form
// of decode(), but it will only be called when the struct would be allocated as an individual
// object, not as part of a list. This allows you to return "nullptr" in these cases to say that
// the pointer value should be null. This does not apply to list elements because struct list
// elements cannot ever be null (since Cap'n Proto encodes struct lists as a flat list rather
// than list-of-pointers).
template <typename T>
void addTypeHandler(Handler<T>& handler);
void addTypeHandler(Type type, Handler<DynamicValue>& handler);
void addTypeHandler(EnumSchema type, Handler<DynamicEnum>& handler);
void addTypeHandler(StructSchema type, Handler<DynamicStruct>& handler);
void addTypeHandler(ListSchema type, Handler<DynamicList>& handler);
void addTypeHandler(InterfaceSchema type, Handler<DynamicCapability>& handler);
// Arrange that whenever the type T appears in the message, your handler will be used to
// encode/decode it.
//
// Note that if you register a handler for a capability type, it will also apply to subtypes.
// Thus Handler<Capability> handles all capabilities.
template <typename T>
void addFieldHandler(StructSchema::Field field, Handler<T>& handler);
// Matches only the specific field. T can be a dynamic type. T must match the field's type.
private:
class HandlerBase;
struct Impl;
kj::Own<Impl> impl;
void encodeField(StructSchema::Field field, DynamicValue::Reader input,
JsonValue::Builder output) const;
void addTypeHandlerImpl(Type type, HandlerBase& handler);
void addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler);
};
// =======================================================================================
// inline implementation details
template <typename T>
kj::String JsonCodec::encode(T&& value) {
typedef FromAny<kj::Decay<T>> Base;
return encode(DynamicValue::Reader(ReaderFor<Base>(kj::fwd<T>(value))), Type::from<Base>());
}
template <typename T>
inline Orphan<T> JsonCodec::decode(kj::ArrayPtr<const char> input, Orphanage orphanage) const {
return decode(input, Type::from<T>(), orphanage).template releaseAs<T>();
}
template <typename T>
inline ReaderFor<T> JsonCodec::decode(kj::ArrayPtr<const char> input) const {
static_assert(style<T>() == Style::PRIMITIVE || style<T>() == Style::CAPABILITY,
"must specify an orphanage to decode an object type");
return decode(input, Type::from<T>(), Orphanage()).getReader().template as<T>();
}
inline Orphan<DynamicList> JsonCodec::decode(
kj::ArrayPtr<const char> input, ListSchema type, Orphanage orphanage) const {
return decode(input, Type(type), orphanage).releaseAs<DynamicList>();
}
inline Orphan<DynamicStruct> JsonCodec::decode(
kj::ArrayPtr<const char> input, StructSchema type, Orphanage orphanage) const {
return decode(input, Type(type), orphanage).releaseAs<DynamicStruct>();
}
inline DynamicCapability::Client JsonCodec::decode(
kj::ArrayPtr<const char> input, InterfaceSchema type) const {
return decode(input, Type(type), Orphanage()).getReader().as<DynamicCapability>();
}
inline DynamicEnum JsonCodec::decode(kj::ArrayPtr<const char> input, EnumSchema type) const {
return decode(input, Type(type), Orphanage()).getReader().as<DynamicEnum>();
}
// -----------------------------------------------------------------------------
template <typename T>
void JsonCodec::encode(T&& value, JsonValue::Builder output) {
typedef FromAny<kj::Decay<T>> Base;
encode(DynamicValue::Reader(ReaderFor<Base>(kj::fwd<T>(value))), Type::from<Base>(), output);
}
template <typename T>
inline Orphan<T> JsonCodec::decode(JsonValue::Reader input, Orphanage orphanage) const {
return decode(input, Type::from<T>(), orphanage).template releaseAs<T>();
}
template <typename T>
inline ReaderFor<T> JsonCodec::decode(JsonValue::Reader input) const {
static_assert(style<T>() == Style::PRIMITIVE || style<T>() == Style::CAPABILITY,
"must specify an orphanage to decode an object type");
return decode(input, Type::from<T>(), Orphanage()).getReader().template as<T>();
}
inline Orphan<DynamicList> JsonCodec::decode(
JsonValue::Reader input, ListSchema type, Orphanage orphanage) const {
return decode(input, Type(type), orphanage).releaseAs<DynamicList>();
}
inline Orphan<DynamicStruct> JsonCodec::decode(
JsonValue::Reader input, StructSchema type, Orphanage orphanage) const {
return decode(input, Type(type), orphanage).releaseAs<DynamicStruct>();
}
inline DynamicCapability::Client JsonCodec::decode(
JsonValue::Reader input, InterfaceSchema type) const {
return decode(input, Type(type), Orphanage()).getReader().as<DynamicCapability>();
}
inline DynamicEnum JsonCodec::decode(JsonValue::Reader input, EnumSchema type) const {
return decode(input, Type(type), Orphanage()).getReader().as<DynamicEnum>();
}
// -----------------------------------------------------------------------------
class JsonCodec::HandlerBase {
// Internal helper; ignore.
public:
virtual void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
JsonValue::Builder output) const = 0;
virtual Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const;
virtual void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const;
};
template <typename T>
class JsonCodec::Handler<T, Style::POINTER>: private JsonCodec::HandlerBase {
public:
virtual void encode(const JsonCodec& codec, ReaderFor<T> input,
JsonValue::Builder output) const = 0;
virtual Orphan<T> decode(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const = 0;
private:
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
JsonValue::Builder output) const override final {
encode(codec, input.as<T>(), output);
}
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const override final {
return decode(codec, input, orphanage);
}
friend class JsonCodec;
};
template <typename T>
class JsonCodec::Handler<T, Style::STRUCT>: private JsonCodec::HandlerBase {
public:
virtual void encode(const JsonCodec& codec, ReaderFor<T> input,
JsonValue::Builder output) const = 0;
virtual void decode(const JsonCodec& codec, JsonValue::Reader input,
BuilderFor<T> output) const = 0;
virtual Orphan<T> decode(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const {
// If subclass does not override, fall back to regular version.
auto result = orphanage.newOrphan<T>();
decode(codec, input, result.get());
return result;
}
private:
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
JsonValue::Builder output) const override final {
encode(codec, input.as<T>(), output);
}
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const override final {
return decode(codec, input, orphanage);
}
void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const override final {
decode(codec, input, output);
}
friend class JsonCodec;
};
template <typename T>
class JsonCodec::Handler<T, Style::PRIMITIVE>: private JsonCodec::HandlerBase {
public:
virtual void encode(const JsonCodec& codec, T input, JsonValue::Builder output) const = 0;
virtual T decode(const JsonCodec& codec, JsonValue::Reader input) const = 0;
private:
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
JsonValue::Builder output) const override final {
encode(codec, input.as<T>(), output);
}
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const override final {
return decode(codec, input);
}
friend class JsonCodec;
};
template <typename T>
class JsonCodec::Handler<T, Style::CAPABILITY>: private JsonCodec::HandlerBase {
public:
virtual void encode(const JsonCodec& codec, typename T::Client input,
JsonValue::Builder output) const = 0;
virtual typename T::Client decode(const JsonCodec& codec, JsonValue::Reader input) const = 0;
private:
void encodeBase(const JsonCodec& codec, DynamicValue::Reader input,
JsonValue::Builder output) const override final {
encode(codec, input.as<T>(), output);
}
Orphan<DynamicValue> decodeBase(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const override final {
return decode(codec, input);
}
friend class JsonCodec;
};
template <typename T>
inline void JsonCodec::addTypeHandler(Handler<T>& handler) {
addTypeHandlerImpl(Type::from<T>(), handler);
}
inline void JsonCodec::addTypeHandler(Type type, Handler<DynamicValue>& handler) {
addTypeHandlerImpl(type, handler);
}
inline void JsonCodec::addTypeHandler(EnumSchema type, Handler<DynamicEnum>& handler) {
addTypeHandlerImpl(type, handler);
}
inline void JsonCodec::addTypeHandler(StructSchema type, Handler<DynamicStruct>& handler) {
addTypeHandlerImpl(type, handler);
}
inline void JsonCodec::addTypeHandler(ListSchema type, Handler<DynamicList>& handler) {
addTypeHandlerImpl(type, handler);
}
inline void JsonCodec::addTypeHandler(InterfaceSchema type, Handler<DynamicCapability>& handler) {
addTypeHandlerImpl(type, handler);
}
template <typename T>
inline void JsonCodec::addFieldHandler(StructSchema::Field field, Handler<T>& handler) {
addFieldHandlerImpl(field, Type::from<T>(), handler);
}
} // namespace capnp
#endif // CAPNP_COMPAT_JSON_H_
......@@ -419,6 +419,11 @@ private:
}
}
if (result.size() == 4 && memcmp(result.begin(), "NULL", 4) == 0) {
// NULL probably collides with a macro.
result.add('_');
}
result.add('\0');
return kj::String(result.releaseAsArray());
......
......@@ -122,6 +122,12 @@ template <> struct Kind_<DynamicCapability> { static constexpr Kind kind = Kind:
} // namespace _ (private)
template <> inline constexpr Style style<DynamicValue >() { return Style::POINTER; }
template <> inline constexpr Style style<DynamicEnum >() { return Style::PRIMITIVE; }
template <> inline constexpr Style style<DynamicStruct >() { return Style::STRUCT; }
template <> inline constexpr Style style<DynamicList >() { return Style::POINTER; }
template <> inline constexpr Style style<DynamicCapability>() { return Style::CAPABILITY; }
// -------------------------------------------------------------------
class DynamicEnum {
......
......@@ -858,7 +858,7 @@ bool Type::operator==(const Type& other) const {
KJ_UNREACHABLE;
case schema::Type::ANY_POINTER:
return scopeId == other.scopeId &&
return scopeId == other.scopeId && isImplicitParam == other.isImplicitParam &&
// Trying to comply with strict aliasing rules. Hopefully the compiler realizes that
// both branches compile to the same instructions and can optimize it away.
(scopeId != 0 || isImplicitParam ? paramIndex == other.paramIndex
......@@ -868,6 +868,44 @@ bool Type::operator==(const Type& other) const {
KJ_UNREACHABLE;
}
size_t Type::hashCode() const {
switch (baseType) {
case schema::Type::VOID:
case schema::Type::BOOL:
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
case schema::Type::TEXT:
case schema::Type::DATA:
return (static_cast<size_t>(baseType) << 3) + listDepth;
case schema::Type::STRUCT:
case schema::Type::ENUM:
case schema::Type::INTERFACE:
return reinterpret_cast<size_t>(schema) + listDepth;
case schema::Type::LIST:
KJ_UNREACHABLE;
case schema::Type::ANY_POINTER: {
// Trying to comply with strict aliasing rules. Hopefully the compiler realizes that
// both branches compile to the same instructions and can optimize it away.
size_t val = scopeId != 0 || isImplicitParam ?
paramIndex : static_cast<uint16_t>(anyPointerKind);
return (val << 1 | isImplicitParam) ^ scopeId;
}
}
KJ_UNREACHABLE;
}
void Type::requireUsableAs(Type expected) const {
KJ_REQUIRE(baseType == expected.baseType && listDepth == expected.listDepth,
"This type is not compatible with the requested native type.");
......
......@@ -642,6 +642,8 @@ public:
bool operator==(const Type& other) const;
inline bool operator!=(const Type& other) const { return !(*this == other); }
size_t hashCode() const;
inline Type wrapInList(uint depth = 1) const;
// Return the Type formed by wrapping this type in List() `depth` times.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment