// Copyright (c) 2013, Kenton Varda <temporal@gmail.com> // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef CAPNP_SCHEMA_H_ #define CAPNP_SCHEMA_H_ #include <capnp/schema.capnp.h> namespace capnp { class Schema; class StructSchema; class EnumSchema; class InterfaceSchema; class ListSchema; template <typename T, Kind k = kind<T>()> struct SchemaType_ { typedef Schema Type; }; template <typename T> struct SchemaType_<T, Kind::PRIMITIVE> { typedef schema::Type::Body::Which Type; }; template <typename T> struct SchemaType_<T, Kind::BLOB> { typedef schema::Type::Body::Which Type; }; template <typename T> struct SchemaType_<T, Kind::ENUM> { typedef EnumSchema Type; }; template <typename T> struct SchemaType_<T, Kind::STRUCT> { typedef StructSchema Type; }; template <typename T> struct SchemaType_<T, Kind::INTERFACE> { typedef InterfaceSchema Type; }; template <typename T> struct SchemaType_<T, Kind::LIST> { typedef ListSchema Type; }; template <typename T> using SchemaType = typename SchemaType_<T>::Type; // SchemaType<T> is the type of T's schema, e.g. StructSchema if T is a struct. class Schema { // Convenience wrapper around capnp::schema::Node. public: inline Schema(): raw(nullptr) {} template <typename T> static inline SchemaType<T> from() { return SchemaType<T>::template fromImpl<T>(); } // Get the Schema for a particular compiled-in type. schema::Node::Reader getProto() const; kj::ArrayPtr<const word> asUncheckedMessage() const; // Get the encoded schema node content as a single message segment. It is safe to read as an // unchecked message. Schema getDependency(uint64_t id) const; // Gets the Schema for one of this Schema's dependencies. For example, if this Schema is for a // struct, you could look up the schema for one of its fields' types. Throws an exception if this // schema doesn't actually depend on the given id. Note that annotation declarations are not // considered dependencies for this purpose. StructSchema asStruct() const; EnumSchema asEnum() const; InterfaceSchema asInterface() const; // Cast the Schema to a specific type. Throws an exception if the type doesn't match. inline bool operator==(const Schema& other) const { return raw == other.raw; } inline bool operator!=(const Schema& other) const { return raw != other.raw; } // Determine whether two Schemas are wrapping the exact same underlying data, by identity. If // you want to check if two Schemas represent the same type (but possibly different versions of // it), compare their IDs instead. template <typename T> void requireUsableAs() const; // Throws an exception if a value with this Schema cannot safely be cast to a native value of // the given type. This passes if either: // - *this == from<T>() // - This schema was loaded with SchemaLoader, the type ID matches typeId<T>(), and // loadCompiledTypeAndDependencies<T>() was called on the SchemaLoader. private: const _::RawSchema* raw; inline explicit Schema(const _::RawSchema* raw): raw(raw) { KJ_IREQUIRE(raw->lazyInitializer == nullptr, "Must call ensureInitialized() on RawSchema before constructing Schema."); } template <typename T> static inline Schema fromImpl() { return Schema(&_::rawSchema<T>()); } void requireUsableAs(const _::RawSchema* expected) const; friend class StructSchema; friend class EnumSchema; friend class InterfaceSchema; friend class ListSchema; friend class SchemaLoader; }; // ------------------------------------------------------------------- class StructSchema: public Schema { public: StructSchema() = default; class Member; class Field; class Union; class Group; class MemberList; MemberList getMembers() const; kj::Maybe<Member> findMemberByName(kj::StringPtr name) const; Member getMemberByName(kj::StringPtr name) const; // Like findMemberByName() but throws an exception on failure. private: StructSchema(const _::RawSchema* raw): Schema(raw) {} template <typename T> static inline StructSchema fromImpl() { return StructSchema(&_::rawSchema<T>()); } friend class Schema; friend kj::StringTree _::structString( _::StructReader reader, const _::RawSchema& schema); friend kj::StringTree _::unionString( _::StructReader reader, const _::RawSchema& schema, uint memberIndex); }; class StructSchema::Member { public: Member() = default; inline schema::StructNode::Member::Reader getProto() const { return proto; } inline StructSchema getContainingStruct() const { return parent; } inline uint getIndex() const { return index; } // Get the index of this member within the containing struct or union. kj::Maybe<Union> getContainingUnion() const; // If this a member of a union, gets the containing union schema. Field asField() const; // Cast the member to a Field. Throws an exception if not a field. Union asUnion() const; // Cast the member to a Union. Throws an exception if not a union. Group asGroup() const; // Cast the member to a Group. Throws an exception if not a group. inline bool operator==(const Member& other) const; inline bool operator!=(const Member& other) const { return !(*this == other); } private: StructSchema parent; uint unionIndex; // 0 = none, >0 = actual union index - 1 uint index; schema::StructNode::Member::Reader proto; inline Member(StructSchema parent, uint unionIndex, uint index, schema::StructNode::Member::Reader proto) : parent(parent), unionIndex(unionIndex), index(index), proto(proto) {} friend class StructSchema; }; class StructSchema::Field: public Member { public: Field() = default; uint32_t getDefaultValueSchemaOffset() const; // For struct, list, and object fields, returns the offset, in words, within the first segment of // the struct's schema, where this field's default value pointer is located. The schema is // always stored as a single-segment unchecked message, which in turn means that the default // value pointer itself can be treated as the root of an unchecked message -- if you know where // to find it, which is what this method helps you with. // // For blobs, returns the offset of the begging of the blob's content within the first segment of // the struct's schema. // // This is primarily useful for code generators. The C++ code generator, for example, embeds // the entire schema as a raw word array within the generated code. Of course, to implement // field accessors, it needs access to those fields' default values. Embedding separate copies // of those default values would be redundant since they are already included in the schema, but // seeking through the schema at runtime to find the default values would be ugly. Instead, // the code generator can use getDefaultValueSchemaOffset() to find the offset of the default // value within the schema, and can simply apply that offset at runtime. // // If the above does not make sense, you probably don't need this method. private: inline Field(const Member& base): Member(base) {} friend class StructSchema; }; class StructSchema::Union: public Member { public: Union() = default; MemberList getMembers() const; kj::Maybe<Member> findMemberByName(kj::StringPtr name) const; Member getMemberByName(kj::StringPtr name) const; // Like findMemberByName() but throws an exception on failure. private: inline Union(const Member& base): Member(base) {} friend class StructSchema; }; class StructSchema::Group: public Member { public: Group() = default; MemberList getMembers() const; kj::Maybe<Member> findMemberByName(kj::StringPtr name) const; Member getMemberByName(kj::StringPtr name) const; // Like findMemberByName() but throws an exception on failure. private: inline Group(const Member& base): Member(base) {} friend class StructSchema; }; class StructSchema::MemberList { public: inline uint size() const { return list.size(); } inline Member operator[](uint index) const { return Member(parent, unionIndex, index, list[index]); } typedef _::IndexingIterator<const MemberList, Member> Iterator; inline Iterator begin() const { return Iterator(this, 0); } inline Iterator end() const { return Iterator(this, size()); } private: StructSchema parent; uint unionIndex; List<schema::StructNode::Member>::Reader list; inline MemberList(StructSchema parent, uint unionIndex, List<schema::StructNode::Member>::Reader list) : parent(parent), unionIndex(unionIndex), list(list) {} friend class StructSchema; }; // ------------------------------------------------------------------- class EnumSchema: public Schema { public: EnumSchema() = default; class Enumerant; class EnumerantList; EnumerantList getEnumerants() const; kj::Maybe<Enumerant> findEnumerantByName(kj::StringPtr name) const; Enumerant getEnumerantByName(kj::StringPtr name) const; // Like findEnumerantByName() but throws an exception on failure. private: EnumSchema(const _::RawSchema* raw): Schema(raw) {} template <typename T> static inline EnumSchema fromImpl() { return EnumSchema(&_::rawSchema<T>()); } friend class Schema; }; class EnumSchema::Enumerant { public: Enumerant() = default; inline schema::EnumNode::Enumerant::Reader getProto() const { return proto; } inline EnumSchema getContainingEnum() const { return parent; } inline uint16_t getOrdinal() const { return ordinal; } inline uint getIndex() const { return ordinal; } inline bool operator==(const Enumerant& other) const; inline bool operator!=(const Enumerant& other) const { return !(*this == other); } private: EnumSchema parent; uint16_t ordinal; schema::EnumNode::Enumerant::Reader proto; inline Enumerant(EnumSchema parent, uint16_t ordinal, schema::EnumNode::Enumerant::Reader proto) : parent(parent), ordinal(ordinal), proto(proto) {} friend class EnumSchema; }; class EnumSchema::EnumerantList { public: inline uint size() const { return list.size(); } inline Enumerant operator[](uint index) const { return Enumerant(parent, index, list[index]); } typedef _::IndexingIterator<const EnumerantList, Enumerant> Iterator; inline Iterator begin() const { return Iterator(this, 0); } inline Iterator end() const { return Iterator(this, size()); } private: EnumSchema parent; List<schema::EnumNode::Enumerant>::Reader list; inline EnumerantList(EnumSchema parent, List<schema::EnumNode::Enumerant>::Reader list) : parent(parent), list(list) {} friend class EnumSchema; }; // ------------------------------------------------------------------- class InterfaceSchema: public Schema { public: InterfaceSchema() = default; class Method; class MethodList; MethodList getMethods() const; kj::Maybe<Method> findMethodByName(kj::StringPtr name) const; Method getMethodByName(kj::StringPtr name) const; // Like findMethodByName() but throws an exception on failure. private: InterfaceSchema(const _::RawSchema* raw): Schema(raw) {} template <typename T> static inline InterfaceSchema fromImpl() { return InterfaceSchema(&_::rawSchema<T>()); } friend class Schema; }; class InterfaceSchema::Method { public: Method() = default; inline schema::InterfaceNode::Method::Reader getProto() const { return proto; } inline InterfaceSchema getContainingInterface() const { return parent; } inline uint16_t getOrdinal() const { return ordinal; } inline uint getIndex() const { return ordinal; } inline bool operator==(const Method& other) const; inline bool operator!=(const Method& other) const { return !(*this == other); } private: InterfaceSchema parent; uint16_t ordinal; schema::InterfaceNode::Method::Reader proto; inline Method(InterfaceSchema parent, uint16_t ordinal, schema::InterfaceNode::Method::Reader proto) : parent(parent), ordinal(ordinal), proto(proto) {} friend class InterfaceSchema; }; class InterfaceSchema::MethodList { public: inline uint size() const { return list.size(); } inline Method operator[](uint index) const { return Method(parent, index, list[index]); } typedef _::IndexingIterator<const MethodList, Method> Iterator; inline Iterator begin() const { return Iterator(this, 0); } inline Iterator end() const { return Iterator(this, size()); } private: InterfaceSchema parent; List<schema::InterfaceNode::Method>::Reader list; inline MethodList(InterfaceSchema parent, List<schema::InterfaceNode::Method>::Reader list) : parent(parent), list(list) {} friend class InterfaceSchema; }; // ------------------------------------------------------------------- class ListSchema { // ListSchema is a little different because list types are not described by schema nodes. So, // ListSchema doesn't subclass Schema. public: ListSchema() = default; static ListSchema of(schema::Type::Body::Which primitiveType); static ListSchema of(StructSchema elementType); static ListSchema of(EnumSchema elementType); static ListSchema of(InterfaceSchema elementType); static ListSchema of(ListSchema elementType); // Construct the schema for a list of the given type. static ListSchema of(schema::Type::Reader elementType, Schema context); // Construct from an element type schema. Requires a context which can handle getDependency() // requests for any type ID found in the schema. inline schema::Type::Body::Which whichElementType() const; // Get the element type's "which()". ListSchema does not actually store a schema::Type::Reader // describing the element type, but if it did, this would be equivalent to calling // .getBody().which() on that type. StructSchema getStructElementType() const; EnumSchema getEnumElementType() const; InterfaceSchema getInterfaceElementType() const; ListSchema getListElementType() const; // Get the schema for complex element types. Each of these throws an exception if the element // type is not of the requested kind. inline bool operator==(const ListSchema& other) const; inline bool operator!=(const ListSchema& other) const { return !(*this == other); } template <typename T> void requireUsableAs() const; private: schema::Type::Body::Which elementType; uint8_t nestingDepth; // 0 for T, 1 for List(T), 2 for List(List(T)), ... Schema elementSchema; // if elementType is struct, enum, interface... inline ListSchema(schema::Type::Body::Which elementType) : elementType(elementType), nestingDepth(0) {} inline ListSchema(schema::Type::Body::Which elementType, Schema elementSchema) : elementType(elementType), nestingDepth(0), elementSchema(elementSchema) {} inline ListSchema(schema::Type::Body::Which elementType, uint8_t nestingDepth, Schema elementSchema) : elementType(elementType), nestingDepth(nestingDepth), elementSchema(elementSchema) {} template <typename T> struct FromImpl; template <typename T> static inline ListSchema fromImpl() { return FromImpl<T>::get(); } void requireUsableAs(ListSchema expected) const; friend class Schema; }; // ======================================================================================= // inline implementation template <> inline schema::Type::Body::Which Schema::from<Void>() { return schema::Type::Body::VOID_TYPE; } template <> inline schema::Type::Body::Which Schema::from<bool>() { return schema::Type::Body::BOOL_TYPE; } template <> inline schema::Type::Body::Which Schema::from<int8_t>() { return schema::Type::Body::INT8_TYPE; } template <> inline schema::Type::Body::Which Schema::from<int16_t>() { return schema::Type::Body::INT16_TYPE; } template <> inline schema::Type::Body::Which Schema::from<int32_t>() { return schema::Type::Body::INT32_TYPE; } template <> inline schema::Type::Body::Which Schema::from<int64_t>() { return schema::Type::Body::INT64_TYPE; } template <> inline schema::Type::Body::Which Schema::from<uint8_t>() { return schema::Type::Body::UINT8_TYPE; } template <> inline schema::Type::Body::Which Schema::from<uint16_t>() { return schema::Type::Body::UINT16_TYPE; } template <> inline schema::Type::Body::Which Schema::from<uint32_t>() { return schema::Type::Body::UINT32_TYPE; } template <> inline schema::Type::Body::Which Schema::from<uint64_t>() { return schema::Type::Body::UINT64_TYPE; } template <> inline schema::Type::Body::Which Schema::from<float>() { return schema::Type::Body::FLOAT32_TYPE; } template <> inline schema::Type::Body::Which Schema::from<double>() { return schema::Type::Body::FLOAT64_TYPE; } template <> inline schema::Type::Body::Which Schema::from<Text>() { return schema::Type::Body::TEXT_TYPE; } template <> inline schema::Type::Body::Which Schema::from<Data>() { return schema::Type::Body::DATA_TYPE; } template <typename T> inline void Schema::requireUsableAs() const { requireUsableAs(&_::rawSchema<T>()); } inline bool StructSchema::Member::operator==(const Member& other) const { return parent == other.parent && unionIndex == other.unionIndex && index == other.index; } inline bool EnumSchema::Enumerant::operator==(const Enumerant& other) const { return parent == other.parent && ordinal == other.ordinal; } inline bool InterfaceSchema::Method::operator==(const Method& other) const { return parent == other.parent && ordinal == other.ordinal; } inline ListSchema ListSchema::of(StructSchema elementType) { return ListSchema(schema::Type::Body::STRUCT_TYPE, 0, elementType); } inline ListSchema ListSchema::of(EnumSchema elementType) { return ListSchema(schema::Type::Body::ENUM_TYPE, 0, elementType); } inline ListSchema ListSchema::of(InterfaceSchema elementType) { return ListSchema(schema::Type::Body::INTERFACE_TYPE, 0, elementType); } inline ListSchema ListSchema::of(ListSchema elementType) { return ListSchema(elementType.elementType, elementType.nestingDepth + 1, elementType.elementSchema); } inline schema::Type::Body::Which ListSchema::whichElementType() const { return nestingDepth == 0 ? elementType : schema::Type::Body::LIST_TYPE; } inline bool ListSchema::operator==(const ListSchema& other) const { return elementType == other.elementType && nestingDepth == other.nestingDepth && elementSchema == other.elementSchema; } template <typename T> inline void ListSchema::requireUsableAs() const { static_assert(kind<T>() == Kind::LIST, "ListSchema::requireUsableAs<T>() requires T is a list type."); requireUsableAs(Schema::from<T>()); } template <typename T> struct ListSchema::FromImpl<List<T>> { static inline ListSchema get() { return of(Schema::from<T>()); } }; } // namespace capnp #endif // CAPNP_SCHEMA_H_