Skip to content

Commit 843f075

Browse files
committed
CSHARP-1723: Fixed BsonConstructorAttribute with named parameters.
1 parent d918bb8 commit 843f075

File tree

4 files changed

+221
-5
lines changed

4 files changed

+221
-5
lines changed

src/MongoDB.Bson/Serialization/BsonCreatorMap.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ public class BsonCreatorMap
3030
private readonly MemberInfo _memberInfo; // null if there is no corresponding constructor or factory method
3131
private readonly Delegate _delegate;
3232
private bool _isFrozen;
33-
private IEnumerable<MemberInfo> _arguments; // the members that define the values for the delegate's parameters
33+
private List<MemberInfo> _arguments; // the members that define the values for the delegate's parameters
3434

3535
// these values are set when Freeze is called
36-
private IEnumerable<string> _elementNames; // the element names of the serialized arguments
36+
private List<string> _elementNames; // the element names of the serialized arguments
3737
private Dictionary<string, object> _defaultValues; // not all arguments have default values
3838

3939
// constructors
@@ -116,8 +116,15 @@ public void Freeze()
116116

117117
var elementNames = new List<string>();
118118
var defaultValues = new Dictionary<string, object>();
119+
120+
var expectedArgumentsCount = GetExpectedArgumentsCount();
119121
if (_arguments != null)
120122
{
123+
if (_arguments.Count != expectedArgumentsCount)
124+
{
125+
throw new BsonSerializationException("Creator map must have the right number of arguments configured.");
126+
}
127+
121128
foreach (var argument in _arguments)
122129
{
123130
var memberMap = allMemberMaps.FirstOrDefault(m => IsSameMember(m.MemberInfo, argument));
@@ -133,6 +140,13 @@ public void Freeze()
133140
}
134141
}
135142
}
143+
else
144+
{
145+
if (expectedArgumentsCount != 0)
146+
{
147+
throw new BsonSerializationException("Creator map must have arguments configured.");
148+
}
149+
}
136150

137151
_elementNames = elementNames;
138152
_defaultValues = defaultValues;
@@ -163,7 +177,15 @@ public BsonCreatorMap SetArguments(IEnumerable<MemberInfo> arguments)
163177
throw new ArgumentNullException("arguments");
164178
}
165179
if (_isFrozen) { ThrowFrozenException(); }
166-
_arguments = new List<MemberInfo>(arguments);
180+
var argumentsList = arguments.ToList(); // only enumerate once
181+
182+
var expectedArgumentsCount = GetExpectedArgumentsCount();
183+
if (argumentsList.Count != expectedArgumentsCount)
184+
{
185+
throw new ArgumentException("Wrong number of arguments provided", nameof(arguments));
186+
}
187+
188+
_arguments = argumentsList;
167189
return this;
168190
}
169191

@@ -230,6 +252,39 @@ internal object CreateInstance(Dictionary<string, object> values)
230252
}
231253

232254
// private methods
255+
private int GetExpectedArgumentsCount()
256+
{
257+
var constructorInfo = _memberInfo as ConstructorInfo;
258+
if (constructorInfo != null)
259+
{
260+
return constructorInfo.GetParameters().Length;
261+
}
262+
263+
var methodInfo = _memberInfo as MethodInfo;
264+
if (methodInfo != null)
265+
{
266+
return methodInfo.GetParameters().Length;
267+
}
268+
269+
var delegateParameters = _delegate.GetMethodInfo().GetParameters();
270+
if (delegateParameters.Length == 0)
271+
{
272+
return 0;
273+
}
274+
else
275+
{
276+
// check if delegate is closed over its first parameter
277+
if (_delegate.Target != null && _delegate.Target.GetType() == delegateParameters[0].ParameterType)
278+
{
279+
return delegateParameters.Length - 1;
280+
}
281+
else
282+
{
283+
return delegateParameters.Length;
284+
}
285+
}
286+
}
287+
233288
private bool IsSameMember(MemberInfo a, MemberInfo b)
234289
{
235290
// two MemberInfos refer to the same member if the Module and MetadataToken are equal

src/MongoDB.Bson/Serialization/Conventions/NamedParameterCreatorMapConvention.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ public void Apply(BsonCreatorMap creatorMap)
4545
var argument = FindMatchingArgument(creatorMap.ClassMap.ClassType, parameter);
4646
if (argument == null)
4747
{
48-
var message = string.Format("Unable to find a matching member to provide the value for parameter '{0}'.", parameter.Name);
49-
throw new BsonException(message);
48+
return;
5049
}
5150
arguments.Add(argument);
5251
}

tests/MongoDB.Bson.Tests/MongoDB.Bson.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
<Compile Include="ObjectModel\LazyBsonDocumentTests.cs" />
132132
<Compile Include="ObjectModel\RawBsonArrayTests.cs" />
133133
<Compile Include="ObjectModel\RawBsonDocumentTests.cs" />
134+
<Compile Include="Serialization\Attributes\BsonConstructorAttributeTests.cs" />
134135
<Compile Include="Serialization\Attributes\BsonRepresentationAttributeTests.cs" />
135136
<Compile Include="Serialization\BsonClassMapAutoMappingTests.cs" />
136137
<Compile Include="Serialization\BsonClassMapTests.cs" />
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/* Copyright 2016 MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using System.Reflection;
20+
using System.Text;
21+
using System.Threading.Tasks;
22+
using FluentAssertions;
23+
using MongoDB.Bson.Serialization;
24+
using MongoDB.Bson.Serialization.Attributes;
25+
using Xunit;
26+
27+
namespace MongoDB.Bson.Tests.Serialization.Attributes
28+
{
29+
public class BsonConstructorWithNoNamesProvidedTests
30+
{
31+
[Fact]
32+
public void constructor_with_int_should_be_mapped_correctly()
33+
{
34+
var classMap = BsonClassMap.LookupClassMap(typeof(C));
35+
36+
var constructorInfo = typeof(C).GetTypeInfo().GetConstructor(new[] { typeof(int) });
37+
var creatorMap = classMap.CreatorMaps.Where(c => c.MemberInfo == constructorInfo).SingleOrDefault();
38+
creatorMap.Should().NotBeNull();
39+
var expectedArguments = new[]
40+
{
41+
typeof(C).GetTypeInfo().GetProperty("X")
42+
};
43+
creatorMap.Arguments.Should().Equal(expectedArguments);
44+
}
45+
46+
[Fact]
47+
public void constructor_with_int_and_string_should_be_mapped_correctly()
48+
{
49+
var classMap = BsonClassMap.LookupClassMap(typeof(C));
50+
51+
var constructorInfo = typeof(C).GetTypeInfo().GetConstructor(new[] { typeof(int), typeof(string) });
52+
var creatorMap = classMap.CreatorMaps.Where(c => c.MemberInfo == constructorInfo).SingleOrDefault();
53+
creatorMap.Should().NotBeNull();
54+
var expectedArguments = new[]
55+
{
56+
typeof(C).GetTypeInfo().GetProperty("X"),
57+
typeof(C).GetTypeInfo().GetProperty("Y")
58+
};
59+
creatorMap.Arguments.Should().Equal(expectedArguments);
60+
}
61+
62+
[Fact]
63+
public void constructor_with_string_should_be_mapped_correctly()
64+
{
65+
var classMap = BsonClassMap.LookupClassMap(typeof(C));
66+
67+
var constructorInfo = typeof(C).GetTypeInfo().GetConstructor(new[] { typeof(string) });
68+
var creatorMap = classMap.CreatorMaps.Where(c => c.MemberInfo == constructorInfo).SingleOrDefault();
69+
creatorMap.Should().NotBeNull();
70+
var expectedArguments = new[]
71+
{
72+
typeof(C).GetTypeInfo().GetProperty("Y")
73+
};
74+
creatorMap.Arguments.Should().Equal(expectedArguments);
75+
}
76+
77+
// nested types
78+
private class C
79+
{
80+
[BsonConstructor]
81+
public C(int x) { }
82+
83+
[BsonConstructor]
84+
public C(string y) { }
85+
86+
[BsonConstructor]
87+
public C(int x, string y) { }
88+
89+
public int X { get; private set; }
90+
public string Y { get; private set; }
91+
}
92+
}
93+
94+
public class BsonConstructorAttributeWhenArgumentNameDoesNotExistTests
95+
{
96+
[Fact]
97+
public void Apply_should_throw()
98+
{
99+
var exception = Record.Exception(() => BsonClassMap.LookupClassMap(typeof(C)));
100+
101+
exception.Should().BeOfType<BsonSerializationException>();
102+
}
103+
104+
// nested types
105+
private class C
106+
{
107+
[BsonConstructor("X")]
108+
public C(int y)
109+
{
110+
}
111+
112+
public int Y { get; private set; }
113+
}
114+
}
115+
116+
public class BsonConstructorAttributeWhenParameterNameDoesNotExistTests
117+
{
118+
[Fact]
119+
public void Apply_should_throw()
120+
{
121+
var exception = Record.Exception(() => BsonClassMap.LookupClassMap(typeof(C)));
122+
123+
exception.Should().BeOfType<BsonSerializationException>();
124+
}
125+
126+
// nested types
127+
private class C
128+
{
129+
[BsonConstructor]
130+
public C(int x)
131+
{
132+
}
133+
134+
public int Y { get; private set; }
135+
}
136+
}
137+
138+
public class BsonConstructorAttributeWhenArgumentsCountIsWrongTests
139+
{
140+
[Fact]
141+
public void Apply_should_throw()
142+
{
143+
var exception = Record.Exception(() => BsonClassMap.LookupClassMap(typeof(C)));
144+
145+
var argumentException = exception.Should().BeOfType<ArgumentException>().Subject;
146+
argumentException.ParamName.Should().Be("arguments");
147+
}
148+
149+
// nested types
150+
private class C
151+
{
152+
[BsonConstructor("X", "Y")]
153+
public C(int y)
154+
{
155+
}
156+
157+
public int X { get; private set; }
158+
public int Y { get; private set; }
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)