convert schema parsing

This commit is contained in:
Ari Lazier 2016-03-30 16:14:48 -07:00
parent 61685dee83
commit f80bcd882d
6 changed files with 216 additions and 176 deletions

View File

@ -20,11 +20,13 @@
#include "js_util.hpp"
#include "js_schema.hpp"
#include "shared_realm.hpp"
#include "binding_context.hpp"
#include "object_accessor.hpp"
#include "results.hpp"
#include "platform.hpp"
#include <map>
#include <set>
@ -191,8 +193,8 @@ void Realm<T>::Constructor(ContextType ctx, ObjectType constructor, size_t argum
using StringType = typename T::String;
realm::Realm::Config config;
std::map<std::string, ObjectDefaults> defaults;
std::map<std::string, ObjectType> constructors;
typename Schema<T>::ObjectDefaultsMap defaults;
typename Schema<T>::ConstructorMap constructors;
if (argumentCount == 0) {
config.path = default_path();
}
@ -215,8 +217,8 @@ void Realm<T>::Constructor(ContextType ctx, ObjectType constructor, size_t argum
StringType schemaString("schema");
ValueType schemaValue = RJSValidatedPropertyValue(ctx, object, schemaString);
if (!JSValueIsUndefined(ctx, schemaValue)) {
config.schema.reset(new Schema(RJSParseSchema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, constructors)));
if (!ValueIsUndefined(ctx, schemaValue)) {
config.schema.reset(new realm::Schema(Schema<T>::parse_schema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, constructors)));
}
StringType schemaVersionString("schemaVersion");

View File

@ -44,163 +44,3 @@ JSObjectRef RJSSchemaCreate(JSContextRef ctx, Schema &schema) {
wrapper->owned = false;
return js::WrapObject(ctx, RJSSchemaClass(), wrapper);
}
static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAttributes, std::string propertyName, ObjectDefaults &objectDefaults) {
static JSStringRef defaultString = JSStringCreateWithUTF8CString("default");
static JSStringRef indexedString = JSStringCreateWithUTF8CString("indexed");
static JSStringRef typeString = JSStringCreateWithUTF8CString("type");
static JSStringRef objectTypeString = JSStringCreateWithUTF8CString("objectType");
static JSStringRef optionalString = JSStringCreateWithUTF8CString("optional");
Property prop;
prop.name = propertyName;
JSObjectRef propertyObject = NULL;
std::string type;
if (JSValueIsObject(ctx, propertyAttributes)) {
propertyObject = RJSValidatedValueToObject(ctx, propertyAttributes);
type = RJSValidatedStringProperty(ctx, propertyObject, typeString);
JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL);
if (!JSValueIsUndefined(ctx, optionalValue)) {
if (!JSValueIsBoolean(ctx, optionalValue)) {
throw std::runtime_error("'optional' designation expected to be of type boolean");
}
prop.is_nullable = JSValueToBoolean(ctx, optionalValue);
}
}
else {
type = RJSValidatedStringForValue(ctx, propertyAttributes);
}
if (type == "bool") {
prop.type = PropertyTypeBool;
}
else if (type == "int") {
prop.type = PropertyTypeInt;
}
else if (type == "float") {
prop.type = PropertyTypeFloat;
}
else if (type == "double") {
prop.type = PropertyTypeDouble;
}
else if (type == "string") {
prop.type = PropertyTypeString;
}
else if (type == "date") {
prop.type = PropertyTypeDate;
}
else if (type == "data") {
prop.type = PropertyTypeData;
}
else if (type == "list") {
if (!propertyObject) {
throw std::runtime_error("List property must specify 'objectType'");
}
prop.type = PropertyTypeArray;
prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString);
}
else {
prop.type = PropertyTypeObject;
prop.is_nullable = true;
// The type could either be 'object' or the name of another object type in the same schema.
if (type == "object") {
if (!propertyObject) {
throw std::runtime_error("Object property must specify 'objectType'");
}
prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString);
}
else {
prop.object_type = type;
}
}
if (propertyObject) {
JSValueRef defaultValue = RJSValidatedPropertyValue(ctx, propertyObject, defaultString);
if (!JSValueIsUndefined(ctx, defaultValue)) {
JSValueProtect(ctx, defaultValue);
objectDefaults.emplace(prop.name, defaultValue);
}
JSValueRef indexedValue = RJSValidatedPropertyValue(ctx, propertyObject, indexedString);
if (!JSValueIsUndefined(ctx, indexedValue)) {
prop.is_indexed = JSValueToBoolean(ctx, indexedValue);
}
}
return prop;
}
static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef objectSchemaObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSObjectRef> &constructors) {
static JSStringRef nameString = JSStringCreateWithUTF8CString("name");
static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey");
static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties");
static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema");
JSObjectRef objectConstructor = NULL;
if (JSObjectIsConstructor(ctx, objectSchemaObject)) {
objectConstructor = objectSchemaObject;
objectSchemaObject = RJSValidatedObjectProperty(ctx, objectConstructor, schemaString, "Realm object constructor must have a 'schema' property.");
}
ObjectDefaults objectDefaults;
ObjectSchema objectSchema;
objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString);
JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object.");
if (RJSIsValueArray(ctx, propertiesObject)) {
size_t propertyCount = RJSValidatedListLength(ctx, propertiesObject);
for (size_t i = 0; i < propertyCount; i++) {
JSObjectRef propertyObject = RJSValidatedObjectAtIndex(ctx, propertiesObject, (unsigned int)i);
std::string propertyName = RJSValidatedStringProperty(ctx, propertyObject, nameString);
objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyObject, propertyName, objectDefaults));
}
}
else {
JSPropertyNameArrayRef propertyNames = JSObjectCopyPropertyNames(ctx, propertiesObject);
size_t propertyCount = JSPropertyNameArrayGetCount(propertyNames);
for (size_t i = 0; i < propertyCount; i++) {
JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNames, i);
JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, propertyName);
objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyValue, RJSStringForJSString(propertyName), objectDefaults));
}
JSPropertyNameArrayRelease(propertyNames);
}
JSValueRef primaryValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, primaryString);
if (!JSValueIsUndefined(ctx, primaryValue)) {
objectSchema.primary_key = RJSValidatedStringForValue(ctx, primaryValue);
Property *property = objectSchema.primary_key_property();
if (!property) {
throw std::runtime_error("Missing primary key property '" + objectSchema.primary_key + "'");
}
property->is_primary = true;
}
// Store prototype so that objects of this type will have their prototype set to this prototype object.
if (objectConstructor) {
JSValueProtect(ctx, objectConstructor);
constructors[objectSchema.name] = std::move(objectConstructor);
}
defaults.emplace(objectSchema.name, std::move(objectDefaults));
return objectSchema;
}
realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSObjectRef> &constructors) {
std::vector<ObjectSchema> schema;
size_t length = RJSValidatedListLength(ctx, jsonObject);
for (unsigned int i = 0; i < length; i++) {
JSObjectRef jsonObjectSchema = RJSValidatedObjectAtIndex(ctx, jsonObject, i);
ObjectSchema objectSchema = RJSParseObjectSchema(ctx, jsonObjectSchema, defaults, constructors);
schema.emplace_back(std::move(objectSchema));
}
return Schema(schema);
}

View File

@ -19,14 +19,189 @@
#pragma once
#include "js_util.hpp"
#include "schema.hpp"
#include <map>
namespace realm {
class Schema;
using ObjectDefaults = std::map<std::string, JSValueRef>;
}
JSClassRef RJSSchemaClass();
JSObjectRef RJSSchemaCreate(JSContextRef ctx, realm::Schema *schema);
realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSObjectRef> &constructors);
namespace realm {
namespace js {
template<typename T>
struct Schema
{
using ContextType = typename T::Context;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using ReturnType = typename T::Return;
using StringType = typename T::String;
using ObjectDefaults = std::map<std::string, ValueType>;
using ObjectDefaultsMap = std::map<std::string, ObjectDefaults>;
using ConstructorMap = std::map<std::string, ObjectType>;
static Property parse_property(ContextType ctx, ValueType attributes, std::string propertyame, ObjectDefaults &objectDefaults);
static ObjectSchema parse_object_schema(ContextType ctx, ObjectType objectSchemaObject, ObjectDefaultsMap &defaults, ConstructorMap &constructors);
static realm::Schema parse_schema(ContextType ctx, ObjectType jsonObject, ObjectDefaultsMap &defaults, ConstructorMap &constructors);
};
template<typename T>
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, std::string propertyName, ObjectDefaults &objectDefaults) {
StringType defaultString("default");
StringType indexedString("indexed");
StringType typeString("type");
StringType objectTypeString("objectType");
StringType optionalString("optional");
Property prop;
prop.name = propertyName;
JSObjectRef propertyObject = NULL;
std::string type;
if (ValueIsObject(ctx, attributes)) {
propertyObject = RJSValidatedValueToObject(ctx, attributes);
type = RJSValidatedStringProperty(ctx, propertyObject, typeString);
ValueType optionalValue = ObjectGetProperty(ctx, propertyObject, optionalString, NULL);
if (!ValueIsUndefined(ctx, optionalValue)) {
prop.is_nullable = RJSValidatedValueToBoolean(ctx, optionalValue, "'optional' designation expected to be of type boolean");
}
}
else {
type = RJSValidatedStringForValue(ctx, attributes);
}
if (type == "bool") {
prop.type = PropertyTypeBool;
}
else if (type == "int") {
prop.type = PropertyTypeInt;
}
else if (type == "float") {
prop.type = PropertyTypeFloat;
}
else if (type == "double") {
prop.type = PropertyTypeDouble;
}
else if (type == "string") {
prop.type = PropertyTypeString;
}
else if (type == "date") {
prop.type = PropertyTypeDate;
}
else if (type == "data") {
prop.type = PropertyTypeData;
}
else if (type == "list") {
if (!propertyObject) {
throw std::runtime_error("List property must specify 'objectType'");
}
prop.type = PropertyTypeArray;
prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString);
}
else {
prop.type = PropertyTypeObject;
prop.is_nullable = true;
// The type could either be 'object' or the name of another object type in the same schema.
if (type == "object") {
if (!propertyObject) {
throw std::runtime_error("Object property must specify 'objectType'");
}
prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString);
}
else {
prop.object_type = type;
}
}
if (propertyObject) {
ValueType defaultValue = RJSValidatedPropertyValue(ctx, propertyObject, defaultString);
if (!ValueIsUndefined(ctx, defaultValue)) {
ValueProtect(ctx, defaultValue);
objectDefaults.emplace(prop.name, defaultValue);
}
ValueType indexedValue = RJSValidatedPropertyValue(ctx, propertyObject, indexedString);
if (!ValueIsUndefined(ctx, indexedValue)) {
prop.is_indexed = RJSValidatedValueToBoolean(ctx, indexedValue);
}
}
return prop;
}
template<typename T>
ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType objectSchemaObject, ObjectDefaultsMap &defaults, ConstructorMap &constructors) {
StringType nameString("name");
StringType primaryString("primaryKey");
StringType propertiesString("properties");
StringType schemaString("schema");
ObjectType objectConstructor = NULL;
if (ValueIsConstructor(ctx, objectSchemaObject)) {
objectConstructor = objectSchemaObject;
objectSchemaObject = RJSValidatedObjectProperty(ctx, objectConstructor, schemaString, "Realm object constructor must have a 'schema' property.");
}
ObjectDefaults objectDefaults;
ObjectSchema objectSchema;
objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString);
ObjectType propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object.");
if (RJSIsValueArray(ctx, propertiesObject)) {
size_t propertyCount = RJSValidatedListLength(ctx, propertiesObject);
for (size_t i = 0; i < propertyCount; i++) {
ObjectType propertyObject = RJSValidatedObjectAtIndex(ctx, propertiesObject, (unsigned int)i);
std::string propertyName = RJSValidatedStringProperty(ctx, propertyObject, nameString);
objectSchema.properties.emplace_back(parse_property(ctx, propertyObject, propertyName, objectDefaults));
}
}
else {
auto propertyNames = ObjectGetPropertyNames(ctx, propertiesObject);
for (auto propertyName : propertyNames) {
ValueType propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, StringType(propertyName.c_str()));
objectSchema.properties.emplace_back(parse_property(ctx, propertyValue, propertyName, objectDefaults));
}
}
JSValueRef primaryValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, primaryString);
if (!JSValueIsUndefined(ctx, primaryValue)) {
objectSchema.primary_key = RJSValidatedStringForValue(ctx, primaryValue);
Property *property = objectSchema.primary_key_property();
if (!property) {
throw std::runtime_error("Missing primary key property '" + objectSchema.primary_key + "'");
}
property->is_primary = true;
}
// Store prototype so that objects of this type will have their prototype set to this prototype object.
if (objectConstructor) {
ValueProtect(ctx, objectConstructor);
constructors[objectSchema.name] = std::move(objectConstructor);
}
defaults.emplace(objectSchema.name, std::move(objectDefaults));
return objectSchema;
}
template<typename T>
realm::Schema Schema<T>::parse_schema(ContextType ctx, ObjectType jsonObject, ObjectDefaultsMap &defaults, ConstructorMap &constructors) {
std::vector<ObjectSchema> schema;
size_t length = RJSValidatedListLength(ctx, jsonObject);
for (unsigned int i = 0; i < length; i++) {
JSObjectRef jsonObjectSchema = RJSValidatedObjectAtIndex(ctx, jsonObject, i);
ObjectSchema objectSchema = parse_object_schema(ctx, jsonObjectSchema, defaults, constructors);
schema.emplace_back(std::move(objectSchema));
}
return realm::Schema(schema);
}
}
}

View File

@ -123,19 +123,19 @@ std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const
JSStringRef RJSStringForString(const std::string &str);
JSValueRef RJSValueForString(JSContextRef ctx, const std::string &str);
inline void RJSValidateArgumentCount(size_t argumentCount, size_t expected, const char *message = NULL) {
inline void RJSValidateArgumentCount(size_t argumentCount, size_t expected, const char *message = nullptr) {
if (argumentCount != expected) {
throw std::invalid_argument(message ?: "Invalid arguments");
}
}
inline void RJSValidateArgumentCountIsAtLeast(size_t argumentCount, size_t expected, const char *message = NULL) {
inline void RJSValidateArgumentCountIsAtLeast(size_t argumentCount, size_t expected, const char *message = nullptr) {
if (argumentCount < expected) {
throw std::invalid_argument(message ?: "Invalid arguments");
}
}
inline void RJSValidateArgumentRange(size_t argumentCount, size_t min, size_t max, const char *message = NULL) {
inline void RJSValidateArgumentRange(size_t argumentCount, size_t min, size_t max, const char *message = nullptr) {
if (argumentCount < min || argumentCount > max) {
throw std::invalid_argument(message ?: "Invalid arguments");
}
@ -159,7 +159,7 @@ bool RJSIsValueArray(JSContextRef ctx, JSValueRef value);
bool RJSIsValueArrayBuffer(JSContextRef ctx, JSValueRef value);
bool RJSIsValueDate(JSContextRef ctx, JSValueRef value);
static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = nullptr) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object) {
throw std::runtime_error(message ?: "Value is not an object.");
@ -167,7 +167,7 @@ static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef
return object;
}
static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef value, const char *message = nullptr) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object || !RJSIsValueDate(ctx, object)) {
throw std::runtime_error(message ?: "Value is not a date.");
@ -175,7 +175,7 @@ static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef v
return object;
}
static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) {
static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = nullptr) {
JSObjectRef object = JSValueToObject(ctx, value, NULL);
if (!object || !JSObjectIsFunction(ctx, object)) {
throw std::runtime_error(message ?: "Value is not a function.");
@ -199,9 +199,9 @@ static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef valu
return number;
}
static inline double RJSValidatedValueToBoolean(JSContextRef ctx, JSValueRef value) {
static inline double RJSValidatedValueToBoolean(JSContextRef ctx, JSValueRef value, const char *err = nullptr) {
if (!JSValueIsBoolean(ctx, value)) {
throw std::invalid_argument("Value is not a boolean.");
throw std::invalid_argument(err ?: "Value is not a boolean.");
}
return JSValueToBoolean(ctx, value);
}
@ -224,7 +224,7 @@ static inline JSValueRef RJSValidatedPropertyAtIndex(JSContextRef ctx, JSObjectR
return propertyValue;
}
static inline JSObjectRef RJSValidatedObjectProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property, const char *err = NULL) {
static inline JSObjectRef RJSValidatedObjectProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property, const char *err = nullptr) {
JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, object, property);
if (JSValueIsUndefined(ctx, propertyValue)) {
throw std::runtime_error(err ?: "Object property '" + RJSStringForJSString(property) + "' is undefined");

View File

@ -20,6 +20,7 @@
#include "types.hpp"
#include <string>
#include <vector>
namespace realm {
namespace js {
@ -37,6 +38,27 @@ static inline jsc::Types::Object ValueToObject(jsc::Types::Context ctx, jsc::Typ
static inline void ValueProtect(jsc::Types::Context ctx, jsc::Types::Value value) { JSValueProtect(ctx, value); }
static inline void ValueUnprotect(jsc::Types::Context ctx, jsc::Types::Value value) { JSValueUnprotect(ctx, value); }
static inline std::string StringTypeToString(JSStringRef jsString) {
std::string str;
size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString);
str.resize(maxSize);
str.resize(JSStringGetUTF8CString(jsString, &str[0], maxSize) - 1);
return str;
}
static inline std::vector<std::string> ObjectGetPropertyNames(jsc::Types::Context ctx, jsc::Types::Object object) {
JSPropertyNameArrayRef propertyNames = JSObjectCopyPropertyNames(ctx, object);
size_t propertyCount = JSPropertyNameArrayGetCount(propertyNames);
std::vector<std::string> outNames;
for (size_t i = 0; i < propertyCount; i++) {
outNames.push_back(StringTypeToString(JSPropertyNameArrayGetNameAtIndex(propertyNames, i)));
}
JSPropertyNameArrayRelease(propertyNames);
return outNames;
}
static inline jsc::Types::Value ObjectGetProperty(jsc::Types::Context ctx, jsc::Types::Object object, jsc::Types::String propertyName, jsc::Types::Exception *exception) {
return JSObjectGetProperty(ctx, object, propertyName, exception);
}
static inline void ObjectCallAsFunction(jsc::Types::Context ctx, jsc::Types::Object function, jsc::Types::Object thisObject, size_t argsCount, const jsc::Types::Value args[], jsc::Types::Exception &exception) {
JSObjectCallAsFunction(ctx, function, thisObject, argsCount, args, &exception);
}

View File

@ -28,6 +28,7 @@ namespace jsc {
class String {
public:
String(const char * str) : m_str(JSStringCreateWithUTF8CString(str)) {}
String(const String &other) : m_str(JSStringRetain(other)) {}
~String() { JSStringRelease(m_str); }
operator JSStringRef() const { return m_str; }