Skip to content

Documentation fixes #2002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ Below you can find how different extensions deal with errors. Note that function
| `ExecuteGetAsync` | No |
| `ExecuteGetAsync<T>` | No |
| `ExecutePostAsync` | No |
| `ExecutePutAsync` | No |
| `ExecuteGetAsync<T>` | No |
| `ExecutePostAsync<T>` | No |
| `ExecutePutAsync` | No |
| `ExecutePutAsync<T>` | No |
| `GetAsync` | Yes |
| `GetAsync<T>` | Yes |
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ record TokenResponse {

Next, we create the authenticator itself. It needs the API key and API key secret to call the token endpoint using basic HTTP authentication. In addition, we can extend the list of parameters with the base URL to convert it to a more generic OAuth2 authenticator.

The easiest way to create an authenticator is to inherit from the `AuthanticatorBase` base class:
The easiest way to create an authenticator is to inherit from the `AuthenticatorBase` base class:

```csharp
public class TwitterAuthenticator : AuthenticatorBase {
Expand Down
2 changes: 1 addition & 1 deletion docs/v107/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Instantiate the client normally, but replace the message handler
var client = new RestClient(...) { ConfigureMessageHandler = _ => mockHttp };
var client = new RestClient(new RestClientOptions { ConfigureMessageHandler = _ => mockHttp });

var request = new RestRequest("http://localhost/api/user/1234");
var response = await client.GetAsync(request);
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="MinVer" Version="4.2.0" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<None Include="$(RepoRoot)\restsharp.png" Pack="true" PackagePath="\"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RestSharp\RestSharp.csproj"/>
Expand Down
67 changes: 64 additions & 3 deletions src/RestSharp/Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// 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.Net;

namespace RestSharp;

Expand All @@ -19,9 +20,44 @@ namespace RestSharp;
/// </summary>
public enum ParameterType {
/// <summary>
/// Cookie parameter
/// A <see cref="Parameter"/> that will added to the QueryString for GET, DELETE, OPTIONS and HEAD requests; and form for POST and PUT requests.
/// </summary>
GetOrPost, UrlSegment, HttpHeader, RequestBody, QueryString
/// <remarks>
/// See <see cref="GetOrPostParameter"/>.
/// </remarks>
GetOrPost,

/// <summary>
/// A <see cref="Parameter"/> that will be added to part of the url by replacing a <c>{placeholder}</c> within the absolute path.
/// </summary>
/// <remarks>
/// See <see cref="UrlSegmentParameter"/>.
/// </remarks>
UrlSegment,

/// <summary>
/// A <see cref="Parameter"/> that will be added as a request header
/// </summary>
/// <remarks>
/// See <see cref="HeaderParameter"/>.
/// </remarks>
HttpHeader,

/// <summary>
/// A <see cref="Parameter"/> that will be added to the request body
/// </summary>
/// <remarks>
/// See <see cref="BodyParameter"/>.
/// </remarks>
RequestBody,

/// <summary>
/// A <see cref="Parameter"/> that will be added to the query string
/// </summary>
/// <remarks>
/// See <see cref="QueryParameter"/>.
/// </remarks>
QueryString
}

/// <summary>
Expand Down Expand Up @@ -55,4 +91,29 @@ public struct DateFormat {
/// <summary>
/// Status for responses (surprised?)
/// </summary>
public enum ResponseStatus { None, Completed, Error, TimedOut, Aborted }
public enum ResponseStatus {
/// <summary>
/// Not Applicable, for when the Request has not yet been made
/// </summary>
None,

/// <summary>
/// <see cref="ResponseStatus"/> for when the request is passes as a result of <see cref="HttpResponseMessage.IsSuccessStatusCode"/> being true, or when the response is <see cref="HttpStatusCode.NotFound"/>
/// </summary>
Completed,

/// <summary>
/// <see cref="ResponseStatus"/> for when the request fails due as a result of <see cref="HttpResponseMessage.IsSuccessStatusCode"/> being false except for the case when the response is <see cref="HttpStatusCode.NotFound"/>
/// </summary>
Error,

/// <summary>
/// <see cref="ResponseStatus"/> for when the Operation is cancelled due to the request taking longer than the length of time prescribed by <see cref="RestRequest.Timeout"/> or due to the <see cref="HttpClient"/> timing out.
/// </summary>
TimedOut,

/// <summary>
/// <see cref="ResponseStatus"/> for when the Operation is cancelled, due to reasons other than <see cref="TimedOut"/>
/// </summary>
Aborted
}
24 changes: 13 additions & 11 deletions src/RestSharp/Parameters/ObjectParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
namespace RestSharp;

static class ObjectParser {
public static IEnumerable<(string Name, string? Value)> GetProperties(this object obj, params string[] includedProperties) {
public static IEnumerable<ParsedParameter> GetProperties(this object obj, params string[] includedProperties) {
// automatically create parameters from object props
var type = obj.GetType();
var props = type.GetProperties();

var properties = new List<(string Name, string? Value)>();
var properties = new List<ParsedParameter>();

foreach (var prop in props.Where(x => IsAllowedProperty(x.Name))) {
var val = prop.GetValue(obj, null);
Expand All @@ -38,14 +38,14 @@ static class ObjectParser {

string? ParseValue(string? format, object? value) => format == null ? value?.ToString() : string.Format($"{{0:{format}}}", value);

IEnumerable<(string, string?)> GetArray(PropertyInfo propertyInfo, object? value) {
IEnumerable<ParsedParameter> GetArray(PropertyInfo propertyInfo, object? value) {
var elementType = propertyInfo.PropertyType.GetElementType();
var array = (Array)value!;

var attribute = propertyInfo.GetCustomAttribute<RequestPropertyAttribute>();
var name = attribute?.Name ?? propertyInfo.Name;

var name = attribute?.Name ?? propertyInfo.Name;
var queryType = attribute?.ArrayQueryType ?? RequestArrayQueryType.CommaSeparated;
var encode = attribute?.Encode ?? true;

if (array.Length > 0 && elementType != null) {
// convert the array to an array of strings
Expand All @@ -54,21 +54,20 @@ static class ObjectParser {
.Select(item => ParseValue(attribute?.Format, item));

return queryType switch {
RequestArrayQueryType.CommaSeparated => new (string, string?)[] { (name, string.Join(",", values)) },
RequestArrayQueryType.ArrayParameters => values.Select(x => ($"{name}[]", x)),
RequestArrayQueryType.CommaSeparated => new[] { new ParsedParameter(name, string.Join(",", values), encode) },
RequestArrayQueryType.ArrayParameters => values.Select(x => new ParsedParameter($"{name}[]", x, encode)),
_ => throw new ArgumentOutOfRangeException()
};

}

return new (string, string?)[] { (name, null) };
return new ParsedParameter[] { new(name, null, encode) };
}

(string, string?) GetValue(PropertyInfo propertyInfo, object? value) {
ParsedParameter GetValue(PropertyInfo propertyInfo, object? value) {
var attribute = propertyInfo.GetCustomAttribute<RequestPropertyAttribute>();
var name = attribute?.Name ?? propertyInfo.Name;
var val = ParseValue(attribute?.Format, value);
return (name, val);
return new ParsedParameter(name, val, attribute?.Encode ?? true);
}

bool IsAllowedProperty(string propertyName)
Expand All @@ -78,11 +77,14 @@ bool IsAllowedProperty(string propertyName)
}
}

record ParsedParameter(string Name, string? Value, bool Encode);

[AttributeUsage(AttributeTargets.Property)]
public class RequestPropertyAttribute : Attribute {
public string? Name { get; set; }
public string? Format { get; set; }
public RequestArrayQueryType ArrayQueryType { get; set; } = RequestArrayQueryType.CommaSeparated;
public bool Encode { get; set; } = true;
}

public enum RequestArrayQueryType { CommaSeparated, ArrayParameters }
2 changes: 1 addition & 1 deletion src/RestSharp/Parameters/UrlSegmentParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace RestSharp;

public record UrlSegmentParameter : NamedParameter {
/// <summary>
/// Instantiates a new query parameter instance that will be added to the request URL part of the query string.
/// Instantiates a new query parameter instance that will be added to the request URL by replacing part of the absolute path.
/// The request resource should have a placeholder {name} that will be replaced with the parameter value when the request is made.
/// </summary>
/// <param name="name">Parameter name</param>
Expand Down
14 changes: 7 additions & 7 deletions src/RestSharp/Request/RestRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,10 @@ public static RestRequest AddFile(
this RestRequest request,
string name,
byte[] bytes,
string filename,
string fileName,
string? contentType = null
)
=> request.AddFile(FileParameter.Create(name, bytes, filename, contentType));
=> request.AddFile(FileParameter.Create(name, bytes, fileName, contentType));

public static RestRequest AddFile(
this RestRequest request,
Expand Down Expand Up @@ -315,19 +315,19 @@ public static RestRequest AddFile(
/// <param name="request">Request instance</param>
/// <param name="name">Parameter name</param>
/// <param name="bytes">File content as bytes</param>
/// <param name="filename">File name</param>
/// <param name="fileName">File name</param>
/// <param name="contentType">Optional: content type. Default is "application/octet-stream"</param>
/// <param name="options">File parameter header options</param>
/// <returns></returns>
public static RestRequest AddFile(
this RestRequest request,
string name,
byte[] bytes,
string filename,
string fileName,
string? contentType,
FileParameterOptions? options
)
=> request.AddFile(FileParameter.Create(name, bytes, filename, contentType, options));
=> request.AddFile(FileParameter.Create(name, bytes, fileName, contentType, options));

/// <summary>
/// Adds a file attachment to the request, where the file content will be retrieved from a given stream
Expand Down Expand Up @@ -439,8 +439,8 @@ public static RestRequest AddXmlBody<T>(this RestRequest request, T obj, string
public static RestRequest AddObject<T>(this RestRequest request, T obj, params string[] includedProperties) where T : class {
var props = obj.GetProperties(includedProperties);

foreach (var (name, value) in props) {
request.AddParameter(name, value);
foreach (var prop in props) {
request.AddParameter(prop.Name, prop.Value, prop.Encode);
}

return request;
Expand Down
2 changes: 1 addition & 1 deletion src/RestSharp/Response/RestResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async Task<RestResponse> GetDefaultResponse() {
ContentType = httpResponse.Content.Headers.ContentType?.MediaType,
ResponseStatus = calculateResponseStatus(httpResponse),
ErrorException = httpResponse.MaybeException(),
ResponseUri = httpResponse.RequestMessage!.RequestUri,
ResponseUri = httpResponse.RequestMessage?.RequestUri,
Server = httpResponse.Headers.Server.ToString(),
StatusCode = httpResponse.StatusCode,
StatusDescription = httpResponse.ReasonPhrase,
Expand Down
5 changes: 5 additions & 0 deletions src/RestSharp/RestClient.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public async Task<RestResponse> ExecuteAsync(RestRequest request, CancellationTo
async Task<InternalResponse> ExecuteInternal(RestRequest request, CancellationToken cancellationToken) {
Ensure.NotNull(request, nameof(request));

// Make sure we are not disposed of when someone tries to call us!
if (_disposed) {
throw new ObjectDisposedException(nameof(RestClient));
}

using var requestContent = new RequestContent(this, request);

if (Authenticator != null) await Authenticator.Authenticate(this, request).ConfigureAwait(false);
Expand Down
10 changes: 6 additions & 4 deletions src/RestSharp/RestClientExtensions.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ public static partial class RestClientExtensions {
object parameters,
CancellationToken cancellationToken = default
) {
var props = parameters.GetProperties();
var props = parameters.GetProperties();
var request = new RestRequest(resource);

foreach (var (name, value) in props) {
Parameter parameter = resource.Contains($"{name}") ? new UrlSegmentParameter(name, value!) : new QueryParameter(name, value);
foreach (var prop in props) {
Parameter parameter = resource.Contains($"{prop.Name}")
? new UrlSegmentParameter(prop.Name, prop.Value!, prop.Encode)
: new QueryParameter(prop.Name, prop.Value, prop.Encode);
request.AddParameter(parameter);
}

Expand Down Expand Up @@ -141,4 +143,4 @@ public static async Task<HttpStatusCode> PutJsonAsync<TRequest>(
var response = await client.PutAsync(restRequest, cancellationToken).ConfigureAwait(false);
return response.StatusCode;
}
}
}
2 changes: 1 addition & 1 deletion test/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<ItemGroup Condition="$(IsTestProject) == 'true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" PrivateAssets="All"/>
<PackageReference Include="coverlet.collector" Version="3.1.2"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.2"/>
Expand Down
40 changes: 40 additions & 0 deletions test/RestSharp.Tests/ParametersTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.IO;

namespace RestSharp.Tests;

Expand Down Expand Up @@ -26,10 +27,49 @@ public void AddDefaultHeadersUsingDictionary() {
public void AddUrlSegmentWithInt() {
const string name = "foo";


var request = new RestRequest().AddUrlSegment(name, 1);
var actual = request.Parameters.FirstOrDefault(x => x.Name == name);
var expected = new UrlSegmentParameter(name, "1");

expected.Should().BeEquivalentTo(actual);
}

[Fact]
public void AddUrlSegmentModifiesUrlSegmentWithInt() {
const string name = "foo";
var pathTemplate = "/{0}/resource";
var path = String.Format(pathTemplate, "{" + name + "}");
var urlSegmentValue = 1;

var request = new RestRequest(path).AddUrlSegment(name, urlSegmentValue);

var expected = String.Format(pathTemplate, urlSegmentValue);

var client = new RestClient(BaseUrl);

var actual = client.BuildUri(request).AbsolutePath;


expected.Should().BeEquivalentTo(actual);
}

[Fact]
public void AddUrlSegmentModifiesUrlSegmentWithString() {
const string name = "foo";
var pathTemplate = "/{0}/resource";
var path = String.Format(pathTemplate, "{" + name + "}");
var urlSegmentValue = "bar";

var request = new RestRequest(path).AddUrlSegment(name, urlSegmentValue);

var expected = String.Format(pathTemplate, urlSegmentValue);

var client = new RestClient(BaseUrl);

var actual = client.BuildUri(request).AbsolutePath;

expected.Should().BeEquivalentTo(actual);

}
}