-
Notifications
You must be signed in to change notification settings - Fork 33
/
RequestInformation.cs
319 lines (309 loc) · 15.5 KB
/
RequestInformation.cs
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Tavis.UriTemplates;
namespace Microsoft.Kiota.Abstractions
{
/// <summary>
/// This class represents an abstract HTTP request.
/// </summary>
public class RequestInformation
{
internal const string RawUrlKey = "request-raw-url";
private Uri? _rawUri;
/// <summary>
/// The URI of the request.
/// </summary>
public Uri URI {
set {
if(value == null)
throw new ArgumentNullException(nameof(value));
QueryParameters.Clear();
PathParameters.Clear();
_rawUri = value;
}
get {
if(_rawUri != null)
return _rawUri;
else if(PathParameters.TryGetValue(RawUrlKey, out var rawUrl) &&
rawUrl is string rawUrlString) {
URI = new Uri(rawUrlString);
return _rawUri!;
}
else
{
if(UrlTemplate?.IndexOf("{+baseurl}", StringComparison.OrdinalIgnoreCase) >= 0 && !PathParameters.ContainsKey("baseurl"))
throw new InvalidOperationException($"{nameof(PathParameters)} must contain a value for \"baseurl\" for the url to be built.");
var parsedUrlTemplate = new UriTemplate(UrlTemplate);
foreach(var urlTemplateParameter in PathParameters)
{
parsedUrlTemplate.SetParameter(urlTemplateParameter.Key, GetSanitizedValue(urlTemplateParameter.Value));
}
foreach(var queryStringParameter in QueryParameters)
{
if(queryStringParameter.Value != null)
{
parsedUrlTemplate.SetParameter(queryStringParameter.Key, GetSanitizedValue(queryStringParameter.Value));
}
}
return new Uri(parsedUrlTemplate.Resolve());
}
}
}
/// <summary>
/// Sanitizes objects in order to appear appropiately in the URL
/// </summary>
/// <param name="value">Object to be sanitized</param>
/// <returns>Sanitized object</returns>
private static object GetSanitizedValue(object value) => value switch
{
bool boolean => boolean.ToString().ToLower(),// pass in a lowercase string as the final url will be uppercase due to the way ToString() works for booleans
DateTimeOffset dateTimeOffset => dateTimeOffset.ToString("o"),// Default to ISO 8601 for datetimeoffsets in the url.
DateTime dateTime => dateTime.ToString("o"),// Default to ISO 8601 for datetimes in the url.
_ => value,//return object as is as the ToString method is good enough.
};
/// <summary>
/// The Url template for the current request.
/// </summary>
public string? UrlTemplate { get; set; }
/// <summary>
/// The path parameters to use for the URL template when generating the URI.
/// </summary>
public IDictionary<string, object> PathParameters { get; set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The <see cref="Method">HTTP method</see> of the request.
/// </summary>
public Method HttpMethod { get; set; }
/// <summary>
/// The Query Parameters of the request.
/// </summary>
public IDictionary<string, object> QueryParameters { get; set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Vanity method to add the query parameters to the request query parameters dictionary.
/// </summary>
/// <param name="source">The query parameters to add.</param>
public void AddQueryParameters(object source)
{
if(source == null) return;
foreach(var property in source.GetType()
.GetProperties()
.Select(
x => (
Name: x.GetCustomAttributes(false)
.OfType<QueryParameterAttribute>()
.FirstOrDefault()?.TemplateName ?? x.Name.ToFirstCharacterLowerCase(),
Value: x.GetValue(source)
)
)
.Where(x => x.Value != null &&
!QueryParameters.ContainsKey(x.Name!) &&
!string.IsNullOrEmpty(x.Value.ToString()) && // no need to add an empty string value
(x.Value is not ICollection collection || collection.Count > 0))) // no need to add empty collection
{
QueryParameters.AddOrReplace(property.Name!, property.Value!);
}
}
/// <summary>
/// The Request Headers.
/// </summary>
public RequestHeaders Headers { get; private set; } = new ();
/// <summary>
/// Vanity method to add the headers to the request headers dictionary.
/// </summary>
public void AddHeaders(RequestHeaders headers) {
if(headers == null) return;
Headers.AddAll(headers);
}
/// <summary>
/// The Request Body.
/// </summary>
public Stream Content { get; set; } = Stream.Null;
private readonly Dictionary<string, IRequestOption> _requestOptions = new Dictionary<string, IRequestOption>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets the options for this request. Options are unique by type. If an option of the same type is added twice, the last one wins.
/// </summary>
public IEnumerable<IRequestOption> RequestOptions { get { return _requestOptions.Values; } }
/// <summary>
/// Adds an option to the request.
/// </summary>
/// <param name="options">The option to add.</param>
public void AddRequestOptions(IEnumerable<IRequestOption> options)
{
if(options == null) return;
foreach(var option in options.Where(x => x != null))
_requestOptions.AddOrReplace(option.GetType().FullName!, option);
}
/// <summary>
/// Removes given options from the current request.
/// </summary>
/// <param name="options">Options to remove.</param>
public void RemoveRequestOptions(params IRequestOption[] options)
{
if(!options?.Any() ?? false) throw new ArgumentNullException(nameof(options));
foreach(var optionName in options!.Where(x => x != null).Select(x => x.GetType().FullName))
_requestOptions.Remove(optionName!);
}
/// <summary>
/// Gets a <see cref="IRequestOption"/> instance of the matching type.
/// </summary>
public T? GetRequestOption<T>() => _requestOptions.TryGetValue(typeof(T).FullName!, out var requestOption) ? (T)requestOption : default;
/// <summary>
/// Adds a <see cref="IResponseHandler"/> as a <see cref="IRequestOption"/> for the request.
/// </summary>
public void SetResponseHandler(IResponseHandler responseHandler)
{
if(responseHandler == null)
throw new ArgumentNullException(nameof(responseHandler));
var responseHandlerOption = new ResponseHandlerOption
{
ResponseHandler = responseHandler
};
AddRequestOptions(new[] { responseHandlerOption });
}
private const string BinaryContentType = "application/octet-stream";
private const string ContentTypeHeader = "Content-Type";
/// <summary>
/// Sets the request body to a binary stream.
/// </summary>
/// <param name="content">The binary stream to set as a body.</param>
public void SetStreamContent(Stream content)
{
using var activity = _activitySource?.StartActivity(nameof(SetStreamContent));
SetRequestType(content, activity);
Content = content;
Headers.Add(ContentTypeHeader, BinaryContentType);
}
private static ActivitySource _activitySource = new(typeof(RequestInformation).Namespace!);
/// <summary>
/// Sets the request body from a model with the specified content type.
/// </summary>
/// <param name="requestAdapter">The core service to get the serialization writer from.</param>
/// <param name="items">The models to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromParsable<T>(IRequestAdapter requestAdapter, string contentType, IEnumerable<T> items) where T : IParsable
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromParsable));
using var writer = GetSerializationWriter(requestAdapter, contentType, items);
SetRequestType(items.FirstOrDefault(static x => x != null), activity);
writer.WriteCollectionOfObjectValues(null, items);
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
/// <summary>
/// Sets the request body from a model with the specified content type.
/// </summary>
/// <param name="requestAdapter">The core service to get the serialization writer from.</param>
/// <param name="item">The model to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromParsable<T>(IRequestAdapter requestAdapter, string contentType, T item) where T : IParsable
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromParsable));
using var writer = GetSerializationWriter(requestAdapter, contentType, item);
SetRequestType(item, activity);
writer.WriteObjectValue(null, item);
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
private static void SetRequestType(object? result, Activity? activity)
{
if (activity == null) return;
if (result == null) return;
activity.SetTag("com.microsoft.kiota.request.type", result.GetType().FullName);
}
private static ISerializationWriter GetSerializationWriter<T>(IRequestAdapter requestAdapter, string contentType, T item)
{
if(string.IsNullOrEmpty(contentType)) throw new ArgumentNullException(nameof(contentType));
if(requestAdapter == null) throw new ArgumentNullException(nameof(requestAdapter));
if(item == null) throw new InvalidOperationException($"{nameof(item)} cannot be null");
return requestAdapter.SerializationWriterFactory.GetSerializationWriter(contentType);
}
/// <summary>
/// Sets the request body from a scalar value with the specified content type.
/// </summary>
/// <param name="requestAdapter">The core service to get the serialization writer from.</param>
/// <param name="items">The scalar values to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromScalarCollection<T>(IRequestAdapter requestAdapter, string contentType, IEnumerable<T> items)
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromScalarCollection));
using var writer = GetSerializationWriter(requestAdapter, contentType, items);
SetRequestType(items.FirstOrDefault(static x => x != null), activity);
writer.WriteCollectionOfPrimitiveValues(null, items);
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
/// <summary>
/// Sets the request body from a scalar value with the specified content type.
/// </summary>
/// <param name="requestAdapter">The core service to get the serialization writer from.</param>
/// <param name="item">The scalar value to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromScalar<T>(IRequestAdapter requestAdapter, string contentType, T item)
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromScalar));
using var writer = GetSerializationWriter(requestAdapter, contentType, item);
SetRequestType(item, activity);
switch(item)
{
case string s:
writer.WriteStringValue(null, s);
break;
case bool b:
writer.WriteBoolValue(null, b);
break;
case byte b:
writer.WriteByteValue(null, b);
break;
case sbyte b:
writer.WriteSbyteValue(null, b);
break;
case int i:
writer.WriteIntValue(null, i);
break;
case float f:
writer.WriteFloatValue(null, f);
break;
case long l:
writer.WriteLongValue(null, l);
break;
case double d:
writer.WriteDoubleValue(null, d);
break;
case Guid g:
writer.WriteGuidValue(null, g);
break;
case DateTimeOffset dto:
writer.WriteDateTimeOffsetValue(null, dto);
break;
case TimeSpan timeSpan:
writer.WriteTimeSpanValue(null, timeSpan);
break;
case Date date:
writer.WriteDateValue(null, date);
break;
case Time time:
writer.WriteTimeValue(null, time);
break;
case null:
writer.WriteNullValue(null);
break;
default:
throw new InvalidOperationException($"error serialization data value with unknown type {item?.GetType()}");
}
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
}
}