Skip to content

Commit

Permalink
Implemented CSHARP-351. When the BsonClassMapSerializer encounters an…
Browse files Browse the repository at this point in the history
… error deserializing a field or property the name of the field or property as well as the name of the class they are in are included in the error message to assist the user in troubleshooting the problem.
  • Loading branch information
rstam committed Nov 4, 2011
1 parent 822ff2b commit c84030d
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Bson/Exceptions/TruncationException.cs
Expand Up @@ -30,7 +30,7 @@ public class TruncationException : BsonException {
/// Initializes a new instance of the TruncationException class.
/// </summary>
public TruncationException()
: base() {
: this("Truncation resulted in data loss.") {
}

/// <summary>
Expand Down
36 changes: 26 additions & 10 deletions Bson/Serialization/BsonClassMapSerializer.cs
Expand Up @@ -112,6 +112,11 @@ IBsonSerializationOptions options
}
var obj = classMap.CreateInstance();

if (bsonReader.CurrentBsonType != BsonType.Document) {
var message = string.Format("Expected a nested document representing the serialized form of a {0} value, but found a value of type {1} instead.", actualType.FullName, bsonReader.CurrentBsonType);
throw new FileFormatException(message);
}

bsonReader.ReadStartDocument();
var missingElementMemberMaps = new HashSet<BsonMemberMap>(classMap.MemberMaps); // make a copy!
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
Expand Down Expand Up @@ -294,17 +299,28 @@ BsonMemberMap extraElementsMemberMap
object obj,
BsonMemberMap memberMap
) {
var nominalType = memberMap.MemberType;
Type actualType;
if (bsonReader.CurrentBsonType == BsonType.Null) {
actualType = nominalType;
} else {
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
try {
var nominalType = memberMap.MemberType;
Type actualType;
if (bsonReader.CurrentBsonType == BsonType.Null) {
actualType = nominalType;
} else {
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
}
var serializer = memberMap.GetSerializer(actualType);
var value = serializer.Deserialize(bsonReader, nominalType, actualType, memberMap.SerializationOptions);
memberMap.Setter(obj, value);
} catch (Exception ex) {
var message = string.Format(
"An error occurred while deserializing the {0} {1} of class {2}: {3}", // terminating period provided by nested message
memberMap.MemberName,
(memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property",
obj.GetType().FullName,
ex.Message
);
throw new FileFormatException(message, ex);
}
var serializer = memberMap.GetSerializer(actualType);
var value = serializer.Deserialize(bsonReader, nominalType, actualType, memberMap.SerializationOptions);
memberMap.Setter(obj, value);
}

private void SerializeExtraElements(
Expand Down
1 change: 1 addition & 0 deletions BsonUnitTests/BsonUnitTests.csproj
Expand Up @@ -137,6 +137,7 @@
<Compile Include="Jira\CSharp338Tests.cs" />
<Compile Include="Jira\CSharp310Tests.cs" />
<Compile Include="Jira\CSharp350Tests.cs" />
<Compile Include="Jira\CSharp351Tests.cs" />
<Compile Include="Jira\CSharp70Tests.cs" />
<Compile Include="Jira\CSharp71Tests.cs" />
<Compile Include="Jira\CSharp74Tests.cs" />
Expand Down
Expand Up @@ -435,7 +435,15 @@ BsonDocumentWrapper value
Assert.AreEqual(expected, json);

var bson = obj.ToBson();
Assert.Throws<NotSupportedException>(() => BsonSerializer.Deserialize<TestClass>(bson));
try {
BsonSerializer.Deserialize<TestClass>(bson);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the V property of class MongoDB.BsonUnitTests.Serialization.BsonDocumentWrapperSerializerTests+TestClass: Specified method is not supported.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<NotSupportedException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}

[Test]
Expand All @@ -446,7 +454,15 @@ BsonDocumentWrapper value
Assert.AreEqual(expected, json);

var bson = obj.ToBson();
Assert.Throws<NotSupportedException>(() => BsonSerializer.Deserialize<TestClass>(bson));
try {
BsonSerializer.Deserialize<TestClass>(bson);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the V property of class MongoDB.BsonUnitTests.Serialization.BsonDocumentWrapperSerializerTests+TestClass: Specified method is not supported.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<NotSupportedException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}

[Test]
Expand All @@ -464,7 +480,15 @@ BsonDocumentWrapper value
Assert.AreEqual(expected, json);

var bson = obj.ToBson();
Assert.Throws<NotSupportedException>(() => BsonSerializer.Deserialize<TestClass>(bson));
try {
BsonSerializer.Deserialize<TestClass>(bson);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the V property of class MongoDB.BsonUnitTests.Serialization.BsonDocumentWrapperSerializerTests+TestClass: Specified method is not supported.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<NotSupportedException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}
}

Expand Down
55 changes: 55 additions & 0 deletions BsonUnitTests/Jira/CSharp351Tests.cs
@@ -0,0 +1,55 @@
/* Copyright 2010-2011 10gen 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.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using NUnit.Framework;

using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;

namespace MongoDB.BsonUnitTests.Jira {
[TestFixture]
public class CSharp351Tests {
private class C {
public int _id { get; set; }
public N N { get; set; }
}

private class N {
public int X { get; set; }
}

[Test]
public void TestErrorMessage() {
var json = "{ _id : 1, N : 'should be a document, not a string' }";
try {
var c = BsonSerializer.Deserialize<C>(json);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expected = "An error occurred while deserializing the N property of class MongoDB.BsonUnitTests.Jira.CSharp351Tests+C: Expected a nested document representing the serialized form of a MongoDB.BsonUnitTests.Jira.CSharp351Tests+N value, but found a value of type String instead.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<FileFormatException>(ex.InnerException);
Assert.AreEqual(expected, ex.Message);
}
}
}
}
50 changes: 45 additions & 5 deletions DriverOnlineTests/Jira/CSharp112Tests.cs
Expand Up @@ -106,7 +106,15 @@ private class L {

for (int i = 0; i < values.Length; i++) {
var query = Query.EQ("_id", i + 1);
Assert.Throws<TruncationException>(() => collection.FindOneAs<D>(query));
try {
collection.FindOneAs<D>(query);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the N field of class MongoDB.DriverOnlineTests.Jira.CSharp112.CSharp112Tests+D: Truncation resulted in data loss.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<TruncationException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}
}

Expand Down Expand Up @@ -160,7 +168,15 @@ private class L {

for (int i = 0; i < values.Length; i++) {
var query = Query.EQ("_id", i + 1);
Assert.Throws<OverflowException>(() => collection.FindOneAs<I>(query));
try {
collection.FindOneAs<I>(query);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the N field of class MongoDB.DriverOnlineTests.Jira.CSharp112.CSharp112Tests+I: Arithmetic operation resulted in an overflow.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<OverflowException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}

// test with values that cause truncation
Expand All @@ -179,7 +195,15 @@ private class L {

for (int i = 0; i < values.Length; i++) {
var query = Query.EQ("_id", i + 1);
Assert.Throws<TruncationException>(() => collection.FindOneAs<I>(query));
try {
collection.FindOneAs<I>(query);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the N field of class MongoDB.DriverOnlineTests.Jira.CSharp112.CSharp112Tests+I: Truncation resulted in data loss.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<TruncationException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}
}

Expand Down Expand Up @@ -234,7 +258,15 @@ private class L {

for (int i = 0; i < values.Length; i++) {
var query = Query.EQ("_id", i + 1);
Assert.Throws<OverflowException>(() => collection.FindOneAs<L>(query));
try {
collection.FindOneAs<L>(query);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the N field of class MongoDB.DriverOnlineTests.Jira.CSharp112.CSharp112Tests+L: Arithmetic operation resulted in an overflow.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<OverflowException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}

// test with values that cause data truncation
Expand All @@ -253,7 +285,15 @@ private class L {

for (int i = 0; i < values.Length; i++) {
var query = Query.EQ("_id", i + 1);
Assert.Throws<TruncationException>(() => collection.FindOneAs<L>(query));
try {
collection.FindOneAs<L>(query);
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the N field of class MongoDB.DriverOnlineTests.Jira.CSharp112.CSharp112Tests+L: Truncation resulted in data loss.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<TruncationException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion DriverOnlineTests/Jira/CSharp218Tests.cs
Expand Up @@ -60,7 +60,15 @@ public struct P {
collection.RemoveAll();
var c = new C { Id = ObjectId.GenerateNewId(), P = new P { X = 1, Y = 2 } };
collection.Insert(c);
Assert.Throws<BsonSerializationException>(() => collection.FindOneAs<C>());
try {
collection.FindOneAs<C>();
Assert.Fail("Expected an exception to be thrown.");
} catch (Exception ex) {
var expectedMessage = "An error occurred while deserializing the P field of class MongoDB.DriverOnlineTests.Jira.CSharp218.CSharp218Tests+C: Value class MongoDB.DriverOnlineTests.Jira.CSharp218.CSharp218Tests+P cannot be deserialized.";
Assert.IsInstanceOf<FileFormatException>(ex);
Assert.IsInstanceOf<BsonSerializationException>(ex.InnerException);
Assert.AreEqual(expectedMessage, ex.Message);
}
}

[Test]
Expand Down

0 comments on commit c84030d

Please sign in to comment.