// Copyright © 2009-2021 John Sheehan, Andrew Young, Alexey Zimarev and RestSharp community // // 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 RestSharp.Extensions; // ReSharper disable UnusedAutoPropertyAccessor.Global namespace RestSharp; /// <summary> /// Container for data used to make requests /// </summary> public class RestRequest { readonly Func<HttpResponseMessage, RestResponse>? _advancedResponseHandler; readonly Func<Stream, Stream?>? _responseWriter; /// <summary> /// Default constructor /// </summary> public RestRequest() => Method = Method.Get; public RestRequest(string? resource, Method method = Method.Get) : this() { Resource = resource ?? ""; Method = method; if (string.IsNullOrWhiteSpace(resource)) return; var queryStringStart = Resource.IndexOf('?'); if (queryStringStart >= 0 && Resource.IndexOf('=') > queryStringStart) { var queryParams = ParseQuery(Resource.Substring(queryStringStart + 1)); Resource = Resource.Substring(0, queryStringStart); foreach (var param in queryParams) this.AddQueryParameter(param.Key, param.Value, false); } static IEnumerable<KeyValuePair<string, string>> ParseQuery(string query) => query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) .Select( x => { var position = x.IndexOf('='); return position > 0 ? new KeyValuePair<string, string>(x.Substring(0, position), x.Substring(position + 1)) : new KeyValuePair<string, string>(x, string.Empty); } ); } public RestRequest(Uri resource, Method method = Method.Get) : this(resource.IsAbsoluteUri ? resource.AbsoluteUri : resource.OriginalString, method) { } readonly List<FileParameter> _files = new(); /// <summary> /// Always send a multipart/form-data request - even when no Files are present. /// </summary> public bool AlwaysMultipartFormData { get; set; } /// <summary> /// When set to true, parameters in a multipart form data requests will be enclosed in /// quotation marks. Default is false. Enable it if the remote endpoint requires parameters /// to be in quotes (for example, FreshDesk API). /// </summary> public bool MultipartFormQuoteParameters { get; set; } public string? FormBoundary { get; set; } /// <summary> /// Container of all HTTP parameters to be passed with the request. /// See AddParameter() for explanation of the types of parameters that can be passed /// </summary> public ParametersCollection Parameters { get; } = new(); /// <summary> /// Container of all the files to be uploaded with the request. /// </summary> public IReadOnlyCollection<FileParameter> Files => _files.AsReadOnly(); /// <summary> /// Determines what HTTP method to use for this request. Supported methods: GET, POST, PUT, DELETE, HEAD, OPTIONS /// Default is GET /// </summary> public Method Method { get; set; } /// <summary> /// Custom request timeout /// </summary> public int Timeout { get; set; } /// <summary> /// The Resource URL to make the request against. /// Tokens are substituted with UrlSegment parameters and match by name. /// Should not include the scheme or domain. Do not include leading slash. /// Combined with RestClient.BaseUrl to assemble final URL: /// {BaseUrl}/{Resource} (BaseUrl is scheme + domain, e.g. http://example.com) /// </summary> /// <example> /// // example for url token replacement /// request.Resource = "Products/{ProductId}"; /// request.AddParameter("ProductId", 123, ParameterType.UrlSegment); /// </example> public string Resource { get; set; } = ""; /// <summary> /// Serializer to use when writing request bodies. /// </summary> public DataFormat RequestFormat { get; set; } /// <summary> /// Used by the default deserializers to determine where to start deserializing from. /// Can be used to skip container or root elements that do not have corresponding deserialzation targets. /// </summary> public string? RootElement { get; set; } /// <summary> /// When supplied, the function will be called before calling the deserializer /// </summary> public Action<RestResponse>? OnBeforeDeserialization { get; set; } /// <summary> /// When supplied, the function will be called before making a request /// </summary> public Func<HttpRequestMessage, ValueTask>? OnBeforeRequest { get; set; } /// <summary> /// When supplied, the function will be called after the request is complete /// </summary> public Func<HttpResponseMessage, ValueTask>? OnAfterRequest { get; set; } internal void IncreaseNumAttempts() => Attempts++; /// <summary> /// How many attempts were made to send this Request /// </summary> /// <remarks> /// This number is incremented each time the RestClient sends the request. /// </remarks> public int Attempts { get; private set; } /// <summary> /// Completion option for <seealso cref="HttpClient"/> /// </summary> public HttpCompletionOption CompletionOption { get; set; } = HttpCompletionOption.ResponseContentRead; /// <summary> /// Set this to write response to Stream rather than reading into memory. /// </summary> public Func<Stream, Stream?>? ResponseWriter { get => _responseWriter; init { if (AdvancedResponseWriter != null) throw new ArgumentException( "AdvancedResponseWriter is not null. Only one response writer can be used." ); _responseWriter = value; } } /// <summary> /// Set this to handle the response stream yourself, based on the response details /// </summary> public Func<HttpResponseMessage, RestResponse>? AdvancedResponseWriter { get => _advancedResponseHandler; init { if (ResponseWriter != null) throw new ArgumentException("ResponseWriter is not null. Only one response writer can be used."); _advancedResponseHandler = value; } } /// <summary> /// Adds a parameter object to the request parameters /// </summary> /// <param name="parameter">Parameter to add</param> /// <returns></returns> public RestRequest AddParameter(Parameter parameter) => this.With(x => x.Parameters.AddParameter(parameter)); /// <summary> /// Removes a parameter object from the request parameters /// </summary> /// <param name="parameter">Parameter to remove</param> public RestRequest RemoveParameter(Parameter parameter) { Parameters.RemoveParameter(parameter); return this; } internal RestRequest AddFile(FileParameter file) => this.With(x => x._files.Add(file)); }