Skip to content

Commit 8b388fb

Browse files
Add more tests for PUT and POST (#1688)
* Made `AddJsonBody` and `AddXmlBody` generic with class constraint so that people don't use them to add strings * Added an option to disable the charset (`RestClientOptions.DisableCharset`)
1 parent 3e36e84 commit 8b388fb

File tree

9 files changed

+231
-32
lines changed

9 files changed

+231
-32
lines changed

src/RestSharp/Parameters/FileParameter.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ Stream GetFile() {
7676
public static FileParameter Create(
7777
string name,
7878
Func<Stream> getFile,
79-
long contentLength,
8079
string fileName,
8180
string? contentType = null
8281
)

src/RestSharp/Request/RequestContent.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ void AddBody(bool hasPostParameters) {
117117
// we don't have parameters, only the body
118118
Content = bodyContent;
119119
}
120+
121+
if (_client.Options.DisableCharset) {
122+
Content.Headers.ContentType.CharSet = "";
123+
}
120124
}
121125

122126
void AddPostParameters(ParametersCollection? postParameters) {

src/RestSharp/Request/RestRequestExtensions.cs

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,34 +66,106 @@ public static RestRequest AddOrUpdateParameter(this RestRequest request, string
6666
public static RestRequest AddOrUpdateParameter<T>(this RestRequest request, string name, T value, bool encode = true) where T : struct
6767
=> request.AddOrUpdateParameter(name, value.ToString(), encode);
6868

69+
/// <summary>
70+
/// Adds a URL segment parameter to the request. The resource URL must have a placeholder for the parameter for it to work.
71+
/// For example, if you add a URL segment parameter with the name "id", the resource URL should contain {id} in its path.
72+
/// </summary>
73+
/// <param name="request">Request instance</param>
74+
/// <param name="name">Name of the parameter, must be matching a placeholder in the resource URL as {name}</param>
75+
/// <param name="value">Value of the parameter</param>
76+
/// <param name="encode">Encode the value or not, default true</param>
77+
/// <returns></returns>
6978
public static RestRequest AddUrlSegment(this RestRequest request, string name, string value, bool encode = true)
7079
=> request.AddParameter(new UrlSegmentParameter(name, value, encode));
7180

81+
/// <summary>
82+
/// Adds a URL segment parameter to the request. The resource URL must have a placeholder for the parameter for it to work.
83+
/// For example, if you add a URL segment parameter with the name "id", the resource URL should contain {id} in its path.
84+
/// </summary>
85+
/// <param name="request">Request instance</param>
86+
/// <param name="name">Name of the parameter, must be matching a placeholder in the resource URL as {name}</param>
87+
/// <param name="value">Value of the parameter</param>
88+
/// <param name="encode">Encode the value or not, default true</param>
89+
/// <returns></returns>
7290
public static RestRequest AddUrlSegment<T>(this RestRequest request, string name, T value, bool encode = true) where T : struct
7391
=> request.AddUrlSegment(name, Ensure.NotNull(value.ToString(), nameof(value)), encode);
7492

93+
/// <summary>
94+
/// Adds a query string parameter to the request. The request resource should not contain any placeholders for this parameter.
95+
/// The parameter will be added to the request URL as a query string using name=value format.
96+
/// </summary>
97+
/// <param name="request">Request instance</param>
98+
/// <param name="name">Parameter name</param>
99+
/// <param name="value">Parameter value</param>
100+
/// <param name="encode">Encode the value or not, default true</param>
101+
/// <returns></returns>
75102
public static RestRequest AddQueryParameter(this RestRequest request, string name, string? value, bool encode = true)
76103
=> request.AddParameter(new QueryParameter(name, value, encode));
77104

105+
/// <summary>
106+
/// Adds a query string parameter to the request. The request resource should not contain any placeholders for this parameter.
107+
/// The parameter will be added to the request URL as a query string using name=value format.
108+
/// </summary>
109+
/// <param name="request">Request instance</param>
110+
/// <param name="name">Parameter name</param>
111+
/// <param name="value">Parameter value</param>
112+
/// <param name="encode">Encode the value or not, default true</param>
113+
/// <returns></returns>
78114
public static RestRequest AddQueryParameter<T>(this RestRequest request, string name, T value, bool encode = true) where T : struct
79115
=> request.AddQueryParameter(name, value.ToString(), encode);
80116

117+
/// <summary>
118+
/// Adds a header to the request. RestSharp will try to separate request and content headers when calling the resource.
119+
/// </summary>
120+
/// <param name="request">Request instance</param>
121+
/// <param name="name">Header name</param>
122+
/// <param name="value">Header value</param>
123+
/// <returns></returns>
81124
public static RestRequest AddHeader(this RestRequest request, string name, string value) {
82125
CheckAndThrowsForInvalidHost(name, value);
83126
return request.AddParameter(new HeaderParameter(name, value));
84127
}
85128

129+
/// <summary>
130+
/// Adds a header to the request. RestSharp will try to separate request and content headers when calling the resource.
131+
/// </summary>
132+
/// <param name="request">Request instance</param>
133+
/// <param name="name">Header name</param>
134+
/// <param name="value">Header value</param>
135+
/// <returns></returns>
86136
public static RestRequest AddHeader<T>(this RestRequest request, string name, T value) where T : struct
87137
=> request.AddHeader(name, Ensure.NotNull(value.ToString(), nameof(value)));
88138

139+
/// <summary>
140+
/// Adds or updates the request header. RestSharp will try to separate request and content headers when calling the resource.
141+
/// Existing header with the same name will be replaced.
142+
/// </summary>
143+
/// <param name="request">Request instance</param>
144+
/// <param name="name">Header name</param>
145+
/// <param name="value">Header value</param>
146+
/// <returns></returns>
89147
public static RestRequest AddOrUpdateHeader(this RestRequest request, string name, string value) {
90148
CheckAndThrowsForInvalidHost(name, value);
91149
return request.AddOrUpdateParameter(new HeaderParameter(name, value));
92150
}
93151

152+
/// <summary>
153+
/// Adds or updates the request header. RestSharp will try to separate request and content headers when calling the resource.
154+
/// Existing header with the same name will be replaced.
155+
/// </summary>
156+
/// <param name="request">Request instance</param>
157+
/// <param name="name">Header name</param>
158+
/// <param name="value">Header value</param>
159+
/// <returns></returns>
94160
public static RestRequest AddOrUpdateHeader<T>(this RestRequest request, string name, T value) where T : struct
95161
=> request.AddOrUpdateHeader(name, Ensure.NotNull(value.ToString(), nameof(value)));
96162

163+
/// <summary>
164+
/// Adds multiple headers to the request, using the key-value pairs provided.
165+
/// </summary>
166+
/// <param name="request">Request instance</param>
167+
/// <param name="headers">Collection of key-value pairs, where key will be used as header name, and value as header value</param>
168+
/// <returns></returns>
97169
public static RestRequest AddHeaders(this RestRequest request, ICollection<KeyValuePair<string, string>> headers) {
98170
CheckAndThrowsDuplicateKeys(headers);
99171

@@ -104,6 +176,12 @@ public static RestRequest AddHeaders(this RestRequest request, ICollection<KeyVa
104176
return request;
105177
}
106178

179+
/// <summary>
180+
/// Adds or updates multiple headers to the request, using the key-value pairs provided. Existing headers with the same name will be replaced.
181+
/// </summary>
182+
/// <param name="request">Request instance</param>
183+
/// <param name="headers">Collection of key-value pairs, where key will be used as header name, and value as header value</param>
184+
/// <returns></returns>
107185
public static RestRequest AddOrUpdateHeaders(this RestRequest request, ICollection<KeyValuePair<string, string>> headers) {
108186
CheckAndThrowsDuplicateKeys(headers);
109187

@@ -114,9 +192,42 @@ public static RestRequest AddOrUpdateHeaders(this RestRequest request, ICollecti
114192
return request;
115193
}
116194

195+
/// <summary>
196+
/// Adds a parameter of a given type to the request. It will create a typed parameter instance based on the type argument.
197+
/// It is not recommended to use this overload unless you must, as it doesn't provide any restrictions, and if the name-value-type
198+
/// combination doesn't match, it will throw.
199+
/// </summary>
200+
/// <param name="request">Request instance</param>
201+
/// <param name="name">Name of the parameter, must be matching a placeholder in the resource URL as {name}</param>
202+
/// <param name="value">Value of the parameter</param>
203+
/// <param name="type">Enum value specifying what kind of parameter is being added</param>
204+
/// <param name="encode">Encode the value or not, default true</param>
205+
/// <returns></returns>
117206
public static RestRequest AddParameter(this RestRequest request, string? name, object value, ParameterType type, bool encode = true)
118207
=> request.AddParameter(Parameter.CreateParameter(name, value, type, encode));
119208

209+
/// <summary>
210+
/// Adds or updates request parameter of a given type. It will create a typed parameter instance based on the type argument.
211+
/// Parameter will be added or updated based on its name. If the request has a parameter with the same name, it will be updated.
212+
/// It is not recommended to use this overload unless you must, as it doesn't provide any restrictions, and if the name-value-type
213+
/// combination doesn't match, it will throw.
214+
/// </summary>
215+
/// <param name="request">Request instance</param>
216+
/// <param name="name">Name of the parameter, must be matching a placeholder in the resource URL as {name}</param>
217+
/// <param name="value">Value of the parameter</param>
218+
/// <param name="type">Enum value specifying what kind of parameter is being added</param>
219+
/// <param name="encode">Encode the value or not, default true</param>
220+
/// <returns></returns>
221+
public static RestRequest AddOrUpdateParameter(this RestRequest request, string name, object value, ParameterType type, bool encode = true)
222+
=> request.AddOrUpdateParameter(Parameter.CreateParameter(name, value, type, encode));
223+
224+
/// <summary>
225+
/// Adds or updates request parameter, given the parameter instance, for example <see cref="QueryParameter"/> or <see cref="UrlSegmentParameter"/>.
226+
/// It will replace an existing parameter with the same name.
227+
/// </summary>
228+
/// <param name="request">Request instance</param>
229+
/// <param name="parameter">Parameter instance</param>
230+
/// <returns></returns>
120231
public static RestRequest AddOrUpdateParameter(this RestRequest request, Parameter parameter) {
121232
var p = request.Parameters.FirstOrDefault(x => x.Name == parameter.Name && x.Type == parameter.Type);
122233

@@ -126,16 +237,20 @@ public static RestRequest AddOrUpdateParameter(this RestRequest request, Paramet
126237
return request;
127238
}
128239

240+
/// <summary>
241+
/// Adds or updates multiple request parameters, given the parameter instance, for example
242+
/// <see cref="QueryParameter"/> or <see cref="UrlSegmentParameter"/>. Parameters with the same name will be replaced.
243+
/// </summary>
244+
/// <param name="request">Request instance</param>
245+
/// <param name="parameters">Collection of parameter instances</param>
246+
/// <returns></returns>
129247
public static RestRequest AddOrUpdateParameters(this RestRequest request, IEnumerable<Parameter> parameters) {
130248
foreach (var parameter in parameters)
131249
request.AddOrUpdateParameter(parameter);
132250

133251
return request;
134252
}
135253

136-
public static RestRequest AddOrUpdateParameter(this RestRequest request, string name, object value, ParameterType type, bool encode = true)
137-
=> request.AddOrUpdateParameter(Parameter.CreateParameter(name, value, type, encode));
138-
139254
/// <summary>
140255
/// Adds a file parameter to the request body. The file will be read from disk as a stream.
141256
/// </summary>
@@ -159,15 +274,23 @@ public static RestRequest AddFile(this RestRequest request, string name, string
159274
public static RestRequest AddFile(this RestRequest request, string name, byte[] bytes, string filename, string? contentType = null)
160275
=> request.AddFile(FileParameter.Create(name, bytes, filename, contentType));
161276

277+
/// <summary>
278+
/// Adds a file attachment to the request, where the file content will be retrieved from a given stream
279+
/// </summary>
280+
/// <param name="request">Request instance</param>
281+
/// <param name="name">Parameter name</param>
282+
/// <param name="getFile">Function that returns a stream with the file content</param>
283+
/// <param name="fileName">File name</param>
284+
/// <param name="contentType">Optional: content type. Default is "application/octet-stream"</param>
285+
/// <returns></returns>
162286
public static RestRequest AddFile(
163287
this RestRequest request,
164288
string name,
165289
Func<Stream> getFile,
166290
string fileName,
167-
long contentLength,
168291
string? contentType = null
169292
)
170-
=> request.AddFile(FileParameter.Create(name, getFile, contentLength, fileName, contentType));
293+
=> request.AddFile(FileParameter.Create(name, getFile, fileName, contentType));
171294

172295
/// <summary>
173296
/// Adds a body parameter to the request
@@ -201,7 +324,7 @@ public static RestRequest AddBody(this RestRequest request, object obj, string?
201324
/// <param name="obj">Object that will be serialized to JSON</param>
202325
/// <param name="contentType">Optional: content type. Default is "application/json"</param>
203326
/// <returns></returns>
204-
public static RestRequest AddJsonBody(this RestRequest request, object obj, string contentType = ContentType.Json) {
327+
public static RestRequest AddJsonBody<T>(this RestRequest request, T obj, string contentType = ContentType.Json) where T : class {
205328
request.RequestFormat = DataFormat.Json;
206329
return request.AddParameter(new JsonParameter("", obj, contentType));
207330
}
@@ -214,7 +337,8 @@ public static RestRequest AddJsonBody(this RestRequest request, object obj, stri
214337
/// <param name="contentType">Optional: content type. Default is "application/xml"</param>
215338
/// <param name="xmlNamespace">Optional: XML namespace</param>
216339
/// <returns></returns>
217-
public static RestRequest AddXmlBody(this RestRequest request, object obj, string contentType = ContentType.Xml, string xmlNamespace = "") {
340+
public static RestRequest AddXmlBody<T>(this RestRequest request, T obj, string contentType = ContentType.Xml, string xmlNamespace = "")
341+
where T : class {
218342
request.RequestFormat = DataFormat.Xml;
219343
request.AddParameter(new XmlParameter("", obj, xmlNamespace, contentType));
220344
return request;
@@ -227,7 +351,7 @@ public static RestRequest AddXmlBody(this RestRequest request, object obj, strin
227351
/// <param name="obj">Object to add as form data</param>
228352
/// <param name="includedProperties">Properties to include, or nothing to include everything</param>
229353
/// <returns></returns>
230-
public static RestRequest AddObject(this RestRequest request, object obj, params string[] includedProperties) {
354+
public static RestRequest AddObject<T>(this RestRequest request, T obj, params string[] includedProperties) where T : class {
231355
var props = obj.GetProperties(includedProperties);
232356

233357
foreach (var (name, value) in props) {

src/RestSharp/RestClientOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba
5151
/// running) will be sent along to the server. The default is false.
5252
/// </summary>
5353
public bool UseDefaultCredentials { get; set; }
54+
55+
/// <summary>
56+
/// Set to true if you need the Content-Type not to have the charset
57+
/// </summary>
58+
public bool DisableCharset { get; set; }
5459

5560
#if NETSTANDARD
5661
public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.GZip;

test/RestSharp.IntegrationTests/AsyncTests.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Net;
22
using RestSharp.IntegrationTests.Fixtures;
3-
using RestSharp.Tests.Shared.Fixtures;
43

54
namespace RestSharp.IntegrationTests;
65

@@ -63,18 +62,6 @@ public async Task Can_Timeout_GET_Async() {
6362
Assert.Equal(ResponseStatus.TimedOut, response.ResponseStatus);
6463
}
6564

66-
[Fact]
67-
public async Task Can_Timeout_PUT_Async() {
68-
var request = new RestRequest("timeout", Method.Put).AddBody("Body_Content");
69-
70-
// Half the value of ResponseHandler.Timeout
71-
request.Timeout = 200;
72-
73-
var response = await _client.ExecuteAsync(request);
74-
75-
Assert.Equal(ResponseStatus.TimedOut, response.ResponseStatus);
76-
}
77-
7865
[Fact]
7966
public async Task Handles_GET_Request_Errors_Async() {
8067
var request = new RestRequest("status?code=404");

test/RestSharp.IntegrationTests/Fixtures/TestServer.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
using System.Text.Json;
12
using Microsoft.AspNetCore.Builder;
23
using Microsoft.AspNetCore.Hosting;
34
using Microsoft.AspNetCore.Http;
45
using Microsoft.Extensions.Logging;
6+
using RestSharp.Tests.Shared.Extensions;
57

68
namespace RestSharp.IntegrationTests.Fixtures;
79

@@ -29,15 +31,27 @@ public HttpServer(ITestOutputHelper output = null) {
2931

3032
builder.WebHost.UseUrls(Address);
3133
_app = builder.Build();
34+
35+
var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
36+
37+
// GET
3238
_app.MapGet("success", () => new TestResponse { Message = "Works!" });
3339
_app.MapGet("echo", (string msg) => msg);
3440
_app.MapGet("timeout", async () => await Task.Delay(2000));
3541
_app.MapPut("timeout", async () => await Task.Delay(2000));
3642
// ReSharper disable once ConvertClosureToMethodGroup
3743
_app.MapGet("status", (int code) => Results.StatusCode(code));
38-
3944
_app.MapGet("headers", HandleHeaders);
4045

46+
// PUT
47+
_app.MapPut(
48+
"content",
49+
async context => {
50+
var content = await context.Request.Body.StreamToStringAsync();
51+
await context.Response.WriteAsync(content);
52+
}
53+
);
54+
4155
IResult HandleHeaders(HttpContext ctx) {
4256
var response = ctx.Request.Headers.Select(x => new TestServerResponse(x.Key, x.Value));
4357
return Results.Ok(response);
@@ -54,4 +68,6 @@ public async Task Stop() {
5468
}
5569
}
5670

57-
public record TestServerResponse(string Name, string Value);
71+
record TestServerResponse(string Name, string Value);
72+
73+
record ContentResponse(string Content);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Text.Json;
2+
using RestSharp.IntegrationTests.Fixtures;
3+
4+
namespace RestSharp.IntegrationTests;
5+
6+
[Collection(nameof(TestServerCollection))]
7+
public class PutTests {
8+
readonly ITestOutputHelper _output;
9+
readonly RestClient _client;
10+
11+
static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web);
12+
13+
public PutTests(TestServerFixture fixture, ITestOutputHelper output) {
14+
_output = output;
15+
_client = new RestClient(fixture.Server.Url);
16+
}
17+
18+
[Fact]
19+
public async Task Should_put_json_body() {
20+
var body = new TestRequest("foo", 100);
21+
var request = new RestRequest("content").AddJsonBody(body);
22+
var response = await _client.PutAsync(request);
23+
24+
var expected = JsonSerializer.Serialize(body, Options);
25+
response!.Content.Should().Be(expected);
26+
}
27+
28+
[Fact]
29+
public async Task Should_put_json_body_using_extension() {
30+
var body = new TestRequest("foo", 100);
31+
var response = await _client.PutJsonAsync<TestRequest, TestRequest>("content", body);
32+
response.Should().BeEquivalentTo(response);
33+
}
34+
35+
[Fact]
36+
public async Task Can_Timeout_PUT_Async() {
37+
var request = new RestRequest("timeout", Method.Put).AddBody("Body_Content");
38+
39+
// Half the value of ResponseHandler.Timeout
40+
request.Timeout = 200;
41+
42+
var response = await _client.ExecuteAsync(request);
43+
44+
Assert.Equal(ResponseStatus.TimedOut, response.ResponseStatus);
45+
}
46+
47+
}
48+
49+
record TestRequest(string Data, int Number);

0 commit comments

Comments
 (0)