blob: 6f1bd6eaada1d8c90a94dd74b9644a3a2cc57c5b [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/orphan.h>
#include <kj/debug.h>
#include <kj/function.h>
#include <kj/vector.h>
#include <kj/one-of.h>
#include <kj/encoding.h>
#include <kj/map.h>
namespace capnp {
struct JsonCodec::Impl {
bool prettyPrint = false;
HasMode hasMode = HasMode::NON_NULL;
size_t maxNestingDepth = 64;
bool rejectUnknownFields = false;
kj::HashMap<Type, HandlerBase*> typeHandlers;
kj::HashMap<StructSchema::Field, HandlerBase*> fieldHandlers;
kj::HashMap<Type, kj::Maybe<kj::Own<AnnotatedHandler>>> annotatedHandlers;
kj::HashMap<Type, kj::Own<AnnotatedEnumHandler>> annotatedEnumHandlers;
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 '\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 (static_cast<uint8_t>(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; }
void JsonCodec::setMaxNestingDepth(size_t maxNestingDepth) {
impl->maxNestingDepth = maxNestingDepth;
}
void JsonCodec::setHasMode(HasMode mode) { impl->hasMode = mode; }
void JsonCodec::setRejectUnknownFields(bool enabled) { impl->rejectUnknownFields = 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::encode(DynamicValue::Reader input, Type type, JsonValue::Builder output) const {
// TODO(someday): For interfaces, check for handlers on superclasses, per documentation...
// TODO(someday): For branded types, should we check for handlers on the generic?
// TODO(someday): Allow registering handlers for "all structs", "all lists", etc?
KJ_IF_MAYBE(handler, impl->typeHandlers.find(type)) {
(*handler)->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:
output.setNumber(input.as<double>());
break;
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
{
double value = input.as<double>();
// Inf, -inf and NaN are not allowed in the JSON spec. Storing into string.
if (kj::inf() == value) {
output.setString("Infinity");
} else if (-kj::inf() == value) {
output.setString("-Infinity");
} else if (kj::isNaN(value)) {
output.setString("NaN");
} else {
output.setNumber(value);
}
}
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], impl->hasMode));
}
// 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, impl->hasMode);
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 {
KJ_IF_MAYBE(handler, impl->fieldHandlers.find(field)) {
(*handler)->encodeBase(*this, input, output);
return;
}
encode(input, field.getType(), output);
}
Orphan<DynamicList> JsonCodec::decodeArray(List<JsonValue>::Reader input, ListSchema type, Orphanage orphanage) const {
auto orphan = orphanage.newOrphan(type, input.size());
auto output = orphan.get();
for (auto i: kj::indices(input)) {
output.adopt(i, decode(input[i], type.getElementType(), orphanage));
}
return orphan;
}
void JsonCodec::decodeObject(JsonValue::Reader input, StructSchema type, Orphanage orphanage, DynamicStruct::Builder output) const {
KJ_REQUIRE(input.isObject(), "Expected object value") { return; }
for (auto field: input.getObject()) {
KJ_IF_MAYBE(fieldSchema, type.findFieldByName(field.getName())) {
decodeField(*fieldSchema, field.getValue(), orphanage, output);
} else {
KJ_REQUIRE(!impl->rejectUnknownFields, "Unknown field", field.getName());
}
}
}
void JsonCodec::decodeField(StructSchema::Field fieldSchema, JsonValue::Reader fieldValue,
Orphanage orphanage, DynamicStruct::Builder output) const {
auto fieldType = fieldSchema.getType();
KJ_IF_MAYBE(handler, impl->fieldHandlers.find(fieldSchema)) {
output.adopt(fieldSchema, (*handler)->decodeBase(*this, fieldValue, fieldType, orphanage));
} else {
output.adopt(fieldSchema, decode(fieldValue, fieldType, orphanage));
}
}
void JsonCodec::decode(JsonValue::Reader input, DynamicStruct::Builder output) const {
auto type = output.getSchema();
KJ_IF_MAYBE(handler, impl->typeHandlers.find(type)) {
return (*handler)->decodeStructBase(*this, input, output);
}
decodeObject(input, type, Orphanage::getForMessageContaining(output), output);
}
Orphan<DynamicValue> JsonCodec::decode(
JsonValue::Reader input, Type type, Orphanage orphanage) const {
KJ_IF_MAYBE(handler, impl->typeHandlers.find(type)) {
return (*handler)->decodeBase(*this, input, type, orphanage);
}
switch(type.which()) {
case schema::Type::VOID:
return capnp::VOID;
case schema::Type::BOOL:
switch (input.which()) {
case JsonValue::BOOLEAN:
return input.getBoolean();
default:
KJ_FAIL_REQUIRE("Expected boolean value");
}
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
// Relies on range check in DynamicValue::Reader::as<IntType>
switch (input.which()) {
case JsonValue::NUMBER:
return input.getNumber();
case JsonValue::STRING:
return input.getString().parseAs<int64_t>();
default:
KJ_FAIL_REQUIRE("Expected integer value");
}
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::UINT64:
// Relies on range check in DynamicValue::Reader::as<IntType>
switch (input.which()) {
case JsonValue::NUMBER:
return input.getNumber();
case JsonValue::STRING:
return input.getString().parseAs<uint64_t>();
default:
KJ_FAIL_REQUIRE("Expected integer value");
}
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
switch (input.which()) {
case JsonValue::NULL_:
return kj::nan();
case JsonValue::NUMBER:
return input.getNumber();
case JsonValue::STRING:
return input.getString().parseAs<double>();
default:
KJ_FAIL_REQUIRE("Expected float value");
}
case schema::Type::TEXT:
switch (input.which()) {
case JsonValue::STRING:
return orphanage.newOrphanCopy(input.getString());
default:
KJ_FAIL_REQUIRE("Expected text value");
}
case schema::Type::DATA:
switch (input.which()) {
case JsonValue::ARRAY: {
auto array = input.getArray();
auto orphan = orphanage.newOrphan<Data>(array.size());
auto data = orphan.get();
for (auto i: kj::indices(array)) {
auto x = array[i].getNumber();
KJ_REQUIRE(byte(x) == x, "Number in byte array is not an integer in [0, 255]");
data[i] = x;
}
return kj::mv(orphan);
}
default:
KJ_FAIL_REQUIRE("Expected data value");
}
case schema::Type::LIST:
switch (input.which()) {
case JsonValue::ARRAY:
return decodeArray(input.getArray(), type.asList(), orphanage);
default:
KJ_FAIL_REQUIRE("Expected list value") { break; }
return orphanage.newOrphan(type.asList(), 0);
}
case schema::Type::ENUM:
switch (input.which()) {
case JsonValue::STRING:
return DynamicEnum(type.asEnum().getEnumerantByName(input.getString()));
default:
KJ_FAIL_REQUIRE("Expected enum value") { break; }
return DynamicEnum(type.asEnum(), 0);
}
case schema::Type::STRUCT: {
auto structType = type.asStruct();
auto orphan = orphanage.newOrphan(structType);
decodeObject(input, structType, orphanage, orphan.get());
return kj::mv(orphan);
}
case schema::Type::INTERFACE:
KJ_FAIL_REQUIRE("don't know how to JSON-decode capabilities; "
"please register a JsonCodec::Handler for this");
case schema::Type::ANY_POINTER:
KJ_FAIL_REQUIRE("don't know how to JSON-decode AnyPointer; "
"please register a JsonCodec::Handler for this");
}
KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
}
// -----------------------------------------------------------------------------
namespace {
class Input {
public:
Input(kj::ArrayPtr<const char> input) : wrapped(input) {}
bool exhausted() {
return wrapped.size() == 0 || wrapped.front() == '\0';
}
char nextChar() {
KJ_REQUIRE(!exhausted(), "JSON message ends prematurely.");
return wrapped.front();
}
void advance(size_t numBytes = 1) {
KJ_REQUIRE(numBytes <= wrapped.size(), "JSON message ends prematurely.");
wrapped = kj::arrayPtr(wrapped.begin() + numBytes, wrapped.end());
}
void advanceTo(const char *newPos) {
KJ_REQUIRE(wrapped.begin() <= newPos && newPos < wrapped.end(),
"JSON message ends prematurely.");
wrapped = kj::arrayPtr(newPos, wrapped.end());
}
kj::ArrayPtr<const char> consume(size_t numBytes = 1) {
auto originalPos = wrapped.begin();
advance(numBytes);
return kj::arrayPtr(originalPos, wrapped.begin());
}
void consume(char expected) {
char current = nextChar();
KJ_REQUIRE(current == expected, "Unexpected input in JSON message.");
advance();
}
void consume(kj::ArrayPtr<const char> expected) {
KJ_REQUIRE(wrapped.size() >= expected.size());
auto prefix = wrapped.slice(0, expected.size());
KJ_REQUIRE(prefix == expected, "Unexpected input in JSON message.");
advance(expected.size());
}
bool tryConsume(char expected) {
bool found = !exhausted() && nextChar() == expected;
if (found) { advance(); }
return found;
}
template <typename Predicate>
void consumeOne(Predicate&& predicate) {
char current = nextChar();
KJ_REQUIRE(predicate(current), "Unexpected input in JSON message.");
advance();
}
template <typename Predicate>
kj::ArrayPtr<const char> consumeWhile(Predicate&& predicate) {
auto originalPos = wrapped.begin();
while (!exhausted() && predicate(nextChar())) { advance(); }
return kj::arrayPtr(originalPos, wrapped.begin());
}
template <typename F> // Function<void(Input&)>
kj::ArrayPtr<const char> consumeCustom(F&& f) {
// Allows consuming in a custom manner without exposing the wrapped ArrayPtr.
auto originalPos = wrapped.begin();
f(*this);
return kj::arrayPtr(originalPos, wrapped.begin());
}
void consumeWhitespace() {
consumeWhile([](char chr) {
return (
chr == ' ' ||
chr == '\n' ||
chr == '\r' ||
chr == '\t'
);
});
}
private:
kj::ArrayPtr<const char> wrapped;
}; // class Input
class Parser {
public:
Parser(size_t maxNestingDepth, kj::ArrayPtr<const char> input) :
maxNestingDepth(maxNestingDepth), input(input), nestingDepth(0) {}
void parseValue(JsonValue::Builder& output) {
input.consumeWhitespace();
KJ_DEFER(input.consumeWhitespace());
KJ_REQUIRE(!input.exhausted(), "JSON message ends prematurely.");
switch (input.nextChar()) {
case 'n': input.consume(kj::StringPtr("null")); output.setNull(); break;
case 'f': input.consume(kj::StringPtr("false")); output.setBoolean(false); break;
case 't': input.consume(kj::StringPtr("true")); output.setBoolean(true); break;
case '"': parseString(output); break;
case '[': parseArray(output); break;
case '{': parseObject(output); break;
case '-': case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7': case '8':
case '9': parseNumber(output); break;
default: KJ_FAIL_REQUIRE("Unexpected input in JSON message.");
}
}
void parseNumber(JsonValue::Builder& output) {
output.setNumber(consumeNumber().parseAs<double>());
}
void parseString(JsonValue::Builder& output) {
output.setString(consumeQuotedString());
}
void parseArray(JsonValue::Builder& output) {
// TODO(perf): Using orphans leaves holes in the message. It's expected
// that a JsonValue is used for interop, and won't be sent or written as a
// Cap'n Proto message. This also applies to parseObject below.
kj::Vector<Orphan<JsonValue>> values;
auto orphanage = Orphanage::getForMessageContaining(output);
bool expectComma = false;
input.consume('[');
KJ_REQUIRE(++nestingDepth <= maxNestingDepth, "JSON message nested too deeply.");
KJ_DEFER(--nestingDepth);
while (input.consumeWhitespace(), input.nextChar() != ']') {
auto orphan = orphanage.newOrphan<JsonValue>();
auto builder = orphan.get();
if (expectComma) {
input.consumeWhitespace();
input.consume(',');
input.consumeWhitespace();
}
parseValue(builder);
values.add(kj::mv(orphan));
expectComma = true;
}
output.initArray(values.size());
auto array = output.getArray();
for (auto i : kj::indices(values)) {
array.adoptWithCaveats(i, kj::mv(values[i]));
}
input.consume(']');
}
void parseObject(JsonValue::Builder& output) {
kj::Vector<Orphan<JsonValue::Field>> fields;
auto orphanage = Orphanage::getForMessageContaining(output);
bool expectComma = false;
input.consume('{');
KJ_REQUIRE(++nestingDepth <= maxNestingDepth, "JSON message nested too deeply.");
KJ_DEFER(--nestingDepth);
while (input.consumeWhitespace(), input.nextChar() != '}') {
auto orphan = orphanage.newOrphan<JsonValue::Field>();
auto builder = orphan.get();
if (expectComma) {
input.consumeWhitespace();
input.consume(',');
input.consumeWhitespace();
}
builder.setName(consumeQuotedString());
input.consumeWhitespace();
input.consume(':');
input.consumeWhitespace();
auto valueBuilder = builder.getValue();
parseValue(valueBuilder);
fields.add(kj::mv(orphan));
expectComma = true;
}
output.initObject(fields.size());
auto object = output.getObject();
for (auto i : kj::indices(fields)) {
object.adoptWithCaveats(i, kj::mv(fields[i]));
}
input.consume('}');
}
bool inputExhausted() { return input.exhausted(); }
private:
kj::String consumeQuotedString() {
input.consume('"');
// TODO(perf): Avoid copy / alloc if no escapes encountered.
// TODO(perf): Get statistics on string size and preallocate?
kj::Vector<char> decoded;
do {
auto stringValue = input.consumeWhile([](const char chr) {
return chr != '"' && chr != '\\';
});
decoded.addAll(stringValue);
if (input.nextChar() == '\\') { // handle escapes.
input.advance();
switch(input.nextChar()) {
case '"' : decoded.add('"' ); input.advance(); break;
case '\\': decoded.add('\\'); input.advance(); break;
case '/' : decoded.add('/' ); input.advance(); break;
case 'b' : decoded.add('\b'); input.advance(); break;
case 'f' : decoded.add('\f'); input.advance(); break;
case 'n' : decoded.add('\n'); input.advance(); break;
case 'r' : decoded.add('\r'); input.advance(); break;
case 't' : decoded.add('\t'); input.advance(); break;
case 'u' :
input.consume('u');
unescapeAndAppend(input.consume(size_t(4)), decoded);
break;
default: KJ_FAIL_REQUIRE("Invalid escape in JSON string."); break;
}
}
} while(input.nextChar() != '"');
input.consume('"');
decoded.add('\0');
// TODO(perf): This copy can be eliminated, but I can't find the kj::wayToDoIt();
return kj::String(decoded.releaseAsArray());
}
kj::String consumeNumber() {
auto numArrayPtr = input.consumeCustom([](Input& input) {
input.tryConsume('-');
if (!input.tryConsume('0')) {
input.consumeOne([](char c) { return '1' <= c && c <= '9'; });
input.consumeWhile([](char c) { return '0' <= c && c <= '9'; });
}
if (input.tryConsume('.')) {
input.consumeWhile([](char c) { return '0' <= c && c <= '9'; });
}
if (input.tryConsume('e') || input.tryConsume('E')) {
input.tryConsume('+') || input.tryConsume('-');
input.consumeWhile([](char c) { return '0' <= c && c <= '9'; });
}
});
KJ_REQUIRE(numArrayPtr.size() > 0, "Expected number in JSON input.");
kj::Vector<char> number;
number.addAll(numArrayPtr);
number.add('\0');
return kj::String(number.releaseAsArray());
}
// TODO(someday): This "interface" is ugly, and won't work if/when surrogates are handled.
void unescapeAndAppend(kj::ArrayPtr<const char> hex, kj::Vector<char>& target) {
KJ_REQUIRE(hex.size() == 4);
int codePoint = 0;
for (int i = 0; i < 4; ++i) {
char c = hex[i];
codePoint <<= 4;
if ('0' <= c && c <= '9') {
codePoint |= c - '0';
} else if ('a' <= c && c <= 'f') {
codePoint |= c - 'a';
} else if ('A' <= c && c <= 'F') {
codePoint |= c - 'A';
} else {
KJ_FAIL_REQUIRE("Invalid hex digit in unicode escape.", c);
}
}
if (codePoint < 128) {
target.add(0x7f & static_cast<char>(codePoint));
} else {
// TODO(perf): This is sorta malloc-heavy...
char16_t u = codePoint;
target.addAll(kj::decodeUtf16(kj::arrayPtr(&u, 1)));
}
}
const size_t maxNestingDepth;
Input input;
size_t nestingDepth;
}; // class Parser
} // namespace
void JsonCodec::decodeRaw(kj::ArrayPtr<const char> input, JsonValue::Builder output) const {
Parser parser(impl->maxNestingDepth, input);
parser.parseValue(output);
KJ_REQUIRE(parser.inputExhausted(), "Input remains after parsing JSON.");
}
// -----------------------------------------------------------------------------
Orphan<DynamicValue> JsonCodec::HandlerBase::decodeBase(
const JsonCodec& codec, JsonValue::Reader input, Type type, 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.upsert(type, &handler, [](HandlerBase*& existing, HandlerBase* replacement) {
KJ_REQUIRE(existing == replacement, "type already has a different registered 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.upsert(field, &handler, [](HandlerBase*& existing, HandlerBase* replacement) {
KJ_REQUIRE(existing == replacement, "field already has a different registered handler");
});
}
// =======================================================================================
static constexpr uint64_t JSON_NAME_ANNOTATION_ID = 0xfa5b1fd61c2e7c3dull;
static constexpr uint64_t JSON_FLATTEN_ANNOTATION_ID = 0x82d3e852af0336bfull;
static constexpr uint64_t JSON_DISCRIMINATOR_ANNOTATION_ID = 0xcfa794e8d19a0162ull;
static constexpr uint64_t JSON_BASE64_ANNOTATION_ID = 0xd7d879450a253e4bull;
static constexpr uint64_t JSON_HEX_ANNOTATION_ID = 0xf061e22f0ae5c7b5ull;
class JsonCodec::Base64Handler final: public JsonCodec::Handler<capnp::Data> {
public:
void encode(const JsonCodec& codec, capnp::Data::Reader input, JsonValue::Builder output) const {
output.setString(kj::encodeBase64(input));
}
Orphan<capnp::Data> decode(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const {
return orphanage.newOrphanCopy(capnp::Data::Reader(kj::decodeBase64(input.getString())));
}
};
class JsonCodec::HexHandler final: public JsonCodec::Handler<capnp::Data> {
public:
void encode(const JsonCodec& codec, capnp::Data::Reader input, JsonValue::Builder output) const {
output.setString(kj::encodeHex(input));
}
Orphan<capnp::Data> decode(const JsonCodec& codec, JsonValue::Reader input,
Orphanage orphanage) const {
return orphanage.newOrphanCopy(capnp::Data::Reader(kj::decodeHex(input.getString())));
}
};
class JsonCodec::AnnotatedHandler final: public JsonCodec::Handler<DynamicStruct> {
public:
AnnotatedHandler(JsonCodec& codec, StructSchema schema,
kj::Maybe<json::DiscriminatorOptions::Reader> discriminator,
kj::Maybe<kj::StringPtr> unionDeclName,
kj::Vector<Schema>& dependencies)
: schema(schema) {
auto schemaProto = schema.getProto();
auto typeName = schemaProto.getDisplayName();
if (discriminator == nullptr) {
// There are two cases of unions:
// * Named unions, which are special cases of named groups. In this case, the union may be
// annotated by annotating the field. In this case, we receive a non-null `discriminator`
// as a constructor parameter, and schemaProto.getAnnotations() must be empty because
// it's not possible to annotate a group's type (because the type is anonymous).
// * Unnamed unions, of which there can only be one in any particular scope. In this case,
// the parent struct type itself is annotated.
// So if we received `null` as the constructor parameter, check for annotations on the struct
// type.
for (auto anno: schemaProto.getAnnotations()) {
switch (anno.getId()) {
case JSON_DISCRIMINATOR_ANNOTATION_ID:
discriminator = anno.getValue().getStruct().getAs<json::DiscriminatorOptions>();
break;
}
}
}
KJ_IF_MAYBE(d, discriminator) {
if (d->hasName()) {
unionTagName = d->getName();
} else {
unionTagName = unionDeclName;
}
KJ_IF_MAYBE(u, unionTagName) {
fieldsByName.insert(*u, FieldNameInfo {
FieldNameInfo::UNION_TAG, 0, 0, nullptr
});
}
if (d->hasValueName()) {
fieldsByName.insert(d->getValueName(), FieldNameInfo {
FieldNameInfo::UNION_VALUE, 0, 0, nullptr
});
}
}
discriminantOffset = schemaProto.getStruct().getDiscriminantOffset();
fields = KJ_MAP(field, schema.getFields()) {
auto fieldProto = field.getProto();
auto type = field.getType();
auto fieldName = fieldProto.getName();
FieldNameInfo nameInfo;
nameInfo.index = field.getIndex();
nameInfo.type = FieldNameInfo::NORMAL;
nameInfo.prefixLength = 0;
FieldInfo info;
info.name = fieldName;
kj::Maybe<json::DiscriminatorOptions::Reader> subDiscriminator;
bool flattened = false;
for (auto anno: field.getProto().getAnnotations()) {
switch (anno.getId()) {
case JSON_NAME_ANNOTATION_ID:
info.name = anno.getValue().getText();
break;
case JSON_FLATTEN_ANNOTATION_ID:
KJ_REQUIRE(type.isStruct(), "only struct types can be flattened", fieldName, typeName);
flattened = true;
info.prefix = anno.getValue().getStruct().getAs<json::FlattenOptions>().getPrefix();
break;
case JSON_DISCRIMINATOR_ANNOTATION_ID:
KJ_REQUIRE(fieldProto.isGroup(), "only unions can have discriminator");
subDiscriminator = anno.getValue().getStruct().getAs<json::DiscriminatorOptions>();
break;
case JSON_BASE64_ANNOTATION_ID: {
KJ_REQUIRE(field.getType().isData(), "only Data can be marked for base64 encoding");
static Base64Handler handler;
codec.addFieldHandler(field, handler);
break;
}
case JSON_HEX_ANNOTATION_ID: {
KJ_REQUIRE(field.getType().isData(), "only Data can be marked for hex encoding");
static HexHandler handler;
codec.addFieldHandler(field, handler);
break;
}
}
}
if (fieldProto.isGroup()) {
// Load group type handler now, even if not flattened, so that we can pass its
// `subDiscriminator`.
kj::Maybe<kj::StringPtr> subFieldName;
if (flattened) {
// If the group was flattened, then we allow its field name to be used as the
// discriminator name, so that the discriminator doesn't have to explicitly specify a
// name.
subFieldName = fieldName;
}
auto& subHandler = codec.loadAnnotatedHandler(
type.asStruct(), subDiscriminator, subFieldName, dependencies);
if (flattened) {
info.flattenHandler = subHandler;
}
} else if (type.isStruct()) {
if (flattened) {
info.flattenHandler = codec.loadAnnotatedHandler(
type.asStruct(), nullptr, nullptr, dependencies);
}
}
bool isUnionMember = fieldProto.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;
KJ_IF_MAYBE(fh, info.flattenHandler) {
// Set up fieldsByName for each of the child's fields.
for (auto& entry: fh->fieldsByName) {
kj::StringPtr flattenedName;
kj::String ownName;
if (info.prefix.size() > 0) {
ownName = kj::str(info.prefix, entry.key);
flattenedName = ownName;
} else {
flattenedName = entry.key;
}
fieldsByName.upsert(flattenedName, FieldNameInfo {
isUnionMember ? FieldNameInfo::FLATTENED_FROM_UNION : FieldNameInfo::FLATTENED,
field.getIndex(), (uint)info.prefix.size(), kj::mv(ownName)
}, [&](FieldNameInfo& existing, FieldNameInfo&& replacement) {
KJ_REQUIRE(existing.type == FieldNameInfo::FLATTENED_FROM_UNION &&
replacement.type == FieldNameInfo::FLATTENED_FROM_UNION,
"flattened members have the same name and are not mutually exclusive");
});
}
}
info.nameForDiscriminant = info.name;
if (!flattened) {
bool isUnionWithValueName = false;
if (isUnionMember) {
KJ_IF_MAYBE(d, discriminator) {
if (d->hasValueName()) {
info.name = d->getValueName();
isUnionWithValueName = true;
}
}
}
if (!isUnionWithValueName) {
fieldsByName.insert(info.name, kj::mv(nameInfo));
}
}
if (isUnionMember) {
unionTagValues.insert(info.nameForDiscriminant, field);
}
// Look for dependencies that we need to add.
while (type.isList()) type = type.asList().getElementType();
if (codec.impl->typeHandlers.find(type) == nullptr) {
switch (type.which()) {
case schema::Type::STRUCT:
dependencies.add(type.asStruct());
break;
case schema::Type::ENUM:
dependencies.add(type.asEnum());
break;
case schema::Type::INTERFACE:
dependencies.add(type.asInterface());
break;
default:
break;
}
}
return info;
};
}
const StructSchema schema;
void encode(const JsonCodec& codec, DynamicStruct::Reader input,
JsonValue::Builder output) const override {
kj::Vector<FlattenedField> flattenedFields;
gatherForEncode(codec, input, nullptr, nullptr, flattenedFields);
auto outs = output.initObject(flattenedFields.size());
for (auto i: kj::indices(flattenedFields)) {
auto& in = flattenedFields[i];
auto out = outs[i];
out.setName(in.name);
KJ_SWITCH_ONEOF(in.type) {
KJ_CASE_ONEOF(type, Type) {
codec.encode(in.value, type, out.initValue());
}
KJ_CASE_ONEOF(field, StructSchema::Field) {
codec.encodeField(field, in.value, out.initValue());
}
}
}
}
void decode(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const override {
KJ_REQUIRE(input.isObject());
kj::HashSet<const void*> unionsSeen;
kj::Vector<JsonValue::Field::Reader> retries;
for (auto field: input.getObject()) {
if (!decodeField(codec, field.getName(), field.getValue(), output, unionsSeen)) {
retries.add(field);
}
}
while (!retries.empty()) {
auto retriesCopy = kj::mv(retries);
KJ_ASSERT(retries.empty());
for (auto field: retriesCopy) {
if (!decodeField(codec, field.getName(), field.getValue(), output, unionsSeen)) {
retries.add(field);
}
}
if (retries.size() == retriesCopy.size()) {
// We made no progress in this iteration. Give up on the remaining fields.
break;
}
}
}
private:
struct FieldInfo {
kj::StringPtr name;
kj::StringPtr nameForDiscriminant;
kj::Maybe<const AnnotatedHandler&> flattenHandler;
kj::StringPtr prefix;
};
kj::Array<FieldInfo> fields;
// Maps field index -> info about the field
struct FieldNameInfo {
enum {
NORMAL,
// This is a normal field with the given `index`.
FLATTENED,
// This is a field of a flattened inner struct or group (that is not in a union). `index`
// is the field index of the particular struct/group field.
UNION_TAG,
// The parent struct is a flattened union, and this field is the discriminant tag. It is a
// string field whose name determines the union type. `index` is not used.
FLATTENED_FROM_UNION,
// The parent struct is a flattened union, and some of the union's members are flattened
// structs or groups, and this field is possibly a member of one or more of them. `index`
// is not used, because it's possible that the same field name appears in multiple variants.
// Instead, the parser must find the union tag, and then can descend and attempt to parse
// the field in the context of whichever variant is selected.
UNION_VALUE
// This field is the value of a discriminated union that has `valueName` set.
} type;
uint index;
// For `NORMAL` and `FLATTENED`, the index of the field in schema.getFields().
uint prefixLength;
kj::String ownName;
};
kj::HashMap<kj::StringPtr, FieldNameInfo> fieldsByName;
// Maps JSON names to info needed to parse them.
kj::HashMap<kj::StringPtr, StructSchema::Field> unionTagValues;
// If the parent struct is a flattened union, it has a tag field which is a string with one of
// these values. The map maps to the union member to set.
kj::Maybe<kj::StringPtr> unionTagName;
// If the parent struct is a flattened union, the name of the "tag" field.
uint discriminantOffset;
// Shortcut for schema.getProto().getStruct().getDiscriminantOffset(), used in a hack to identify
// which unions have been seen.
struct FlattenedField {
kj::String ownName;
kj::StringPtr name;
kj::OneOf<StructSchema::Field, Type> type;
DynamicValue::Reader value;
FlattenedField(kj::StringPtr prefix, kj::StringPtr name,
kj::OneOf<StructSchema::Field, Type> type, DynamicValue::Reader value)
: ownName(prefix.size() > 0 ? kj::str(prefix, name) : nullptr),
name(prefix.size() > 0 ? ownName : name),
type(type), value(value) {}
};
void gatherForEncode(const JsonCodec& codec, DynamicValue::Reader input,
kj::StringPtr prefix, kj::StringPtr morePrefix,
kj::Vector<FlattenedField>& flattenedFields) const {
kj::String ownPrefix;
if (morePrefix.size() > 0) {
if (prefix.size() > 0) {
ownPrefix = kj::str(prefix, morePrefix);
prefix = ownPrefix;
} else {
prefix = morePrefix;
}
}
auto reader = input.as<DynamicStruct>();
auto schema = reader.getSchema();
for (auto field: schema.getNonUnionFields()) {
auto& info = fields[field.getIndex()];
if (!reader.has(field, codec.impl->hasMode)) {
// skip
} else KJ_IF_MAYBE(handler, info.flattenHandler) {
handler->gatherForEncode(codec, reader.get(field), prefix, info.prefix, flattenedFields);
} else {
flattenedFields.add(FlattenedField {
prefix, info.name, field, reader.get(field) });
}
}
KJ_IF_MAYBE(which, reader.which()) {
auto& info = fields[which->getIndex()];
KJ_IF_MAYBE(tag, unionTagName) {
flattenedFields.add(FlattenedField {
prefix, *tag, Type(schema::Type::TEXT), Text::Reader(info.nameForDiscriminant) });
}
KJ_IF_MAYBE(handler, info.flattenHandler) {
handler->gatherForEncode(codec, reader.get(*which), prefix, info.prefix, flattenedFields);
} else {
auto type = which->getType();
if (type.which() == schema::Type::VOID && unionTagName != nullptr) {
// When we have an explicit union discriminant, we don't need to encode void fields.
} else {
flattenedFields.add(FlattenedField {
prefix, info.name, *which, reader.get(*which) });
}
}
}
}
bool decodeField(const JsonCodec& codec, kj::StringPtr name, JsonValue::Reader value,
DynamicStruct::Builder output, kj::HashSet<const void*>& unionsSeen) const {
KJ_ASSERT(output.getSchema() == schema);
KJ_IF_MAYBE(info, fieldsByName.find(name)) {
switch (info->type) {
case FieldNameInfo::NORMAL: {
auto field = output.getSchema().getFields()[info->index];
codec.decodeField(field, value, Orphanage::getForMessageContaining(output), output);
return true;
}
case FieldNameInfo::FLATTENED:
return KJ_ASSERT_NONNULL(fields[info->index].flattenHandler)
.decodeField(codec, name.slice(info->prefixLength), value,
output.get(output.getSchema().getFields()[info->index]).as<DynamicStruct>(),
unionsSeen);
case FieldNameInfo::UNION_TAG: {
KJ_REQUIRE(value.isString(), "Expected string value.");
// Mark that we've seen a union tag for this struct.
const void* ptr = getUnionInstanceIdentifier(output);
KJ_IF_MAYBE(field, unionTagValues.find(value.getString())) {
// clear() has the side-effect of activating this member of the union, without
// allocating any objects.
output.clear(*field);
unionsSeen.insert(ptr);
}
return true;
}
case FieldNameInfo::FLATTENED_FROM_UNION: {
const void* ptr = getUnionInstanceIdentifier(output);
if (unionsSeen.contains(ptr)) {
auto variant = KJ_ASSERT_NONNULL(output.which());
return KJ_ASSERT_NONNULL(fields[variant.getIndex()].flattenHandler)
.decodeField(codec, name.slice(info->prefixLength), value,
output.get(variant).as<DynamicStruct>(), unionsSeen);
} else {
// We haven't seen the union tag yet, so we can't parse this field yet. Try again later.
return false;
}
}
case FieldNameInfo::UNION_VALUE: {
const void* ptr = getUnionInstanceIdentifier(output);
if (unionsSeen.contains(ptr)) {
auto variant = KJ_ASSERT_NONNULL(output.which());
codec.decodeField(variant, value, Orphanage::getForMessageContaining(output), output);
return true;
} else {
// We haven't seen the union tag yet, so we can't parse this field yet. Try again later.
return false;
}
}
}
KJ_UNREACHABLE;
} else {
// Ignore undefined field -- unless the flag is set to reject them.
KJ_REQUIRE(!codec.impl->rejectUnknownFields, "Unknown field", name);
return true;
}
}
const void* getUnionInstanceIdentifier(DynamicStruct::Builder obj) const {
// Gets a value uniquely identifying an instance of a union.
// HACK: We return a pointer to the union's discriminant within the underlying buffer.
return reinterpret_cast<const uint16_t*>(
AnyStruct::Reader(obj.asReader()).getDataSection().begin()) + discriminantOffset;
}
};
class JsonCodec::AnnotatedEnumHandler final: public JsonCodec::Handler<DynamicEnum> {
public:
AnnotatedEnumHandler(EnumSchema schema): schema(schema) {
auto enumerants = schema.getEnumerants();
auto builder = kj::heapArrayBuilder<kj::StringPtr>(enumerants.size());
for (auto e: enumerants) {
auto proto = e.getProto();
kj::StringPtr name = proto.getName();
for (auto anno: proto.getAnnotations()) {
switch (anno.getId()) {
case JSON_NAME_ANNOTATION_ID:
name = anno.getValue().getText();
break;
}
}
builder.add(name);
nameToValue.insert(name, e.getIndex());
}
valueToName = builder.finish();
}
void encode(const JsonCodec& codec, DynamicEnum input, JsonValue::Builder output) const override {
KJ_IF_MAYBE(e, input.getEnumerant()) {
KJ_ASSERT(e->getIndex() < valueToName.size());
output.setString(valueToName[e->getIndex()]);
} else {
output.setNumber(input.getRaw());
}
}
DynamicEnum decode(const JsonCodec& codec, JsonValue::Reader input) const override {
if (input.isNumber()) {
return DynamicEnum(schema, static_cast<uint16_t>(input.getNumber()));
} else {
uint16_t val = KJ_REQUIRE_NONNULL(nameToValue.find(input.getString()),
"invalid enum value", input.getString());
return DynamicEnum(schema.getEnumerants()[val]);
}
}
private:
EnumSchema schema;
kj::Array<kj::StringPtr> valueToName;
kj::HashMap<kj::StringPtr, uint16_t> nameToValue;
};
class JsonCodec::JsonValueHandler final: public JsonCodec::Handler<DynamicStruct> {
public:
void encode(const JsonCodec& codec, DynamicStruct::Reader input,
JsonValue::Builder output) const override {
#if _MSC_VER && !defined(__clang__)
// TODO(msvc): Hack to work around missing AnyStruct::Builder constructor on MSVC.
rawCopy(input, toDynamic(output));
#else
rawCopy(input, kj::mv(output));
#endif
}
void decode(const JsonCodec& codec, JsonValue::Reader input,
DynamicStruct::Builder output) const override {
rawCopy(input, kj::mv(output));
}
private:
void rawCopy(AnyStruct::Reader input, AnyStruct::Builder output) const {
// HACK: Manually copy using AnyStruct, so that if JsonValue's definition changes, this code
// doesn't need to be updated. However, note that if JsonValue ever adds new fields that
// change its size, and the input struct is a newer version than the output, we may lose
// the new fields. Technically the "correct" thing to do would be to allocate the output
// struct to be exactly the same size as the input, but JsonCodec's Handler interface is
// not designed to allow that -- it passes in an already-allocated builder. Oops.
auto dataIn = input.getDataSection();
auto dataOut = output.getDataSection();
memcpy(dataOut.begin(), dataIn.begin(), kj::min(dataOut.size(), dataIn.size()));
auto ptrIn = input.getPointerSection();
auto ptrOut = output.getPointerSection();
for (auto i: kj::zeroTo(kj::min(ptrIn.size(), ptrOut.size()))) {
ptrOut[i].set(ptrIn[i]);
}
}
};
JsonCodec::AnnotatedHandler& JsonCodec::loadAnnotatedHandler(
StructSchema schema, kj::Maybe<json::DiscriminatorOptions::Reader> discriminator,
kj::Maybe<kj::StringPtr> unionDeclName, kj::Vector<Schema>& dependencies) {
auto& entry = impl->annotatedHandlers.upsert(schema, nullptr,
[&](kj::Maybe<kj::Own<AnnotatedHandler>>& existing, auto dummy) {
KJ_ASSERT(existing != nullptr,
"cyclic JSON flattening detected", schema.getProto().getDisplayName());
});
KJ_IF_MAYBE(v, entry.value) {
// Already exists.
return **v;
} else {
// Not seen before.
auto newHandler = kj::heap<AnnotatedHandler>(
*this, schema, discriminator, unionDeclName, dependencies);
auto& result = *newHandler;
// Map may have changed, so we have to look up again.
KJ_ASSERT_NONNULL(impl->annotatedHandlers.find(schema)) = kj::mv(newHandler);
addTypeHandler(schema, result);
return result;
};
}
void JsonCodec::handleByAnnotation(Schema schema) {
switch (schema.getProto().which()) {
case schema::Node::STRUCT: {
if (schema.getProto().getId() == capnp::typeId<JsonValue>()) {
// Special handler for JsonValue.
static JsonValueHandler GLOBAL_HANDLER;
addTypeHandler(schema.asStruct(), GLOBAL_HANDLER);
} else {
kj::Vector<Schema> dependencies;
loadAnnotatedHandler(schema.asStruct(), nullptr, nullptr, dependencies);
for (auto dep: dependencies) {
handleByAnnotation(dep);
}
}
break;
}
case schema::Node::ENUM: {
auto enumSchema = schema.asEnum();
impl->annotatedEnumHandlers.findOrCreate(enumSchema, [&]() {
auto handler = kj::heap<AnnotatedEnumHandler>(enumSchema);
addTypeHandler(enumSchema, *handler);
return kj::HashMap<Type, kj::Own<AnnotatedEnumHandler>>::Entry {
enumSchema, kj::mv(handler) };
});
break;
}
default:
break;
}
}
} // namespace capnp