realm-js/src/js_schema.hpp
Maximilian Alexander 96370f3240 Add Schema Name to Error Message with Primary Key (#1655)
* specifying schema name in error
2018-02-07 08:25:54 +01:00

360 lines
15 KiB
C++

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#pragma once
#include <map>
#include "js_types.hpp"
#include "schema.hpp"
namespace realm {
namespace js {
template<typename T>
struct Schema {
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using String = js::String<T>;
using Object = js::Object<T>;
using Value = js::Value<T>;
using ObjectDefaults = std::map<std::string, Protected<ValueType>>;
using ObjectDefaultsMap = std::map<std::string, ObjectDefaults>;
using ConstructorMap = std::map<std::string, Protected<FunctionType>>;
static ObjectType dict_for_property_array(ContextType, const ObjectSchema &, ObjectType);
static Property parse_property(ContextType, ValueType, StringData, std::string, ObjectDefaults &);
static ObjectSchema parse_object_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
static realm::Schema parse_schema(ContextType, ObjectType, ObjectDefaultsMap &, ConstructorMap &);
static ObjectType object_for_schema(ContextType, const realm::Schema &);
static ObjectType object_for_object_schema(ContextType, const ObjectSchema &);
static ObjectType object_for_property(ContextType, const Property &);
};
template<typename T>
typename T::Object Schema<T>::dict_for_property_array(ContextType ctx, const ObjectSchema &object_schema, ObjectType array) {
size_t count = object_schema.persisted_properties.size();
if (count != Object::validated_get_length(ctx, array)) {
throw std::runtime_error("Array must contain values for all object properties");
}
ObjectType dict = Object::create_empty(ctx);
for (uint32_t i = 0; i < count; i++) {
ValueType value = Object::get_property(ctx, array, i);
Object::set_property(ctx, dict, object_schema.persisted_properties[i].name, value);
}
return dict;
}
static inline void parse_property_type(StringData object_name, Property& prop, StringData type)
{
using realm::PropertyType;
if (!type || !type.size()) {
throw std::logic_error(util::format("Property '%1.%2' must have a non-empty type", object_name, prop.name));
}
if (type.ends_with("[]")) {
prop.type |= PropertyType::Array;
type = type.substr(0, type.size() - 2);
}
if (type.ends_with("?")) {
prop.type |= PropertyType::Nullable;
type = type.substr(0, type.size() - 1);
}
if (type == "bool") {
prop.type |= PropertyType::Bool;
}
else if (type == "int") {
prop.type |= PropertyType::Int;
}
else if (type == "float") {
prop.type |= PropertyType::Float;
}
else if (type == "double") {
prop.type |= PropertyType::Double;
}
else if (type == "string") {
prop.type |= PropertyType::String;
}
else if (type == "date") {
prop.type |= PropertyType::Date;
}
else if (type == "data") {
prop.type |= PropertyType::Data;
}
else if (type == "list") {
if (is_nullable(prop.type)) {
throw std::logic_error(util::format("List property '%1.%2' cannot be optional", object_name, prop.name));
}
if (is_array(prop.type)) {
throw std::logic_error(util::format("List property '%1.%2' must have a non-list value type", object_name, prop.name));
}
prop.type |= PropertyType::Object | PropertyType::Array;
}
else if (type == "linkingObjects") {
prop.type |= PropertyType::LinkingObjects | PropertyType::Array;
}
else if (type == "object") {
prop.type |= PropertyType::Object;
}
else {
// The type could be the name of another object type in the same schema.
prop.type |= PropertyType::Object;
prop.object_type = type;
}
// Object properties are implicitly optional
if (prop.type == PropertyType::Object && !is_array(prop.type)) {
prop.type |= PropertyType::Nullable;
}
}
template<typename T>
Property Schema<T>::parse_property(ContextType ctx, ValueType attributes, StringData object_name,
std::string property_name, ObjectDefaults &object_defaults) {
static const String default_string = "default";
static const String indexed_string = "indexed";
static const String type_string = "type";
static const String object_type_string = "objectType";
static const String optional_string = "optional";
static const String property_string = "property";
Property prop;
prop.name = std::move(property_name);
ObjectType property_object = {};
std::string type;
using realm::PropertyType;
if (Value::is_object(ctx, attributes)) {
property_object = Value::validated_to_object(ctx, attributes);
std::string property_type = Object::validated_get_string(ctx, property_object, type_string);
parse_property_type(object_name, prop, property_type);
ValueType optional_value = Object::get_property(ctx, property_object, optional_string);
if (!Value::is_undefined(ctx, optional_value) && Value::validated_to_boolean(ctx, optional_value, "optional")) {
prop.type |= PropertyType::Nullable;
}
ValueType default_value = Object::get_property(ctx, property_object, default_string);
if (!Value::is_undefined(ctx, default_value)) {
object_defaults.emplace(prop.name, Protected<ValueType>(ctx, default_value));
}
ValueType indexed_value = Object::get_property(ctx, property_object, indexed_string);
if (!Value::is_undefined(ctx, indexed_value)) {
prop.is_indexed = Value::validated_to_boolean(ctx, indexed_value);
}
}
else {
std::string property_type = Value::validated_to_string(ctx, attributes);
parse_property_type(object_name, prop, property_type);
}
if (prop.type == PropertyType::Object && prop.object_type.empty()) {
if (!Value::is_valid(property_object)) {
throw std::logic_error(util::format("%1 property %2.%3 must specify 'objectType'",
is_array(prop.type) ? "List" : "Object", object_name, prop.name));
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
}
if (prop.type == PropertyType::LinkingObjects) {
if (!Value::is_valid(property_object)) {
throw std::logic_error(util::format("Linking objects property %1.%2 must specify 'objectType'",
object_name, prop.name));
}
prop.object_type = Object::validated_get_string(ctx, property_object, object_type_string);
prop.link_origin_property_name = Object::validated_get_string(ctx, property_object, property_string);
}
return prop;
}
template<typename T>
ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_schema_object, ObjectDefaultsMap &defaults, ConstructorMap &constructors) {
static const String name_string = "name";
static const String primary_string = "primaryKey";
static const String properties_string = "properties";
static const String schema_string = "schema";
FunctionType object_constructor = {};
if (Value::is_constructor(ctx, object_schema_object)) {
object_constructor = Value::to_constructor(ctx, object_schema_object);
object_schema_object = Object::validated_get_object(ctx, object_constructor, schema_string, "Realm object constructor must have a 'schema' property.");
}
ObjectDefaults object_defaults;
ObjectSchema object_schema;
object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string, "ObjectSchema");
ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema");
if (Value::is_array(ctx, properties_object)) {
uint32_t length = Object::validated_get_length(ctx, properties_object);
for (uint32_t i = 0; i < length; i++) {
ObjectType property_object = Object::validated_get_object(ctx, properties_object, i);
std::string property_name = Object::validated_get_string(ctx, property_object, name_string);
Property property = parse_property(ctx, property_object, object_schema.name, std::move(property_name), object_defaults);
if (property.type == realm::PropertyType::LinkingObjects) {
object_schema.computed_properties.emplace_back(std::move(property));
}
else {
object_schema.persisted_properties.emplace_back(std::move(property));
}
}
}
else {
auto property_names = Object::get_property_names(ctx, properties_object);
for (auto& property_name : property_names) {
ValueType property_value = Object::get_property(ctx, properties_object, property_name);
Property property = parse_property(ctx, property_value, object_schema.name, property_name, object_defaults);
if (property.type == realm::PropertyType::LinkingObjects) {
object_schema.computed_properties.emplace_back(std::move(property));
}
else {
object_schema.persisted_properties.emplace_back(std::move(property));
}
}
}
ValueType primary_value = Object::get_property(ctx, object_schema_object, primary_string);
if (!Value::is_undefined(ctx, primary_value)) {
object_schema.primary_key = Value::validated_to_string(ctx, primary_value);
Property *property = object_schema.primary_key_property();
if (!property) {
throw std::runtime_error("Schema named '" + object_schema.name + "' specifies primary key of '" + object_schema.primary_key + "' but does not declare a property of that name.");
}
property->is_primary = true;
}
// Store prototype so that objects of this type will have their prototype set to this prototype object.
if (Value::is_valid(object_constructor)) {
constructors.emplace(object_schema.name, Protected<FunctionType>(ctx, object_constructor));
}
defaults.emplace(object_schema.name, std::move(object_defaults));
return object_schema;
}
template<typename T>
realm::Schema Schema<T>::parse_schema(ContextType ctx, ObjectType schema_object,
ObjectDefaultsMap &defaults, ConstructorMap &constructors) {
std::vector<ObjectSchema> schema;
uint32_t length = Object::validated_get_length(ctx, schema_object);
for (uint32_t i = 0; i < length; i++) {
ObjectType object_schema_object = Object::validated_get_object(ctx, schema_object, i, "ObjectSchema");
ObjectSchema object_schema = parse_object_schema(ctx, object_schema_object, defaults, constructors);
schema.emplace_back(std::move(object_schema));
}
return realm::Schema(schema);
}
template<typename T>
typename T::Object Schema<T>::object_for_schema(ContextType ctx, const realm::Schema &schema) {
ObjectType object = Object::create_array(ctx);
uint32_t count = 0;
for (auto& object_schema : schema) {
Object::set_property(ctx, object, count++, object_for_object_schema(ctx, object_schema));
}
return object;
}
template<typename T>
typename T::Object Schema<T>::object_for_object_schema(ContextType ctx, const ObjectSchema &object_schema) {
ObjectType object = Object::create_empty(ctx);
static const String name_string = "name";
Object::set_property(ctx, object, name_string, Value::from_string(ctx, object_schema.name));
ObjectType properties = Object::create_empty(ctx);
for (auto& property : object_schema.persisted_properties) {
Object::set_property(ctx, properties, property.name, object_for_property(ctx, property));
}
for (auto& property : object_schema.computed_properties) {
Object::set_property(ctx, properties, property.name, object_for_property(ctx, property));
}
static const String properties_string = "properties";
Object::set_property(ctx, object, properties_string, properties);
static const String primary_key_string = "primaryKey";
if (object_schema.primary_key.size()) {
Object::set_property(ctx, object, primary_key_string, Value::from_string(ctx, object_schema.primary_key));
}
return object;
}
template<typename T>
typename T::Object Schema<T>::object_for_property(ContextType ctx, const Property &property) {
ObjectType object = Object::create_empty(ctx);
static const String name_string = "name";
Object::set_property(ctx, object, name_string, Value::from_string(ctx, property.name));
static const String type_string = "type";
if (is_array(property.type)) {
if (property.type == realm::PropertyType::LinkingObjects) {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "linkingObjects"));
}
else {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, "list"));
}
}
else {
Object::set_property(ctx, object, type_string, Value::from_string(ctx, string_for_property_type(property.type)));
}
static const String object_type_string = "objectType";
if (property.object_type.size()) {
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, property.object_type));
}
else if (is_array(property.type)) {
Object::set_property(ctx, object, object_type_string, Value::from_string(ctx, string_for_property_type(property.type & ~realm::PropertyType::Flags)));
}
static const String property_string = "property";
if (property.type == realm::PropertyType::LinkingObjects) {
Object::set_property(ctx, object, property_string, Value::from_string(ctx, property.link_origin_property_name));
}
static const String indexed_string = "indexed";
Object::set_property(ctx, object, indexed_string, Value::from_boolean(ctx, property.is_indexed));
static const String optional_string = "optional";
Object::set_property(ctx, object, optional_string, Value::from_boolean(ctx, is_nullable(property.type)));
return object;
}
} // js
} // realm