Skip to content

Commit

Permalink
Initial API changes to introduce a SerializationContext
Browse files Browse the repository at this point in the history
This will allow for custom converters to be registered with the FirestoreDb. This commit doesn't introduce those custom converters, but makes the API changes required for them.

After this, the implementation of registered custom converters should mostly be in FirestoreDb, SerializationContext and ConverterCache.
  • Loading branch information
jskeet committed Aug 27, 2019
1 parent 68410d7 commit 6036478
Show file tree
Hide file tree
Showing 32 changed files with 175 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ internal class SampleData
};

internal static ExpandoObject Expando { get; } = CreateExpando();
internal static Value Serialized { get; } = ValueSerializer.Serialize(Attributed);
internal static Dictionary<string, Value> SerializedMap { get; } = ValueSerializer.SerializeMap(Attributed);
internal static Value Serialized { get; } = ValueSerializer.Serialize(SerializationContext.Default, Attributed);
internal static Dictionary<string, Value> SerializedMap { get; } = ValueSerializer.SerializeMap(SerializationContext.Default, Attributed);

// Expandos don't work well with object initializers :(
private static ExpandoObject CreateExpando()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static DocumentSnapshot GetSampleSnapshot(string docId)
CreateTime = new Timestamp(1, 10).ToProto(),
UpdateTime = new Timestamp(2, 20).ToProto(),
Name = $"projects/proj/databases/db/documents/col1/{docId}",
Fields = { ValueSerializer.SerializeMap(new { Name = docId }) }
Fields = { ValueSerializer.SerializeMap(db.SerializationContext, new { Name = docId }) }
};
return DocumentSnapshot.ForDocument(db, proto, readTime);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,38 @@ namespace Google.Cloud.Firestore.Benchmarks
{
public class ValueSerializerBenchmark
{
private static readonly SerializationContext _serializationContext = new SerializationContext(null);

[Benchmark]
public Dictionary<string, Value> SerializeMap_Attributed() =>
ValueSerializer.SerializeMap(SampleData.Attributed);
ValueSerializer.SerializeMap(_serializationContext, SampleData.Attributed);

[Benchmark]
public Value Serialize_Attributed() =>
ValueSerializer.Serialize(SampleData.Attributed);
ValueSerializer.Serialize(_serializationContext, SampleData.Attributed);

[Benchmark]
public Dictionary<string, Value> SerializeMap_Anonymous() =>
ValueSerializer.SerializeMap(SampleData.Anonymous);
ValueSerializer.SerializeMap(_serializationContext, SampleData.Anonymous);

[Benchmark]
public Value Serialize_Anonymous() =>
ValueSerializer.Serialize(SampleData.Anonymous);
ValueSerializer.Serialize(_serializationContext, SampleData.Anonymous);

[Benchmark]
public Dictionary<string, Value> SerializeMap_Dictionary() =>
ValueSerializer.SerializeMap(SampleData.Dictionary);
ValueSerializer.SerializeMap(_serializationContext, SampleData.Dictionary);

[Benchmark]
public Value Serialize_Dictionary() =>
ValueSerializer.Serialize(SampleData.Dictionary);
ValueSerializer.Serialize(_serializationContext, SampleData.Dictionary);

[Benchmark]
public Dictionary<string, Value> SerializeMap_Expando() =>
ValueSerializer.SerializeMap(SampleData.Expando);
ValueSerializer.SerializeMap(_serializationContext, SampleData.Expando);

[Benchmark]
public Value Serialize_Expando() =>
ValueSerializer.Serialize(SampleData.Expando);
ValueSerializer.Serialize(_serializationContext, SampleData.Expando);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static class FirestoreAssert
/// </summary>
internal static void AssertSerialized(DocumentSnapshot snapshot, object expectedValue)
{
var serialized = ValueSerializer.SerializeMap(expectedValue);
var serialized = ValueSerializer.SerializeMap(snapshot.Database.SerializationContext, expectedValue);
Assert.Equal(serialized, snapshot.Document.Fields);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void SerializeMap()
{
var converter = AttributedTypeConverter.ForType(typeof(Model));
var sample = new Model { ReadWrite = 50, SeparatedBackingProperty = 100, Created = Timestamp.FromDateTime(DateTime.UtcNow) };
var result = converter.Serialize(sample).MapValue.Fields;
var result = converter.Serialize(SerializationContext.Default, sample).MapValue.Fields;

Assert.Equal(new Value { IntegerValue = 20 }, result["ReadOnly"]);
Assert.Equal(new Value { IntegerValue = 50 }, result["ReadWrite"]);
Expand Down Expand Up @@ -187,7 +187,7 @@ public void InheritedProperties()
};

var converter = AttributedTypeConverter.ForType(typeof(ModelDerived));
var result = converter.Serialize(sample).MapValue.Fields;
var result = converter.Serialize(SerializationContext.Default, sample).MapValue.Fields;
Assert.Equal(new Value { IntegerValue = 1 }, result["Abstract"]);
Assert.Equal(new Value { IntegerValue = 2 }, result["BaseOnly"]);
Assert.Equal(new Value { IntegerValue = 3 }, result["DerivedOnly"]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public void NullReturnsProhibited()
var db = FirestoreDb.Create("proj", "db", new FakeFirestoreClient());
var context = new DeserializationContext(GetSampleSnapshot(db, "doc1"));
var converter = CustomConverter.ForConverterType(typeof(NullReturningConverter), typeof(string));
Assert.Throws<InvalidOperationException>(() => converter.Serialize(""));
Assert.Throws<InvalidOperationException>(() => converter.SerializeMap("", new Dictionary<string, Value>()));
Assert.Throws<InvalidOperationException>(() => converter.Serialize(SerializationContext.Default, ""));
Assert.Throws<InvalidOperationException>(() => converter.SerializeMap(SerializationContext.Default, "", new Dictionary<string, Value>()));
Assert.Throws<InvalidOperationException>(() => converter.DeserializeValue(context, new Value { StringValue = "" }));
Assert.Throws<InvalidOperationException>(() => converter.DeserializeMap(context, new Dictionary<string, Value>()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Document = Google.Cloud.Firestore.V1.Document;
using Xunit;
using static Google.Cloud.Firestore.Tests.DocumentSnapshotHelpers;

namespace Google.Cloud.Firestore.Tests
{
using Google.Cloud.ClientTesting;
using static ProtoHelpers;

public class DocumentChangeTest
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal static DocumentSnapshot GetSampleSnapshot(FirestoreDb db, string docId)
CreateTime = CreateProtoTimestamp(1, 10),
UpdateTime = CreateProtoTimestamp(2, 20),
Name = db.Document($"col1/{docId}").Path,
Fields = { ValueSerializer.SerializeMap(new { Name = docId }) }
Fields = { ValueSerializer.SerializeMap(db.SerializationContext, new { Name = docId }) }
};
return DocumentSnapshot.ForDocument(db, proto, readTime);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ private static DocumentSnapshot GetSampleSnapshot()
CreateTime = CreateProtoTimestamp(1, 10),
UpdateTime = CreateProtoTimestamp(2, 20),
Name = "projects/proj/databases/db/documents/col1/doc1/col2/doc2",
Fields = { ValueSerializer.SerializeMap(poco) }
Fields = { ValueSerializer.SerializeMap(SerializationContext.Default, poco) }
};
return DocumentSnapshot.ForDocument(db, proto, readTime);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ dynamic ConvertCursor(Cursor cursor)
new Document
{
Name = docRef.Path,
Fields = { ValueSerializer.SerializeMap(DeserializeJson(cursor.DocSnapshot.JsonData)) },
Fields = { ValueSerializer.SerializeMap(SerializationContext.Default, DeserializeJson(cursor.DocSnapshot.JsonData)) },
CreateTime = wkt::Timestamp.FromDateTimeOffset(DateTimeOffset.MinValue),
UpdateTime = wkt::Timestamp.FromDateTimeOffset(DateTimeOffset.MinValue),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class SentinelValueTest
public void ArrayRemove()
{
var sentinel = FieldValue.ArrayRemove("a", 1);
var value = ValueSerializer.Serialize(sentinel);
var value = ValueSerializer.Serialize(SerializationContext.Default, sentinel);
Assert.Equal(SentinelKind.ArrayRemove, SentinelValue.GetKind(value));
var array = SentinelValue.GetArrayValue(value);
var expected = CreateArray(CreateValue("a"), CreateValue(1)).ArrayValue;
Expand All @@ -39,7 +39,7 @@ public void ArrayRemove()
public void ArrayUnion()
{
var sentinel = FieldValue.ArrayUnion("a", "b");
var value = ValueSerializer.Serialize(sentinel);
var value = ValueSerializer.Serialize(SerializationContext.Default, sentinel);
Assert.Equal(SentinelKind.ArrayUnion, SentinelValue.GetKind(value));
var array = SentinelValue.GetArrayValue(value);
var expected = CreateArray(CreateValue("a"), CreateValue("b")).ArrayValue;
Expand All @@ -50,7 +50,7 @@ public void ArrayUnion()
public void Increment_Int64()
{
var sentinel = FieldValue.Increment(100L);
var value = ValueSerializer.Serialize(sentinel);
var value = ValueSerializer.Serialize(SerializationContext.Default, sentinel);
Assert.Equal(SentinelKind.NumericIncrement, SentinelValue.GetKind(value));
var increment = SentinelValue.GetIncrement(value);
var expected = new Value { IntegerValue = 100L };
Expand All @@ -61,7 +61,7 @@ public void Increment_Int64()
public void Increment_Double()
{
var sentinel = FieldValue.Increment(12.5);
var value = ValueSerializer.Serialize(sentinel);
var value = ValueSerializer.Serialize(SerializationContext.Default, sentinel);
Assert.Equal(SentinelKind.NumericIncrement, SentinelValue.GetKind(value));
var increment = SentinelValue.GetIncrement(value);
var expected = new Value { DoubleValue = 12.5 };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public void Deserialize_Invalid(Value value, BclType targetType)
[Fact]
public void DeserializeArrayToObjectUsesList()
{
var value = ValueSerializer.Serialize(new[] { 1, 2 });
var value = ValueSerializer.Serialize(SerializationContext.Default, new[] { 1, 2 });
var deserialized = DeserializeDefault(value, typeof(object));
Assert.IsType<List<object>>(deserialized);
Assert.Equal(new List<object> { 1L, 2L }, deserialized);
Expand Down Expand Up @@ -298,7 +298,7 @@ public void RoundTrip_NonPublicProperties()
InternalProperty = "x",
PublicAccessToPrivateProperty = "y"
};
var value = ValueSerializer.Serialize(poco);
var value = ValueSerializer.Serialize(SerializationContext.Default, poco);
// Just verify that we're not using the public property directly...
Assert.True(value.MapValue.Fields.ContainsKey("PrivateProperty"));
Assert.False(value.MapValue.Fields.ContainsKey("PublicAccessToPrivateProperty"));
Expand All @@ -311,7 +311,7 @@ public void RoundTrip_NonPublicProperties()
public void DeserializePrivateConstructor()
{
var original = PrivateConstructor.Create("test", 15);
var value = ValueSerializer.Serialize(original);
var value = ValueSerializer.Serialize(SerializationContext.Default, original);
Assert.Equal("test", value.MapValue.Fields["Name"].StringValue);
Assert.Equal(15L, value.MapValue.Fields["Value"].IntegerValue);

Expand All @@ -323,7 +323,7 @@ public void DeserializePrivateConstructor()
[Fact]
public void DeserializeInt64AndDoubleToEachOther()
{
var value = ValueSerializer.Serialize(new { Name = "Test", DoubleValue = 100L, LongValue = 10.9 });
var value = ValueSerializer.Serialize(SerializationContext.Default, new { Name = "Test", DoubleValue = 100L, LongValue = 10.9 });
Assert.Equal(Value.ValueTypeOneofCase.IntegerValue, value.MapValue.Fields["DoubleValue"].ValueTypeCase);
Assert.Equal(Value.ValueTypeOneofCase.DoubleValue, value.MapValue.Fields["LongValue"].ValueTypeCase);
var deserialized = (ModelWithDoubleAndLong) DeserializeDefault(value, typeof(ModelWithDoubleAndLong));
Expand Down Expand Up @@ -377,7 +377,7 @@ public void DeserializeDoubleToInteger_OutOfRange(BclType type, double doubleVal

private string DeserializeAndReturnWarnings<T>(object valueToSerialize)
{
var value = ValueSerializer.Serialize(valueToSerialize);
var value = ValueSerializer.Serialize(SerializationContext.Default, valueToSerialize);
string warning = null;
var db = FirestoreDb.Create("proj", "db", new FakeFirestoreClient()).WithWarningLogger(Log);
var snapshot = GetSampleSnapshot(db, "doc1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public class ValueSerializerTest
[MemberData(nameof(SerializeOnlyData))]
public void Serialize(object input, Value expectedOutput)
{
var actual = ValueSerializer.Serialize(input);
var actual = ValueSerializer.Serialize(SerializationContext.Default, input);
Assert.Equal(expectedOutput, actual);
}

Expand All @@ -106,7 +106,7 @@ public void Serialize(object input, Value expectedOutput)
public void ValueIsCloned(IMessage proto, Func<Value, IMessage> selector)
{
// Protos should be accepted, but cloned (as they're mutable, and we mutate things too)
var value = ValueSerializer.Serialize(proto);
var value = ValueSerializer.Serialize(SerializationContext.Default, proto);
var actual = selector(value);
Assert.NotSame(proto, actual);
Assert.Equal(proto, actual);
Expand All @@ -116,22 +116,22 @@ public void ValueIsCloned(IMessage proto, Func<Value, IMessage> selector)
public void Serialize_Invalid()
{
// It's unlikely that we'll ever support serializing System.Type...
Assert.Throws<ArgumentException>(() => ValueSerializer.Serialize(typeof(ValueSerializer)));
Assert.Throws<ArgumentException>(() => ValueSerializer.Serialize(SerializationContext.Default, typeof(ValueSerializer)));
}

[Theory]
[MemberData(nameof(SerializeMapTestData))]
public void SerializeMap(object input, Dictionary<string, Value> expectedOutput)
{
var actual = ValueSerializer.SerializeMap(input);
var actual = ValueSerializer.SerializeMap(SerializationContext.Default, input);
Assert.Equal(expectedOutput, actual);
}

[Theory]
[MemberData(nameof(SerializeMapTestData))]
public void SerializeValue_SameAsMap(object input, Dictionary<string, Value> expectedMap)
{
var actual = ValueSerializer.Serialize(input);
var actual = ValueSerializer.Serialize(SerializationContext.Default, input);
var expectedValue = new Value { MapValue = new MapValue { Fields = { expectedMap } } };
Assert.Equal(expectedValue, actual);
}
Expand All @@ -140,15 +140,15 @@ public void SerializeValue_SameAsMap(object input, Dictionary<string, Value> exp
public void SerializeMap_Invalid()
{
// It's unlikely that we'll ever support serializing System.Type...
Assert.Throws<ArgumentException>(() => ValueSerializer.SerializeMap(typeof(ValueSerializer)));
Assert.Throws<ArgumentException>(() => ValueSerializer.SerializeMap(SerializationContext.Default, typeof(ValueSerializer)));
}

[Fact]
public void UInt64Overflow()
{
ulong value = long.MaxValue;
value++;
Assert.Throws<OverflowException>(() => ValueSerializer.Serialize(value));
Assert.Throws<OverflowException>(() => ValueSerializer.Serialize(SerializationContext.Default, value));
}

[Theory]
Expand All @@ -157,14 +157,14 @@ public void UInt64Overflow()
public void BadDateTimeKind(DateTimeKind kind)
{
var date = new DateTime(2017, 10, 6, 1, 2, 3, kind);
Assert.Throws<ArgumentException>(() => ValueSerializer.Serialize(date));
Assert.Throws<ArgumentException>(() => ValueSerializer.Serialize(SerializationContext.Default, date));
}

[Fact]
public void ArrayInArray()
{
var badArray = new[] { new int[10] };
Assert.Throws<ArgumentException>(() => ValueSerializer.Serialize(badArray));
Assert.Throws<ArgumentException>(() => ValueSerializer.Serialize(SerializationContext.Default, badArray));
}

[FirestoreData]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ internal AnonymousTypeConverter(BclType targetType) : base(targetType)
_properties = targetType.GetTypeInfo().DeclaredProperties.ToList();
}

public override void SerializeMap(object value, IDictionary<string, Value> map)
public override void SerializeMap(SerializationContext context, object value, IDictionary<string, Value> map)
{
foreach (var property in _properties)
{
map[property.Name] = ValueSerializer.Serialize(property.GetValue(value));
map[property.Name] = ValueSerializer.Serialize(context, property.GetValue(value));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ public override object DeserializeMap(DeserializationContext context, IDictionar
return ret;
}

public override void SerializeMap(object value, IDictionary<string, Value> map)
public override void SerializeMap(SerializationContext context, object value, IDictionary<string, Value> map)
{
foreach (var property in _readableProperties)
{
map[property.FirestoreName] = property.GetProtoValue(value);
map[property.FirestoreName] = property.GetProtoValue(context, value);
}
}

Expand Down Expand Up @@ -199,16 +199,16 @@ internal AttributedProperty(PropertyInfo property, FirestorePropertyAttribute at

// TODO: Consider creating delegates for the property get/set methods.
// Note: these methods have to handle null values when there's a custom converter involved, just like ValueSerializer/ValueDeserializer do.
internal Value GetProtoValue(object obj)
internal Value GetProtoValue(SerializationContext context, object obj)
{
if (_sentinelValue != null)
{
return _sentinelValue.ToProtoValue();
}
object propertyValue = _propertyInfo.GetValue(obj);
return _converter == null ? ValueSerializer.Serialize(propertyValue)
return _converter == null ? ValueSerializer.Serialize(context, propertyValue)
: propertyValue == null ? new Value { NullValue = wkt::NullValue.NullValue }
: _converter.Serialize(propertyValue);
: _converter.Serialize(context, propertyValue);
}

internal void SetValue(DeserializationContext context, Value value, object target)
Expand Down

0 comments on commit 6036478

Please sign in to comment.