diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e12a3c0..4e614626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ x.y.z Release notes * When authentication fails due to a misbehaving server, a proper error is thrown. ### Internal -* None +* Strings can now be assigned to Date columns. When that happens the JavaScript Date constructor will be invoked to parse the string. +* Base64 strings can now be assigned to Data columns. 2.0.12 Release notes (2017-12-1) ============================================================= diff --git a/src/js_object_accessor.hpp b/src/js_object_accessor.hpp index 4fc116e5..c2f8f4b6 100644 --- a/src/js_object_accessor.hpp +++ b/src/js_object_accessor.hpp @@ -22,6 +22,10 @@ #include "js_realm_object.hpp" #include "js_schema.hpp" +#if REALM_ENABLE_SYNC +#include +#endif + namespace realm { class List; class Object; @@ -239,7 +243,23 @@ struct Unbox { if (ctx->is_null(value)) { return BinaryData(); } - ctx->m_owned_binary_data = js::Value::validated_to_binary(ctx->m_ctx, value, "Property"); +#if REALM_ENABLE_SYNC + // realm-sync holds the base64-decoding routine + if (js::Value::is_string(ctx->m_ctx, value)) { + // the incoming value might be a base64 string, so let's try to parse it + std::string str = js::Value::to_string(ctx->m_ctx, value); + size_t max_size = util::base64_decoded_size(str.size()); + std::unique_ptr data(new char[max_size]); + if (auto size = util::base64_decode(str, data.get(), max_size)) { + ctx->m_owned_binary_data = OwnedBinaryData(std::move(data), *size); + return ctx->m_owned_binary_data.get(); + } else { + throw std::runtime_error("Attempting to populate BinaryData from string that is not valid base64"); + } + } +#endif + + ctx->m_owned_binary_data = js::Value::validated_to_binary(ctx->m_ctx, value); return ctx->m_owned_binary_data.get(); } }; @@ -257,7 +277,14 @@ struct Unbox { if (ctx->is_null(value)) { return Timestamp(); } - auto date = js::Value::validated_to_date(ctx->m_ctx, value, "Property"); + typename JSEngine::Value date; + if (js::Value::is_string(ctx->m_ctx, value)) { + // the incoming value might be a date string, so let the Date constructor have at it + date = js::Value::to_date(ctx->m_ctx, value); + } else { + date = js::Value::validated_to_date(ctx->m_ctx, value); + } + double milliseconds = js::Value::to_number(ctx->m_ctx, date); int64_t seconds = milliseconds / 1000; int32_t nanoseconds = ((int64_t)milliseconds % 1000) * 1000000; diff --git a/src/js_types.hpp b/src/js_types.hpp index 64f78096..0c7e5309 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -389,9 +389,9 @@ inline bool Value::is_valid_for_property_type(ContextType context, const Valu case PropertyType::String: return is_string(context, value); case PropertyType::Data: - return is_binary(context, value); + return is_binary(context, value) || is_string(context, value); case PropertyType::Date: - return is_date(context, value); + return is_date(context, value) || is_string(context, value); case PropertyType::Object: return true; case PropertyType::Any: diff --git a/src/jsc/jsc_value.hpp b/src/jsc/jsc_value.hpp index 4d4cab89..5c113e56 100644 --- a/src/jsc/jsc_value.hpp +++ b/src/jsc/jsc_value.hpp @@ -203,6 +203,15 @@ inline JSObjectRef jsc::Value::to_constructor(JSContextRef ctx, const JSValueRef template<> inline JSObjectRef jsc::Value::to_date(JSContextRef ctx, const JSValueRef &value) { + if (JSValueIsString(ctx, value)) { + JSValueRef error; + std::array args { value }; + if (JSObjectRef result = JSObjectMakeDate(ctx, args.size(), args.data(), &error)) { + return result; + } else { + throw jsc::Exception(ctx, error); + } + } return to_object(ctx, value); } diff --git a/src/node/node_init.hpp b/src/node/node_init.hpp index f2a9cbca..dfc795b6 100644 --- a/src/node/node_init.hpp +++ b/src/node/node_init.hpp @@ -21,9 +21,9 @@ #include "node_string.hpp" #include "node_protected.hpp" #include "node_context.hpp" -#include "node_value.hpp" #include "node_object.hpp" #include "node_function.hpp" +#include "node_value.hpp" #include "node_exception.hpp" #include "node_return_value.hpp" #include "node_class.hpp" diff --git a/src/node/node_value.hpp b/src/node/node_value.hpp index 20dc7d8e..1339028f 100644 --- a/src/node/node_value.hpp +++ b/src/node/node_value.hpp @@ -204,11 +204,6 @@ inline v8::Local node::Value::to_array(v8::Isolate* isolate, const v return to_object(isolate, value); } -template<> -inline v8::Local node::Value::to_date(v8::Isolate* isolate, const v8::Local &value) { - return to_object(isolate, value); -} - template<> inline v8::Local node::Value::to_function(v8::Isolate* isolate, const v8::Local &value) { return value->IsFunction() ? v8::Local::Cast(value) : v8::Local(); @@ -218,6 +213,16 @@ template<> inline v8::Local node::Value::to_constructor(v8::Isolate* isolate, const v8::Local &value) { return to_function(isolate, value); } - + +template<> +inline v8::Local node::Value::to_date(v8::Isolate* isolate, const v8::Local &value) { + if (value->IsString()) { + v8::Local date_constructor = to_constructor(isolate, node::Object::get_property(isolate, isolate->GetCurrentContext()->Global(), "Date")); + std::array, 1> args { {value} }; + return node::Function::construct(isolate, date_constructor, args.size(), args.data()); + } + return to_object(isolate, value); +} + } // js } // realm diff --git a/tests/js/object-tests.js b/tests/js/object-tests.js index 0b19e20e..88792067 100644 --- a/tests/js/object-tests.js +++ b/tests/js/object-tests.js @@ -297,6 +297,15 @@ module.exports = { }); TestCase.assertArraysEqual(new Uint8Array(object.dataCol), RANDOM_DATA); + if (Realm.Sync) { + // The base64 decoder comes from realm-sync + // Should be able to also set a data property to base64-encoded string. + realm.write(function() { + object.dataCol = require('buffer/').Buffer.from(RANDOM_DATA).toString('base64'); + }); + TestCase.assertArraysEqual(new Uint8Array(object.dataCol), RANDOM_DATA); + } + // Should be to set a data property to a DataView. realm.write(function() { object.dataCol = new DataView(RANDOM_DATA.buffer); @@ -348,7 +357,7 @@ module.exports = { object.dataCol = 1; }); TestCase.assertThrows(function() { - object.dataCol = 'data'; + object.dataCol = 'some binary data'; }); TestCase.assertThrows(function() { object.dataCol = [1]; @@ -431,15 +440,18 @@ module.exports = { // test different dates var realm = new Realm({schema: [schemas.DateObject]}); + const stringifiedDate = new Date(); realm.write(function() { realm.create('Date', { currentDate: new Date(10000) }); realm.create('Date', { currentDate: new Date(-10000) }); realm.create('Date', { currentDate: new Date(1000000000000) }); realm.create('Date', { currentDate: new Date(-1000000000000) }); + realm.create('Date', { currentDate: stringifiedDate.toString() }); }); TestCase.assertEqual(realm.objects('Date')[0].currentDate.getTime(), 10000); TestCase.assertEqual(realm.objects('Date')[1].currentDate.getTime(), -10000); TestCase.assertEqual(realm.objects('Date')[2].currentDate.getTime(), 1000000000000); TestCase.assertEqual(realm.objects('Date')[3].currentDate.getTime(), -1000000000000); + TestCase.assertEqual(realm.objects('Date')[4].currentDate.toString(), stringifiedDate.toString()); } }; diff --git a/tests/js/package.json b/tests/js/package.json index c7afa5d5..4a67b4e0 100644 --- a/tests/js/package.json +++ b/tests/js/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "private": true, "dependencies": { + "buffer": "^5.0.8", "es6-promise": "^3.2.1" } }