[csharp] Add option to fallback to int for Timeout (#20069)

* add option to fallback to int for Timeout

* update doc
This commit is contained in:
William Cheng 2024-11-09 15:23:57 +08:00 committed by GitHub
parent bbccd2827f
commit b34df341a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1781 additions and 4 deletions

View File

@ -51,6 +51,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|useCollection|Deserialize array types to Collection<T> instead of List<T>.| |false|
|useDateTimeForDate|Use DateTime to model date properties even if DateOnly supported. (.net 6.0+ only)| |false|
|useDateTimeOffset|Use DateTimeOffset to model date-time properties| |false|
|useIntForTimeout|Use int for Timeout (fall back to v7.9.0 templates). This option (for restsharp only) will be deprecated so please migrated to TimeSpan instead.| |false|
|useOneOfDiscriminatorLookup|Use the discriminator's mapping in oneOf to speed up the model lookup. IMPORTANT: Validation (e.g. one and only one match in oneOf's schemas) will be skipped.| |false|
|useSourceGeneration|Use source generation where available (only `generichost` library supports this option).| |false|
|validatable|Generates self-validatable models.| |true|

View File

@ -111,6 +111,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
protected boolean netStandard = Boolean.FALSE;
protected boolean supportsFileParameters = Boolean.TRUE;
protected boolean supportsDateOnly = Boolean.FALSE;
protected boolean useIntForTimeout = Boolean.FALSE;
@Setter protected boolean validatable = Boolean.TRUE;
@Setter protected boolean equatable = Boolean.FALSE;
@ -120,6 +121,8 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
private static final String OPERATION_PARAMETER_SORTING_KEY = "operationParameterSorting";
private static final String MODEL_PROPERTY_SORTING_KEY = "modelPropertySorting";
private static final String USE_INT_FOR_TIMEOUT = "useIntForTimeout";
enum SortingMethod {
DEFAULT,
ALPHABETICAL,
@ -236,6 +239,10 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
"One of legacy, alphabetical, default.",
this.modelPropertySorting.toString().toLowerCase(Locale.ROOT));
addOption(CSharpClientCodegen.USE_INT_FOR_TIMEOUT,
"Use int for Timeout (fall back to v7.9.0 templates). This option (for restsharp only) will be deprecated so please migrated to TimeSpan instead.",
String.valueOf(this.useIntForTimeout));
CliOption framework = new CliOption(
CodegenConstants.DOTNET_FRAMEWORK,
CodegenConstants.DOTNET_FRAMEWORK_DESC
@ -841,6 +848,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
syncBooleanProperty(additionalProperties, "supportsFileParameters", this::setSupportsFileParameters, this.supportsFileParameters);
syncBooleanProperty(additionalProperties, "useSourceGeneration", this::setUseSourceGeneration, this.useSourceGeneration);
syncBooleanProperty(additionalProperties, "supportsDateOnly", this::setSupportsDateOnly, this.supportsDateOnly);
syncBooleanProperty(additionalProperties, "useIntForTimeout", this::setUseIntForTimeout, this.useIntForTimeout);
final String testPackageName = testPackageName();
String packageFolder = sourceFolder + File.separator + packageName;
@ -989,9 +997,22 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
public void addSupportingFiles(final String clientPackageDir, final String packageFolder,
final AtomicReference<Boolean> excludeTests, final String testPackageFolder, final String testPackageName, final String modelPackageDir, final String authPackageDir) {
if (RESTSHARP.equals(getLibrary())) { // restsharp
if (useIntForTimeout) { // option to fall back to int for Timeout using v7.9.0 template
supportingFiles.add(new SupportingFile("ApiClient.v790.mustache", clientPackageDir, "ApiClient.cs"));
supportingFiles.add(new SupportingFile("IReadableConfiguration.v790.mustache", clientPackageDir, "IReadableConfiguration.cs"));
supportingFiles.add(new SupportingFile("Configuration.v790.mustache", clientPackageDir, "Configuration.cs"));
} else {
supportingFiles.add(new SupportingFile("ApiClient.mustache", clientPackageDir, "ApiClient.cs"));
supportingFiles.add(new SupportingFile("IReadableConfiguration.mustache", clientPackageDir, "IReadableConfiguration.cs"));
supportingFiles.add(new SupportingFile("Configuration.mustache", clientPackageDir, "Configuration.cs"));
}
} else { // other libs, e.g. httpclient
supportingFiles.add(new SupportingFile("ApiClient.mustache", clientPackageDir, "ApiClient.cs"));
supportingFiles.add(new SupportingFile("IReadableConfiguration.mustache", clientPackageDir, "IReadableConfiguration.cs"));
supportingFiles.add(new SupportingFile("Configuration.mustache", clientPackageDir, "Configuration.cs"));
}
supportingFiles.add(new SupportingFile("IApiAccessor.mustache", clientPackageDir, "IApiAccessor.cs"));
supportingFiles.add(new SupportingFile("Configuration.mustache", clientPackageDir, "Configuration.cs"));
supportingFiles.add(new SupportingFile("ApiClient.mustache", clientPackageDir, "ApiClient.cs"));
supportingFiles.add(new SupportingFile("ApiException.mustache", clientPackageDir, "ApiException.cs"));
supportingFiles.add(new SupportingFile("ApiResponse.mustache", clientPackageDir, "ApiResponse.cs"));
supportingFiles.add(new SupportingFile("ExceptionFactory.mustache", clientPackageDir, "ExceptionFactory.cs"));
@ -1017,8 +1038,6 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
supportingFiles.add(new SupportingFile("RetryConfiguration.mustache", clientPackageDir, "RetryConfiguration.cs"));
}
supportingFiles.add(new SupportingFile("IReadableConfiguration.mustache",
clientPackageDir, "IReadableConfiguration.cs"));
supportingFiles.add(new SupportingFile("GlobalConfiguration.mustache",
clientPackageDir, "GlobalConfiguration.cs"));
@ -1187,6 +1206,10 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
this.supportsDateOnly = supportsDateOnly;
}
public void setUseIntForTimeout(Boolean useIntForTimeout) {
this.useIntForTimeout = useIntForTimeout;
}
public void setSupportsRetry(Boolean supportsRetry) {
this.supportsRetry = supportsRetry;
}

View File

@ -0,0 +1,838 @@
{{>partial_header}}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
{{^net60OrLater}}
using System.Web;
{{/net60OrLater}}
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;
using RestSharp.Serializers;
using RestSharpMethod = RestSharp.Method;
using FileIO = System.IO.File;
{{#supportsRetry}}
using Polly;
{{/supportsRetry}}
{{#hasOAuthMethods}}
using {{packageName}}.Client.Auth;
{{/hasOAuthMethods}}
using {{packageName}}.{{modelPackage}};
namespace {{packageName}}.Client
{
/// <summary>
/// Allows RestSharp to Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
/// </summary>
internal class CustomJsonCodec : IRestSerializer, ISerializer, IDeserializer
{
private readonly IReadableConfiguration _configuration;
private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
{
// OpenAPI generated types generally hide default constructors.
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
OverrideSpecifiedNames = false
}
}
};
public CustomJsonCodec(IReadableConfiguration configuration)
{
_configuration = configuration;
}
public CustomJsonCodec(JsonSerializerSettings serializerSettings, IReadableConfiguration configuration)
{
_serializerSettings = serializerSettings;
_configuration = configuration;
}
/// <summary>
/// Serialize the object into a JSON string.
/// </summary>
/// <param name="obj">Object to be serialized.</param>
/// <returns>A JSON string.</returns>
public string Serialize(object obj)
{
if (obj != null && obj is AbstractOpenAPISchema)
{
// the object to be serialized is an oneOf/anyOf schema
return ((AbstractOpenAPISchema)obj).ToJson();
}
else
{
return JsonConvert.SerializeObject(obj, _serializerSettings);
}
}
public string Serialize(Parameter bodyParameter) => Serialize(bodyParameter.Value);
public T Deserialize<T>(RestResponse response)
{
var result = (T)Deserialize(response, typeof(T));
return result;
}
/// <summary>
/// Deserialize the JSON string into a proper object.
/// </summary>
/// <param name="response">The HTTP response.</param>
/// <param name="type">Object type.</param>
/// <returns>Object representation of the JSON string.</returns>
internal object Deserialize(RestResponse response, Type type)
{
if (type == typeof(byte[])) // return byte array
{
return response.RawBytes;
}
// TODO: ? if (type.IsAssignableFrom(typeof(Stream)))
if (type == typeof(Stream))
{
var bytes = response.RawBytes;
if (response.Headers != null)
{
var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath)
? global::System.IO.Path.GetTempPath()
: _configuration.TempFolderPath;
var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$");
foreach (var header in response.Headers)
{
var match = regex.Match(header.ToString());
if (match.Success)
{
string fileName = filePath + ClientUtils.SanitizeFilename(match.Groups[1].Value.Replace("\"", "").Replace("'", ""));
FileIO.WriteAllBytes(fileName, bytes);
return new FileStream(fileName, FileMode.Open);
}
}
}
var stream = new MemoryStream(bytes);
return stream;
}
if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
{
return DateTime.Parse(response.Content, null, DateTimeStyles.RoundtripKind);
}
if (type == typeof(string) || type.Name.StartsWith("System.Nullable")) // return primitive type
{
return Convert.ChangeType(response.Content, type);
}
// at this point, it must be a model (json)
try
{
return JsonConvert.DeserializeObject(response.Content, type, _serializerSettings);
}
catch (Exception e)
{
throw new ApiException(500, e.Message);
}
}
public ISerializer Serializer => this;
public IDeserializer Deserializer => this;
public string[] AcceptedContentTypes => ContentType.JsonAccept;
public SupportsContentType SupportsContentType => contentType =>
contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase) ||
contentType.Value.EndsWith("javascript", StringComparison.InvariantCultureIgnoreCase);
public ContentType ContentType { get; set; } = ContentType.Json;
public DataFormat DataFormat => DataFormat.Json;
}
{{! NOTE: Any changes related to RestSharp should be done in this class. All other client classes are for extensibility by consumers.}}
/// <summary>
/// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
/// encapsulating general REST accessor use cases.
/// </summary>
{{>visibility}} partial class ApiClient : ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
{
private readonly string _baseUrl;
/// <summary>
/// Specifies the settings on a <see cref="JsonSerializer" /> object.
/// These settings can be adjusted to accommodate custom serialization rules.
/// </summary>
public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings
{
// OpenAPI generated types generally hide default constructors.
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
OverrideSpecifiedNames = false
}
}
};
/// <summary>
/// Allows for extending request processing for <see cref="ApiClient"/> generated code.
/// </summary>
/// <param name="request">The RestSharp request object</param>
partial void InterceptRequest(RestRequest request);
/// <summary>
/// Allows for extending response processing for <see cref="ApiClient"/> generated code.
/// </summary>
/// <param name="request">The RestSharp request object</param>
/// <param name="response">The RestSharp response object</param>
partial void InterceptResponse(RestRequest request, RestResponse response);
/// <summary>
/// Initializes a new instance of the <see cref="ApiClient" />, defaulting to the global configurations' base url.
/// </summary>
public ApiClient()
{
_baseUrl = GlobalConfiguration.Instance.BasePath;
}
/// <summary>
/// Initializes a new instance of the <see cref="ApiClient" />
/// </summary>
/// <param name="basePath">The target service's base path in URL format.</param>
/// <exception cref="ArgumentException"></exception>
public ApiClient(string basePath)
{
if (string.IsNullOrEmpty(basePath))
throw new ArgumentException("basePath cannot be empty");
_baseUrl = basePath;
}
/// <summary>
/// Constructs the RestSharp version of an http method
/// </summary>
/// <param name="method">Swagger Client Custom HttpMethod</param>
/// <returns>RestSharp's HttpMethod instance.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private RestSharpMethod Method(HttpMethod method)
{
RestSharpMethod other;
switch (method)
{
case HttpMethod.Get:
other = RestSharpMethod.Get;
break;
case HttpMethod.Post:
other = RestSharpMethod.Post;
break;
case HttpMethod.Put:
other = RestSharpMethod.Put;
break;
case HttpMethod.Delete:
other = RestSharpMethod.Delete;
break;
case HttpMethod.Head:
other = RestSharpMethod.Head;
break;
case HttpMethod.Options:
other = RestSharpMethod.Options;
break;
case HttpMethod.Patch:
other = RestSharpMethod.Patch;
break;
default:
throw new ArgumentOutOfRangeException("method", method, null);
}
return other;
}
/// <summary>
/// Provides all logic for constructing a new RestSharp <see cref="RestRequest"/>.
/// At this point, all information for querying the service is known.
/// Here, it is simply mapped into the RestSharp request.
/// </summary>
/// <param name="method">The http verb.</param>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object.
/// It is assumed that any merge with GlobalConfiguration has been done before calling this method.</param>
/// <returns>[private] A new RestRequest instance.</returns>
/// <exception cref="ArgumentNullException"></exception>
private RestRequest NewRequest(
HttpMethod method,
string path,
RequestOptions options,
IReadableConfiguration configuration)
{
if (path == null) throw new ArgumentNullException("path");
if (options == null) throw new ArgumentNullException("options");
if (configuration == null) throw new ArgumentNullException("configuration");
RestRequest request = new RestRequest(path, Method(method));
if (options.PathParameters != null)
{
foreach (var pathParam in options.PathParameters)
{
request.AddParameter(pathParam.Key, pathParam.Value, ParameterType.UrlSegment);
}
}
if (options.QueryParameters != null)
{
foreach (var queryParam in options.QueryParameters)
{
foreach (var value in queryParam.Value)
{
request.AddQueryParameter(queryParam.Key, value);
}
}
}
if (configuration.DefaultHeaders != null)
{
foreach (var headerParam in configuration.DefaultHeaders)
{
request.AddHeader(headerParam.Key, headerParam.Value);
}
}
if (options.HeaderParameters != null)
{
foreach (var headerParam in options.HeaderParameters)
{
foreach (var value in headerParam.Value)
{
request.AddHeader(headerParam.Key, value);
}
}
}
if (options.FormParameters != null)
{
foreach (var formParam in options.FormParameters)
{
request.AddParameter(formParam.Key, formParam.Value);
}
}
if (options.Data != null)
{
if (options.Data is Stream stream)
{
var contentType = "application/octet-stream";
if (options.HeaderParameters != null)
{
var contentTypes = options.HeaderParameters["Content-Type"];
contentType = contentTypes[0];
}
var bytes = ClientUtils.ReadAsBytes(stream);
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
}
else
{
if (options.HeaderParameters != null)
{
var contentTypes = options.HeaderParameters["Content-Type"];
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
{
request.RequestFormat = DataFormat.Json;
}
else
{
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
}
}
else
{
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
request.RequestFormat = DataFormat.Json;
}
request.AddJsonBody(options.Data);
}
}
if (options.FileParameters != null)
{
foreach (var fileParam in options.FileParameters)
{
foreach (var file in fileParam.Value)
{
var bytes = ClientUtils.ReadAsBytes(file);
var fileStream = file as FileStream;
if (fileStream != null)
request.AddFile(fileParam.Key, bytes, global::System.IO.Path.GetFileName(fileStream.Name));
else
request.AddFile(fileParam.Key, bytes, "no_file_name_provided");
}
}
}
return request;
}
/// <summary>
/// Transforms a RestResponse instance into a new ApiResponse instance.
/// At this point, we have a concrete http response from the service.
/// Here, it is simply mapped into the [public] ApiResponse object.
/// </summary>
/// <param name="response">The RestSharp response object</param>
/// <returns>A new ApiResponse instance.</returns>
private ApiResponse<T> ToApiResponse<T>(RestResponse<T> response)
{
T result = response.Data;
string rawContent = response.Content;
var transformed = new ApiResponse<T>(response.StatusCode, new Multimap<string, string>({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
{
ErrorText = response.ErrorMessage,
Cookies = new List<Cookie>()
};
if (response.Headers != null)
{
foreach (var responseHeader in response.Headers)
{
transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
}
}
if (response.ContentHeaders != null)
{
foreach (var responseHeader in response.ContentHeaders)
{
transformed.Headers.Add(responseHeader.Name, ClientUtils.ParameterToString(responseHeader.Value));
}
}
if (response.Cookies != null)
{
foreach (var responseCookies in response.Cookies.Cast<Cookie>())
{
transformed.Cookies.Add(
new Cookie(
responseCookies.Name,
responseCookies.Value,
responseCookies.Path,
responseCookies.Domain)
);
}
}
return transformed;
}
/// <summary>
/// Executes the HTTP request for the current service.
/// Based on functions received it can be async or sync.
/// </summary>
/// <param name="getResponse">Local function that executes http request and returns http response.</param>
/// <param name="setOptions">Local function to specify options for the service.</param>
/// <param name="request">The RestSharp request object</param>
/// <param name="options">The RestSharp options object</param>
/// <param name="configuration">A per-request configuration object.
/// It is assumed that any merge with GlobalConfiguration has been done before calling this method.</param>
/// <returns>A new ApiResponse instance.</returns>
private async Task<ApiResponse<T>> ExecClientAsync<T>(Func<RestClient, Task<RestResponse<T>>> getResponse, Action<RestClientOptions> setOptions, RestRequest request, RequestOptions options, IReadableConfiguration configuration)
{
var baseUrl = configuration.GetOperationServerUrl(options.Operation, options.OperationIndex) ?? _baseUrl;
var clientOptions = new RestClientOptions(baseUrl)
{
ClientCertificates = configuration.ClientCertificates,
MaxTimeout = configuration.Timeout,
Proxy = configuration.Proxy,
UserAgent = configuration.UserAgent,
UseDefaultCredentials = configuration.UseDefaultCredentials,
RemoteCertificateValidationCallback = configuration.RemoteCertificateValidationCallback
};
setOptions(clientOptions);
{{#hasOAuthMethods}}
if (!string.IsNullOrEmpty(configuration.OAuthTokenUrl) &&
!string.IsNullOrEmpty(configuration.OAuthClientId) &&
!string.IsNullOrEmpty(configuration.OAuthClientSecret) &&
configuration.OAuthFlow != null)
{
clientOptions.Authenticator = new OAuthAuthenticator(
configuration.OAuthTokenUrl,
configuration.OAuthClientId,
configuration.OAuthClientSecret,
configuration.OAuthScope,
configuration.OAuthFlow,
SerializerSettings,
configuration);
}
{{/hasOAuthMethods}}
using (RestClient client = new RestClient(clientOptions,
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration))))
{
InterceptRequest(request);
RestResponse<T> response = await getResponse(client);
// if the response type is oneOf/anyOf, call FromJSON to deserialize the data
if (typeof(AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
{
try
{
response.Data = (T)typeof(T).GetMethod("FromJson").Invoke(null, new object[] { response.Content });
}
catch (Exception ex)
{
throw ex.InnerException != null ? ex.InnerException : ex;
}
}
else if (typeof(T).Name == "Stream") // for binary response
{
response.Data = (T)(object)new MemoryStream(response.RawBytes);
}
else if (typeof(T).Name == "Byte[]") // for byte response
{
response.Data = (T)(object)response.RawBytes;
}
else if (typeof(T).Name == "String") // for string response
{
response.Data = (T)(object)response.Content;
}
InterceptResponse(request, response);
var result = ToApiResponse(response);
if (response.ErrorMessage != null)
{
result.ErrorText = response.ErrorMessage;
}
if (response.Cookies != null && response.Cookies.Count > 0)
{
if (result.Cookies == null) result.Cookies = new List<Cookie>();
foreach (var restResponseCookie in response.Cookies.Cast<Cookie>())
{
var cookie = new Cookie(
restResponseCookie.Name,
restResponseCookie.Value,
restResponseCookie.Path,
restResponseCookie.Domain
)
{
Comment = restResponseCookie.Comment,
CommentUri = restResponseCookie.CommentUri,
Discard = restResponseCookie.Discard,
Expired = restResponseCookie.Expired,
Expires = restResponseCookie.Expires,
HttpOnly = restResponseCookie.HttpOnly,
Port = restResponseCookie.Port,
Secure = restResponseCookie.Secure,
Version = restResponseCookie.Version
};
result.Cookies.Add(cookie);
}
}
return result;
}
}
private async Task<RestResponse<T>> DeserializeRestResponseFromPolicyAsync<T>(RestClient client, RestRequest request, PolicyResult<RestResponse> policyResult, CancellationToken cancellationToken = default)
{
if (policyResult.Outcome == OutcomeType.Successful)
{
return await client.Deserialize<T>(policyResult.Result, cancellationToken);
}
else
{
return new RestResponse<T>(request)
{
ErrorException = policyResult.FinalException
};
}
}
private ApiResponse<T> Exec<T>(RestRequest request, RequestOptions options, IReadableConfiguration configuration)
{
Action<RestClientOptions> setOptions = (clientOptions) =>
{
var cookies = new CookieContainer();
if (options.Cookies != null && options.Cookies.Count > 0)
{
foreach (var cookie in options.Cookies)
{
cookies.Add(new Cookie(cookie.Name, cookie.Value));
}
}
clientOptions.CookieContainer = cookies;
};
Func<RestClient, Task<RestResponse<T>>> getResponse = (client) =>
{
if (RetryConfiguration.RetryPolicy != null)
{
var policy = RetryConfiguration.RetryPolicy;
var policyResult = policy.ExecuteAndCapture(() => client.Execute(request));
return DeserializeRestResponseFromPolicyAsync<T>(client, request, policyResult);
}
else
{
return Task.FromResult(client.Execute<T>(request));
}
};
return ExecClientAsync(getResponse, setOptions, request, options, configuration).GetAwaiter().GetResult();
}
{{#supportsAsync}}
private Task<ApiResponse<T>> ExecAsync<T>(RestRequest request, RequestOptions options, IReadableConfiguration configuration, CancellationToken cancellationToken = default(CancellationToken))
{
Action<RestClientOptions> setOptions = (clientOptions) =>
{
//no extra options
};
Func<RestClient, Task<RestResponse<T>>> getResponse = async (client) =>
{
{{#supportsRetry}}
if (RetryConfiguration.AsyncRetryPolicy != null)
{
var policy = RetryConfiguration.AsyncRetryPolicy;
var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false);
return await DeserializeRestResponseFromPolicyAsync<T>(client, request, policyResult, cancellationToken);
}
else
{
{{/supportsRetry}}
return await client.ExecuteAsync<T>(request, cancellationToken).ConfigureAwait(false);
{{#supportsRetry}}
}
{{/supportsRetry}}
};
return ExecClientAsync(getResponse, setOptions, request, options, configuration);
}
#region IAsynchronousClient
/// <summary>
/// Make a HTTP GET request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> GetAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Get, path, options, config), options, config, cancellationToken);
}
/// <summary>
/// Make a HTTP POST request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> PostAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Post, path, options, config), options, config, cancellationToken);
}
/// <summary>
/// Make a HTTP PUT request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> PutAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Put, path, options, config), options, config, cancellationToken);
}
/// <summary>
/// Make a HTTP DELETE request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> DeleteAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Delete, path, options, config), options, config, cancellationToken);
}
/// <summary>
/// Make a HTTP HEAD request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> HeadAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Head, path, options, config), options, config, cancellationToken);
}
/// <summary>
/// Make a HTTP OPTION request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> OptionsAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Options, path, options, config), options, config, cancellationToken);
}
/// <summary>
/// Make a HTTP PATCH request (async).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <param name="cancellationToken">Token that enables callers to cancel the request.</param>
/// <returns>A Task containing ApiResponse</returns>
public Task<ApiResponse<T>> PatchAsync<T>(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
{
var config = configuration ?? GlobalConfiguration.Instance;
return ExecAsync<T>(NewRequest(HttpMethod.Patch, path, options, config), options, config, cancellationToken);
}
#endregion IAsynchronousClient
{{/supportsAsync}}
#region ISynchronousClient
/// <summary>
/// Make a HTTP GET request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Get<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Get, path, options, config), options, config);
}
/// <summary>
/// Make a HTTP POST request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Post<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Post, path, options, config), options, config);
}
/// <summary>
/// Make a HTTP PUT request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Put<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Put, path, options, config), options, config);
}
/// <summary>
/// Make a HTTP DELETE request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Delete<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Delete, path, options, config), options, config);
}
/// <summary>
/// Make a HTTP HEAD request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Head<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Head, path, options, config), options, config);
}
/// <summary>
/// Make a HTTP OPTION request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Options<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Options, path, options, config), options, config);
}
/// <summary>
/// Make a HTTP PATCH request (synchronous).
/// </summary>
/// <param name="path">The target path (or resource).</param>
/// <param name="options">The additional request options.</param>
/// <param name="configuration">A per-request configuration object. It is assumed that any merge with
/// GlobalConfiguration has been done before calling this method.</param>
/// <returns>A Task containing ApiResponse</returns>
public ApiResponse<T> Patch<T>(string path, RequestOptions options, IReadableConfiguration configuration = null)
{
var config = configuration ?? GlobalConfiguration.Instance;
return Exec<T>(NewRequest(HttpMethod.Patch, path, options, config), options, config);
}
#endregion ISynchronousClient
}
}

View File

@ -0,0 +1,737 @@
{{>partial_header}}
using System;
{{^net35}}
using System.Collections.Concurrent;
{{/net35}}
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Net.Http;
using System.Net.Security;
{{#useRestSharp}}
{{#hasOAuthMethods}}using {{packageName}}.Client.Auth;
{{/hasOAuthMethods}}
{{/useRestSharp}}
namespace {{packageName}}.Client
{
/// <summary>
/// Represents a set of configuration settings
/// </summary>
{{>visibility}} class Configuration : IReadableConfiguration
{
#region Constants
/// <summary>
/// Version of the package.
/// </summary>
/// <value>Version of the package.</value>
public const string Version = "{{packageVersion}}";
/// <summary>
/// Identifier for ISO 8601 DateTime Format
/// </summary>
/// <remarks>See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 for more information.</remarks>
// ReSharper disable once InconsistentNaming
public const string ISO8601_DATETIME_FORMAT = "o";
#endregion Constants
#region Static Members
/// <summary>
/// Default creation of exceptions for a given method name and response object
/// </summary>
public static readonly ExceptionFactory DefaultExceptionFactory = (methodName, response) =>
{
var status = (int)response.StatusCode;
if (status >= 400)
{
return new ApiException(status,
string.Format("Error calling {0}: {1}", methodName, response.RawContent),
response.RawContent, response.Headers);
}
{{^netStandard}}
if (status == 0)
{
return new ApiException(status,
string.Format("Error calling {0}: {1}", methodName, response.ErrorText), response.ErrorText);
}
{{/netStandard}}
return null;
};
#endregion Static Members
#region Private Members
/// <summary>
/// Defines the base path of the target API server.
/// Example: http://localhost:3000/v1/
/// </summary>
private string _basePath;
private bool _useDefaultCredentials = false;
/// <summary>
/// Gets or sets the API key based on the authentication name.
/// This is the key and value comprising the "secret" for accessing an API.
/// </summary>
/// <value>The API key.</value>
private IDictionary<string, string> _apiKey;
/// <summary>
/// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
/// </summary>
/// <value>The prefix of the API key.</value>
private IDictionary<string, string> _apiKeyPrefix;
private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
private string _tempFolderPath = Path.GetTempPath();
{{#servers.0}}
/// <summary>
/// Gets or sets the servers defined in the OpenAPI spec.
/// </summary>
/// <value>The servers</value>
private IList<IReadOnlyDictionary<string, object>> _servers;
{{/servers.0}}
/// <summary>
/// Gets or sets the operation servers defined in the OpenAPI spec.
/// </summary>
/// <value>The operation servers</value>
private IReadOnlyDictionary<string, List<IReadOnlyDictionary<string, object>>> _operationServers;
{{#hasHttpSignatureMethods}}
/// <summary>
/// HttpSigning configuration
/// </summary>
private HttpSigningConfiguration _HttpSigningConfiguration = null;
{{/hasHttpSignatureMethods}}
#endregion Private Members
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class
/// </summary>
[global::System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
public Configuration()
{
Proxy = null;
UserAgent = WebUtility.UrlEncode("{{httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{packageVersion}}/csharp{{/httpUserAgent}}");
BasePath = "{{{basePath}}}";
DefaultHeaders = new {{^net35}}Concurrent{{/net35}}Dictionary<string, string>();
ApiKey = new {{^net35}}Concurrent{{/net35}}Dictionary<string, string>();
ApiKeyPrefix = new {{^net35}}Concurrent{{/net35}}Dictionary<string, string>();
{{#servers}}
{{#-first}}
Servers = new List<IReadOnlyDictionary<string, object>>()
{
{{/-first}}
{
new Dictionary<string, object> {
{"url", "{{{url}}}"},
{"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
{{#variables}}
{{#-first}}
{
"variables", new Dictionary<string, object> {
{{/-first}}
{
"{{{name}}}", new Dictionary<string, object> {
{"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
{"default_value", {{#isString}}{{^isEnum}}@{{/isEnum}}{{/isString}}"{{{defaultValue}}}"},
{{#enumValues}}
{{#-first}}
{
"enum_values", new List<string>() {
{{/-first}}
"{{{.}}}"{{^-last}},{{/-last}}
{{#-last}}
}
}
{{/-last}}
{{/enumValues}}
}
}{{^-last}},{{/-last}}
{{#-last}}
}
}
{{/-last}}
{{/variables}}
}
}{{^-last}},{{/-last}}
{{#-last}}
};
{{/-last}}
{{/servers}}
OperationServers = new Dictionary<string, List<IReadOnlyDictionary<string, object>>>()
{
{{#apiInfo}}
{{#apis}}
{{#operations}}
{{#operation}}
{{#servers.0}}
{
"{{{classname}}}.{{{nickname}}}", new List<IReadOnlyDictionary<string, object>>
{
{{#servers}}
{
new Dictionary<string, object>
{
{"url", "{{{url}}}"},
{"description", "{{{description}}}{{^description}}No description provided{{/description}}"}
}
},
{{/servers}}
}
},
{{/servers.0}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}
};
// Setting Timeout has side effects (forces ApiClient creation).
Timeout = 100000;
}
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class
/// </summary>
[global::System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
public Configuration(
IDictionary<string, string> defaultHeaders,
IDictionary<string, string> apiKey,
IDictionary<string, string> apiKeyPrefix,
string basePath = "{{{basePath}}}") : this()
{
if (string.{{^net35}}IsNullOrWhiteSpace{{/net35}}{{#net35}}IsNullOrEmpty{{/net35}}(basePath))
throw new ArgumentException("The provided basePath is invalid.", "basePath");
if (defaultHeaders == null)
throw new ArgumentNullException("defaultHeaders");
if (apiKey == null)
throw new ArgumentNullException("apiKey");
if (apiKeyPrefix == null)
throw new ArgumentNullException("apiKeyPrefix");
BasePath = basePath;
foreach (var keyValuePair in defaultHeaders)
{
DefaultHeaders.Add(keyValuePair);
}
foreach (var keyValuePair in apiKey)
{
ApiKey.Add(keyValuePair);
}
foreach (var keyValuePair in apiKeyPrefix)
{
ApiKeyPrefix.Add(keyValuePair);
}
}
#endregion Constructors
#region Properties
/// <summary>
/// Gets or sets the base path for API access.
/// </summary>
public virtual string BasePath
{
get { return _basePath; }
set { _basePath = value; }
}
/// <summary>
/// Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) will be sent along to the server. The default is false.
/// </summary>
public virtual bool UseDefaultCredentials
{
get { return _useDefaultCredentials; }
set { _useDefaultCredentials = value; }
}
/// <summary>
/// Gets or sets the default header.
/// </summary>
[Obsolete("Use DefaultHeaders instead.")]
public virtual IDictionary<string, string> DefaultHeader
{
get
{
return DefaultHeaders;
}
set
{
DefaultHeaders = value;
}
}
/// <summary>
/// Gets or sets the default headers.
/// </summary>
public virtual IDictionary<string, string> DefaultHeaders { get; set; }
/// <summary>
/// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds.
/// </summary>
public virtual int Timeout { get; set; }
/// <summary>
/// Gets or sets the proxy
/// </summary>
/// <value>Proxy.</value>
public virtual WebProxy Proxy { get; set; }
/// <summary>
/// Gets or sets the HTTP user agent.
/// </summary>
/// <value>Http user agent.</value>
public virtual string UserAgent { get; set; }
/// <summary>
/// Gets or sets the username (HTTP basic authentication).
/// </summary>
/// <value>The username.</value>
public virtual string Username { get; set; }
/// <summary>
/// Gets or sets the password (HTTP basic authentication).
/// </summary>
/// <value>The password.</value>
public virtual string Password { get; set; }
/// <summary>
/// Gets the API key with prefix.
/// </summary>
/// <param name="apiKeyIdentifier">API key identifier (authentication scheme).</param>
/// <returns>API key with prefix.</returns>
public string GetApiKeyWithPrefix(string apiKeyIdentifier)
{
string apiKeyValue;
ApiKey.TryGetValue(apiKeyIdentifier, out apiKeyValue);
string apiKeyPrefix;
if (ApiKeyPrefix.TryGetValue(apiKeyIdentifier, out apiKeyPrefix))
{
return apiKeyPrefix + " " + apiKeyValue;
}
return apiKeyValue;
}
/// <summary>
/// Gets or sets certificate collection to be sent with requests.
/// </summary>
/// <value>X509 Certificate collection.</value>
public X509CertificateCollection ClientCertificates { get; set; }
/// <summary>
/// Gets or sets the access token for OAuth2 authentication.
///
/// This helper property simplifies code generation.
/// </summary>
/// <value>The access token.</value>
public virtual string AccessToken { get; set; }
{{#useRestSharp}}
{{#hasOAuthMethods}}
/// <summary>
/// Gets or sets the token URL for OAuth2 authentication.
/// </summary>
/// <value>The OAuth Token URL.</value>
public virtual string OAuthTokenUrl { get; set; }
/// <summary>
/// Gets or sets the client ID for OAuth2 authentication.
/// </summary>
/// <value>The OAuth Client ID.</value>
public virtual string OAuthClientId { get; set; }
/// <summary>
/// Gets or sets the client secret for OAuth2 authentication.
/// </summary>
/// <value>The OAuth Client Secret.</value>
public virtual string OAuthClientSecret { get; set; }
/// <summary>
/// Gets or sets the client scope for OAuth2 authentication.
/// </summary>
/// <value>The OAuth Client Scope.</value>
public virtual string{{nrt?}} OAuthScope { get; set; }
/// <summary>
/// Gets or sets the flow for OAuth2 authentication.
/// </summary>
/// <value>The OAuth Flow.</value>
public virtual OAuthFlow? OAuthFlow { get; set; }
{{/hasOAuthMethods}}
{{/useRestSharp}}
/// <summary>
/// Gets or sets the temporary folder path to store the files downloaded from the server.
/// </summary>
/// <value>Folder path.</value>
public virtual string TempFolderPath
{
get { return _tempFolderPath; }
set
{
if (string.IsNullOrEmpty(value))
{
_tempFolderPath = Path.GetTempPath();
return;
}
// create the directory if it does not exist
if (!Directory.Exists(value))
{
Directory.CreateDirectory(value);
}
// check if the path contains directory separator at the end
if (value[value.Length - 1] == Path.DirectorySeparatorChar)
{
_tempFolderPath = value;
}
else
{
_tempFolderPath = value + Path.DirectorySeparatorChar;
}
}
}
/// <summary>
/// Gets or sets the date time format used when serializing in the ApiClient
/// By default, it's set to ISO 8601 - "o", for others see:
/// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
/// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx
/// No validation is done to ensure that the string you're providing is valid
/// </summary>
/// <value>The DateTimeFormat string</value>
public virtual string DateTimeFormat
{
get { return _dateTimeFormat; }
set
{
if (string.IsNullOrEmpty(value))
{
// Never allow a blank or null string, go back to the default
_dateTimeFormat = ISO8601_DATETIME_FORMAT;
return;
}
// Caution, no validation when you choose date time format other than ISO 8601
// Take a look at the above links
_dateTimeFormat = value;
}
}
/// <summary>
/// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
///
/// Whatever you set here will be prepended to the value defined in AddApiKey.
///
/// An example invocation here might be:
/// <example>
/// ApiKeyPrefix["Authorization"] = "Bearer";
/// </example>
/// … where ApiKey["Authorization"] would then be used to set the value of your bearer token.
///
/// <remarks>
/// OAuth2 workflows should set tokens via AccessToken.
/// </remarks>
/// </summary>
/// <value>The prefix of the API key.</value>
public virtual IDictionary<string, string> ApiKeyPrefix
{
get { return _apiKeyPrefix; }
set
{
if (value == null)
{
throw new InvalidOperationException("ApiKeyPrefix collection may not be null.");
}
_apiKeyPrefix = value;
}
}
/// <summary>
/// Gets or sets the API key based on the authentication name.
/// </summary>
/// <value>The API key.</value>
public virtual IDictionary<string, string> ApiKey
{
get { return _apiKey; }
set
{
if (value == null)
{
throw new InvalidOperationException("ApiKey collection may not be null.");
}
_apiKey = value;
}
}
{{#servers.0}}
/// <summary>
/// Gets or sets the servers.
/// </summary>
/// <value>The servers.</value>
public virtual IList<IReadOnlyDictionary<string, object>> Servers
{
get { return _servers; }
set
{
if (value == null)
{
throw new InvalidOperationException("Servers may not be null.");
}
_servers = value;
}
}
/// <summary>
/// Gets or sets the operation servers.
/// </summary>
/// <value>The operation servers.</value>
public virtual IReadOnlyDictionary<string, List<IReadOnlyDictionary<string, object>>> OperationServers
{
get { return _operationServers; }
set
{
if (value == null)
{
throw new InvalidOperationException("Operation servers may not be null.");
}
_operationServers = value;
}
}
/// <summary>
/// Returns URL based on server settings without providing values
/// for the variables
/// </summary>
/// <param name="index">Array index of the server settings.</param>
/// <return>The server URL.</return>
public string GetServerUrl(int index)
{
return GetServerUrl(Servers, index, null);
}
/// <summary>
/// Returns URL based on server settings.
/// </summary>
/// <param name="index">Array index of the server settings.</param>
/// <param name="inputVariables">Dictionary of the variables and the corresponding values.</param>
/// <return>The server URL.</return>
public string GetServerUrl(int index, Dictionary<string, string> inputVariables)
{
return GetServerUrl(Servers, index, inputVariables);
}
/// <summary>
/// Returns URL based on operation server settings.
/// </summary>
/// <param name="operation">Operation associated with the request path.</param>
/// <param name="index">Array index of the server settings.</param>
/// <return>The operation server URL.</return>
public string GetOperationServerUrl(string operation, int index)
{
return GetOperationServerUrl(operation, index, null);
}
/// <summary>
/// Returns URL based on operation server settings.
/// </summary>
/// <param name="operation">Operation associated with the request path.</param>
/// <param name="index">Array index of the server settings.</param>
/// <param name="inputVariables">Dictionary of the variables and the corresponding values.</param>
/// <return>The operation server URL.</return>
public string GetOperationServerUrl(string operation, int index, Dictionary<string, string> inputVariables)
{
if (operation != null && OperationServers.TryGetValue(operation, out var operationServer))
{
return GetServerUrl(operationServer, index, inputVariables);
}
return null;
}
/// <summary>
/// Returns URL based on server settings.
/// </summary>
/// <param name="servers">Dictionary of server settings.</param>
/// <param name="index">Array index of the server settings.</param>
/// <param name="inputVariables">Dictionary of the variables and the corresponding values.</param>
/// <return>The server URL.</return>
private string GetServerUrl(IList<IReadOnlyDictionary<string, object>> servers, int index, Dictionary<string, string> inputVariables)
{
if (index < 0 || index >= servers.Count)
{
throw new InvalidOperationException($"Invalid index {index} when selecting the server. Must be less than {servers.Count}.");
}
if (inputVariables == null)
{
inputVariables = new Dictionary<string, string>();
}
IReadOnlyDictionary<string, object> server = servers[index];
string url = (string)server["url"];
if (server.ContainsKey("variables"))
{
// go through each variable and assign a value
foreach (KeyValuePair<string, object> variable in (IReadOnlyDictionary<string, object>)server["variables"])
{
IReadOnlyDictionary<string, object> serverVariables = (IReadOnlyDictionary<string, object>)(variable.Value);
if (inputVariables.ContainsKey(variable.Key))
{
if (((List<string>)serverVariables["enum_values"]).Contains(inputVariables[variable.Key]))
{
url = url.Replace("{" + variable.Key + "}", inputVariables[variable.Key]);
}
else
{
throw new InvalidOperationException($"The variable `{variable.Key}` in the server URL has invalid value #{inputVariables[variable.Key]}. Must be {(List<string>)serverVariables["enum_values"]}");
}
}
else
{
// use default value
url = url.Replace("{" + variable.Key + "}", (string)serverVariables["default_value"]);
}
}
}
return url;
}
{{/servers.0}}
{{#hasHttpSignatureMethods}}
/// <summary>
/// Gets and Sets the HttpSigningConfiguration
/// </summary>
public HttpSigningConfiguration HttpSigningConfiguration
{
get { return _HttpSigningConfiguration; }
set { _HttpSigningConfiguration = value; }
}
{{/hasHttpSignatureMethods}}
/// <summary>
/// Gets and Sets the RemoteCertificateValidationCallback
/// </summary>
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
#endregion Properties
#region Methods
/// <summary>
/// Returns a string with essential information for debugging.
/// </summary>
public static string ToDebugReport()
{
string report = "C# SDK ({{{packageName}}}) Debug Report:\n";
report += " OS: " + System.Environment.OSVersion + "\n";
report += " .NET Framework Version: " + System.Environment.Version + "\n";
report += " Version of the API: {{{version}}}\n";
report += " SDK Package Version: {{{packageVersion}}}\n";
return report;
}
/// <summary>
/// Add Api Key Header.
/// </summary>
/// <param name="key">Api Key name.</param>
/// <param name="value">Api Key value.</param>
/// <returns></returns>
public void AddApiKey(string key, string value)
{
ApiKey[key] = value;
}
/// <summary>
/// Sets the API key prefix.
/// </summary>
/// <param name="key">Api Key name.</param>
/// <param name="value">Api Key value.</param>
public void AddApiKeyPrefix(string key, string value)
{
ApiKeyPrefix[key] = value;
}
#endregion Methods
#region Static Members
/// <summary>
/// Merge configurations.
/// </summary>
/// <param name="first">First configuration.</param>
/// <param name="second">Second configuration.</param>
/// <return>Merged configuration.</return>
public static IReadableConfiguration MergeConfigurations(IReadableConfiguration first, IReadableConfiguration second)
{
if (second == null) return first ?? GlobalConfiguration.Instance;
Dictionary<string, string> apiKey = first.ApiKey.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Dictionary<string, string> apiKeyPrefix = first.ApiKeyPrefix.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Dictionary<string, string> defaultHeaders = first.DefaultHeaders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
foreach (var kvp in second.ApiKey) apiKey[kvp.Key] = kvp.Value;
foreach (var kvp in second.ApiKeyPrefix) apiKeyPrefix[kvp.Key] = kvp.Value;
foreach (var kvp in second.DefaultHeaders) defaultHeaders[kvp.Key] = kvp.Value;
var config = new Configuration
{
ApiKey = apiKey,
ApiKeyPrefix = apiKeyPrefix,
DefaultHeaders = defaultHeaders,
BasePath = second.BasePath ?? first.BasePath,
Timeout = second.Timeout,
Proxy = second.Proxy ?? first.Proxy,
UserAgent = second.UserAgent ?? first.UserAgent,
Username = second.Username ?? first.Username,
Password = second.Password ?? first.Password,
AccessToken = second.AccessToken ?? first.AccessToken,
{{#useRestSharp}}
{{#hasOAuthMethods}}
OAuthTokenUrl = second.OAuthTokenUrl ?? first.OAuthTokenUrl,
OAuthClientId = second.OAuthClientId ?? first.OAuthClientId,
OAuthClientSecret = second.OAuthClientSecret ?? first.OAuthClientSecret,
OAuthScope = second.OAuthScope ?? first.OAuthScope,
OAuthFlow = second.OAuthFlow ?? first.OAuthFlow,
{{/hasOAuthMethods}}
{{/useRestSharp}}
{{#hasHttpSignatureMethods}}
HttpSigningConfiguration = second.HttpSigningConfiguration ?? first.HttpSigningConfiguration,
{{/hasHttpSignatureMethods}}
TempFolderPath = second.TempFolderPath ?? first.TempFolderPath,
DateTimeFormat = second.DateTimeFormat ?? first.DateTimeFormat,
ClientCertificates = second.ClientCertificates ?? first.ClientCertificates,
UseDefaultCredentials = second.UseDefaultCredentials,
RemoteCertificateValidationCallback = second.RemoteCertificateValidationCallback ?? first.RemoteCertificateValidationCallback,
};
return config;
}
#endregion Static Members
}
}

View File

@ -0,0 +1,178 @@
{{>partial_header}}
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
{{#useRestSharp}}
{{#hasOAuthMethods}}using {{packageName}}.Client.Auth;
{{/hasOAuthMethods}}
{{/useRestSharp}}
namespace {{packageName}}.Client
{
/// <summary>
/// Represents a readable-only configuration contract.
/// </summary>
public interface IReadableConfiguration
{
/// <summary>
/// Gets the access token.
/// </summary>
/// <value>Access token.</value>
string AccessToken { get; }
{{#useRestSharp}}
{{#hasOAuthMethods}}
/// <summary>
/// Gets the OAuth token URL.
/// </summary>
/// <value>OAuth Token URL.</value>
string OAuthTokenUrl { get; }
/// <summary>
/// Gets the OAuth client ID.
/// </summary>
/// <value>OAuth Client ID.</value>
string OAuthClientId { get; }
/// <summary>
/// Gets the OAuth client secret.
/// </summary>
/// <value>OAuth Client Secret.</value>
string OAuthClientSecret { get; }
/// <summary>
/// Gets the OAuth token scope.
/// </summary>
/// <value>OAuth Token scope.</value>
string{{nrt?}} OAuthScope { get; }
/// <summary>
/// Gets the OAuth flow.
/// </summary>
/// <value>OAuth Flow.</value>
OAuthFlow? OAuthFlow { get; }
{{/hasOAuthMethods}}
{{/useRestSharp}}
/// <summary>
/// Gets the API key.
/// </summary>
/// <value>API key.</value>
IDictionary<string, string> ApiKey { get; }
/// <summary>
/// Gets the API key prefix.
/// </summary>
/// <value>API key prefix.</value>
IDictionary<string, string> ApiKeyPrefix { get; }
/// <summary>
/// Gets the base path.
/// </summary>
/// <value>Base path.</value>
string BasePath { get; }
/// <summary>
/// Gets the date time format.
/// </summary>
/// <value>Date time format.</value>
string DateTimeFormat { get; }
/// <summary>
/// Gets the default header.
/// </summary>
/// <value>Default header.</value>
[Obsolete("Use DefaultHeaders instead.")]
IDictionary<string, string> DefaultHeader { get; }
/// <summary>
/// Gets the default headers.
/// </summary>
/// <value>Default headers.</value>
IDictionary<string, string> DefaultHeaders { get; }
/// <summary>
/// Gets the temp folder path.
/// </summary>
/// <value>Temp folder path.</value>
string TempFolderPath { get; }
/// <summary>
/// Gets the HTTP connection timeout (in milliseconds)
/// </summary>
/// <value>HTTP connection timeout.</value>
int Timeout { get; }
/// <summary>
/// Gets the proxy.
/// </summary>
/// <value>Proxy.</value>
WebProxy Proxy { get; }
/// <summary>
/// Gets the user agent.
/// </summary>
/// <value>User agent.</value>
string UserAgent { get; }
/// <summary>
/// Gets the username.
/// </summary>
/// <value>Username.</value>
string Username { get; }
/// <summary>
/// Gets the password.
/// </summary>
/// <value>Password.</value>
string Password { get; }
/// <summary>
/// Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) will be sent along to the server. The default is false.
/// </summary>
bool UseDefaultCredentials { get; }
/// <summary>
/// Get the servers associated with the operation.
/// </summary>
/// <value>Operation servers.</value>
IReadOnlyDictionary<string, List<IReadOnlyDictionary<string, object>>> OperationServers { get; }
/// <summary>
/// Gets the API key with prefix.
/// </summary>
/// <param name="apiKeyIdentifier">API key identifier (authentication scheme).</param>
/// <returns>API key with prefix.</returns>
string GetApiKeyWithPrefix(string apiKeyIdentifier);
/// <summary>
/// Gets the Operation server url at the provided index.
/// </summary>
/// <param name="operation">Operation server name.</param>
/// <param name="index">Index of the operation server settings.</param>
/// <returns></returns>
string GetOperationServerUrl(string operation, int index);
/// <summary>
/// Gets certificate collection to be sent with requests.
/// </summary>
/// <value>X509 Certificate collection.</value>
X509CertificateCollection ClientCertificates { get; }
{{#hasHttpSignatureMethods}}
/// <summary>
/// Gets the HttpSigning configuration
/// </summary>
HttpSigningConfiguration HttpSigningConfiguration { get; }
{{/hasHttpSignatureMethods}}
/// <summary>
/// Callback function for handling the validation of remote certificates. Useful for certificate pinning and
/// overriding certificate errors in the scope of a request.
/// </summary>
RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; }
}
}