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)>
......
This diff is collapsed.
This diff is collapsed.
# 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);
}
}
This diff is collapsed.
......@@ -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