-
Notifications
You must be signed in to change notification settings - Fork 552
Expand file tree
/
Copy pathParameterUtils.cs
More file actions
258 lines (240 loc) · 12.3 KB
/
ParameterUtils.cs
File metadata and controls
258 lines (240 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
Copyright 2013 Google 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;
using System.Collections.Generic;
using System.Net.Http;
using System.Linq;
using System.Reflection;
using Google.Apis.Logging;
using Google.Apis.Util;
namespace Google.Apis.Requests.Parameters
{
/// <summary>
/// Utility class for iterating on <see cref="RequestParameterAttribute"/> properties in a request object.
/// </summary>
public static class ParameterUtils
{
private static readonly ILogger Logger = ApplicationContext.Logger.ForType(typeof(ParameterUtils));
/// <summary>
/// Creates a <see cref="System.Net.Http.FormUrlEncodedContent"/> with all the specified parameters in
/// the input request. It uses reflection to iterate over all properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be serialized
/// to the returned <see cref="System.Net.Http.FormUrlEncodedContent"/>.
/// </param>
/// <returns>
/// A <see cref="System.Net.Http.FormUrlEncodedContent"/> which contains the all the given object required
/// values.
/// </returns>
public static FormUrlEncodedContent CreateFormUrlEncodedContent(object request)
{
IList<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
IterateParameters(request, (type, name, value) =>
{
list.Add(new KeyValuePair<string, string>(name, value.ToString()));
});
return new FormUrlEncodedContent(list);
}
/// <summary>
/// Creates a parameter dictionary by using reflection to iterate over all properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set
/// in the output dictionary.
/// </param>
public static IDictionary<string, object> CreateParameterDictionary(object request)
{
// Use the typed implementation, then drop type information to preserve the legacy return type.
return CreateParameterDictionaryWithTypes(request)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Value);
}
/// <summary>
/// Creates a parameter dictionary with type information by using reflection to iterate over all properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set
/// in the output dictionary along with their parameter type.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown when multiple properties set the same parameter name to non-null values.
/// </exception>
/// <returns>
/// A dictionary where the key is the parameter name and the value is a ParameterValue containing type and value.
/// </returns>
private static IDictionary<string, ParameterValue> CreateParameterDictionaryWithTypes(object request)
{
var dict = new Dictionary<string, ParameterValue>();
IterateParameters(request, (type, name, value) =>
{
if (dict.TryGetValue(name, out var existingEntry))
{
var existingValue = existingEntry.Value;
// Repeated enum query parameters end up with two properties: a single
// one, and a Repeatable<T> (where the T is always non-nullable, whether or not the parameter
// is optional). If both properties are set, we fail. Note that this delegate is called
// for nullable enum properties with a null value, annoyingly... if that happens and we then
// see a non-null value, we'll overwrite it. If that happens when we've already got a non-null
// value, we'll ignore it.
if (existingValue is null && value is object)
{
// Overwrite null value with non-null value
dict[name] = new ParameterValue(type, value);
}
else if (value is null)
{
// Ignore new null value
}
else
{
// Throw if we see a second non-null value
throw new InvalidOperationException(
$"The query parameter '{name}' is set by multiple properties. For repeated enum query parameters, ensure that only one property is set to a non-null value.");
}
}
else
{
dict.Add(name, new ParameterValue(type, value));
}
});
return dict;
}
/// <summary>
/// Sets query parameters in the given builder with all all properties with the
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="builder">The request builder</param>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set in the
/// given request builder object
/// </param>
public static void InitParameters(RequestBuilder builder, object request)
{
IterateParameters(request, (type, name, value) =>
{
builder.AddParameter(type, name, value.ToString());
});
}
/// <summary>
/// Sets request parameters in the given builder with all properties with the
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute
/// by expanding <see cref="IEnumerable"/> values into multiple parameters.
/// </summary>
/// <param name="builder">The request builder</param>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set in the
/// given request builder object
/// </param>
public static void InitParametersWithExpansion(RequestBuilder builder, object request)
{
// Use typed methods to preserve RequestParameterType information
var parametersWithTypes = CreateParameterDictionaryWithTypes(request);
// Expand and add all parameters to the builder with their correct types
foreach (var param in ExpandParametersWithTypes(parametersWithTypes))
{
builder.AddParameter(param.Type, param.Name, param.Value);
}
}
/// <summary>
/// Expands a dictionary of typed parameters into a sequence of <see cref="TypedParameter"/> instances.
/// </summary>
/// <remarks>
/// If a parameter value implements <see cref="System.Collections.IEnumerable"/> (and is not a <see cref="string"/>), it is expanded into
/// multiple <see cref="TypedParameter"/> instances with the same name and <see cref="RequestParameterType"/>.
/// This supports repeatable parameters represented as <see cref="Google.Apis.Util.Repeatable{T}"/> (which is <see cref="System.Collections.IEnumerable"/>) and other
/// enumerable values.
/// </remarks>
/// <param name="dictionary">
/// A dictionary where the key is the parameter name and the value is a <see cref="ParameterValue"/> containing both
/// the parameter type and raw value.
/// </param>
/// <returns>
/// An enumerable of <see cref="TypedParameter"/> instances, with enumerable values expanded into repeated parameters.
/// </returns>
internal static IEnumerable<TypedParameter> ExpandParametersWithTypes(IDictionary<string, ParameterValue> dictionary)
{
foreach (var pair in dictionary)
{
var paramType = pair.Value.Type;
var value = pair.Value.Value;
var name = pair.Key;
// Try parsing the value as an enumerable.
var valueAsEnumerable = value as IEnumerable;
if (!(value is string) && valueAsEnumerable != null)
{
foreach (var elem in valueAsEnumerable)
{
yield return new TypedParameter(paramType, name, Utilities.ConvertToString(elem));
}
}
else
{
// Otherwise just convert it to a string.
yield return new TypedParameter(paramType, name, Utilities.ConvertToString(value));
}
}
}
/// <summary>
/// Iterates over all <see cref="Google.Apis.Util.RequestParameterAttribute"/> properties in the request
/// object and invokes the specified action for each of them.
/// </summary>
/// <param name="request">A request object</param>
/// <param name="action">An action to invoke which gets the parameter type, name and its value</param>
private static void IterateParameters(object request, Action<RequestParameterType, string, object> action)
{
// Use ReflectionCache to avoid repeated reflection + attribute lookup on every call.
foreach (var propertyWithAttribute in ReflectionCache.GetRequestParameterProperties(request.GetType()))
{
var property = propertyWithAttribute.Property;
var attribute = propertyWithAttribute.Attribute;
// Get the name of this parameter from the attribute, if it doesn't exist take a lower-case variant of
// property name.
string name = attribute.Name ?? property.Name.ToLowerInvariant();
var propertyType = property.PropertyType;
var value = property.GetValue(request, null);
// Call action with the type name and value.
if (propertyType.GetTypeInfo().IsValueType || value != null)
{
if (attribute.Type == RequestParameterType.UserDefinedQueries)
{
if (typeof(IEnumerable<KeyValuePair<string, string>>).IsAssignableFrom(value.GetType()))
{
foreach (var pair in (IEnumerable<KeyValuePair<string, string>>)value)
{
action(RequestParameterType.Query, pair.Key, pair.Value);
}
}
else
{
Logger.Warning("Parameter marked with RequestParameterType.UserDefinedQueries attribute " +
"was not of type IEnumerable<KeyValuePair<string, string>> and will be skipped.");
}
}
else
{
action(attribute.Type, name, value);
}
}
}
}
}
}