diff --git a/generation/templates/AbstractOpenAPISchema.mustache b/generation/templates/AbstractOpenAPISchema.mustache
new file mode 100644
index 00000000..05e78204
--- /dev/null
+++ b/generation/templates/AbstractOpenAPISchema.mustache
@@ -0,0 +1,68 @@
+{{>partial_header}}
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace {{packageName}}.{{modelPackage}}
+{
+ ///
+ /// Abstract base class for oneOf, anyOf schemas in the OpenAPI specification
+ ///
+ {{>visibility}} abstract partial class AbstractOpenAPISchema
+ {
+ ///
+ /// Custom JSON serializer
+ ///
+ static public readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ MissingMemberHandling = MissingMemberHandling.Error,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ ///
+ /// Custom JSON serializer for objects with additional properties
+ ///
+ static public readonly JsonSerializerSettings AdditionalPropertiesSerializerSettings = new JsonSerializerSettings
+ {
+ // OpenAPI generated types generally hide default constructors.
+ ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
+ MissingMemberHandling = MissingMemberHandling.Ignore,
+ ContractResolver = new DefaultContractResolver
+ {
+ NamingStrategy = new CamelCaseNamingStrategy
+ {
+ OverrideSpecifiedNames = false
+ }
+ }
+ };
+
+ ///
+ /// Gets or Sets the actual instance
+ ///
+ public abstract Object ActualInstance { get; set; }
+
+ ///
+ /// Gets or Sets IsNullable to indicate whether the instance is nullable
+ ///
+ public bool IsNullable { get; protected set; }
+
+ ///
+ /// Gets or Sets the schema type, which can be either `oneOf` or `anyOf`
+ ///
+ public string SchemaType { get; protected set; }
+
+ ///
+ /// Converts the instance into JSON string.
+ ///
+ public abstract string ToJson();
+ }
+}
diff --git a/generation/templates/ApiClient.mustache b/generation/templates/ApiClient.mustache
new file mode 100644
index 00000000..eb5be40d
--- /dev/null
+++ b/generation/templates/ApiClient.mustache
@@ -0,0 +1,842 @@
+{{>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
+{
+ ///
+ /// Allows RestSharp to Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
+ ///
+ 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;
+ }
+
+ ///
+ /// Serialize the object into a JSON string.
+ ///
+ /// Object to be serialized.
+ /// A JSON string.
+ 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(RestResponse response)
+ {
+ var result = (T)Deserialize(response, typeof(T));
+ return result;
+ }
+
+ ///
+ /// Deserialize the JSON string into a proper object.
+ ///
+ /// The HTTP response.
+ /// Object type.
+ /// Object representation of the JSON string.
+ 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)
+ ? 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.}}
+ ///
+ /// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
+ /// encapsulating general REST accessor use cases.
+ ///
+ {{>visibility}} partial class ApiClient : ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
+ {
+ private readonly string _baseUrl;
+
+ ///
+ /// Specifies the settings on a object.
+ /// These settings can be adjusted to accommodate custom serialization rules.
+ ///
+ 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
+ }
+ }
+ };
+
+ ///
+ /// Allows for extending request processing for generated code.
+ ///
+ /// The RestSharp request object
+ partial void InterceptRequest(RestRequest request);
+
+ ///
+ /// Allows for extending response processing for generated code.
+ ///
+ /// The RestSharp request object
+ /// The RestSharp response object
+ partial void InterceptResponse(RestRequest request, RestResponse response);
+
+ ///
+ /// Initializes a new instance of the , defaulting to the global configurations' base url.
+ ///
+ public ApiClient()
+ {
+ _baseUrl = GlobalConfiguration.Instance.BasePath;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ ///
+ /// The target service's base path in URL format.
+ ///
+ public ApiClient(string basePath)
+ {
+ if (string.IsNullOrEmpty(basePath))
+ throw new ArgumentException("basePath cannot be empty");
+
+ _baseUrl = basePath;
+ }
+
+ ///
+ /// Constructs the RestSharp version of an http method
+ ///
+ /// Swagger Client Custom HttpMethod
+ /// RestSharp's HttpMethod instance.
+ ///
+ 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;
+ }
+
+ ///
+ /// Provides all logic for constructing a new RestSharp .
+ /// At this point, all information for querying the service is known.
+ /// Here, it is simply mapped into the RestSharp request.
+ ///
+ /// The http verb.
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object.
+ /// It is assumed that any merge with GlobalConfiguration has been done before calling this method.
+ /// [private] A new RestRequest instance.
+ ///
+ 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, Path.GetFileName(fileStream.Name));
+ else
+ request.AddFile(fileParam.Key, bytes, "no_file_name_provided");
+ }
+ }
+ }
+
+ return request;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The RestSharp response object
+ /// A new ApiResponse instance.
+ private ApiResponse ToApiResponse(RestResponse response)
+ {
+ T result = response.Data;
+ string rawContent = response.Content;
+
+ var transformed = new ApiResponse(response.StatusCode, new Multimap({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
+ {
+ ErrorText = response.ErrorMessage,
+ Cookies = new List()
+ };
+
+ 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())
+ {
+ transformed.Cookies.Add(
+ new Cookie(
+ responseCookies.Name,
+ responseCookies.Value,
+ responseCookies.Path,
+ responseCookies.Domain)
+ );
+ }
+ }
+
+ return transformed;
+ }
+
+ ///
+ /// Executes the HTTP request for the current service.
+ /// Based on functions received it can be async or sync.
+ ///
+ /// Local function that executes http request and returns http response.
+ /// Local function to specify options for the service.
+ /// The RestSharp request object
+ /// The RestSharp options object
+ /// A per-request configuration object.
+ /// It is assumed that any merge with GlobalConfiguration has been done before calling this method.
+ /// A new ApiResponse instance.
+ private ApiResponse ExecClient(Func> getResponse, Action 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.OAuthFlow,
+ SerializerSettings,
+ configuration);
+ }
+
+ {{/hasOAuthMethods}}
+ using (RestClient client = new RestClient(clientOptions,
+ configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(SerializerSettings, configuration))))
+ {
+ InterceptRequest(request);
+
+ RestResponse response = 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();
+ foreach (var restResponseCookie in response.Cookies.Cast())
+ {
+ 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 RestResponse DeserializeRestResponseFromPolicy(RestClient client, RestRequest request, PolicyResult policyResult)
+ {
+ if (policyResult.Outcome == OutcomeType.Successful)
+ {
+ return client.Deserialize(policyResult.Result);
+ }
+ else
+ {
+ return new RestResponse(request)
+ {
+ ErrorException = policyResult.FinalException
+ };
+ }
+ }
+
+ private ApiResponse Exec(RestRequest request, RequestOptions options, IReadableConfiguration configuration)
+ {
+ Action 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> getResponse = (client) =>
+ {
+ if (RetryConfiguration.RetryPolicy != null)
+ {
+ var policy = RetryConfiguration.RetryPolicy;
+ var policyResult = policy.ExecuteAndCapture(() => client.Execute(request));
+ return DeserializeRestResponseFromPolicy(client, request, policyResult);
+ }
+ else
+ {
+ return client.Execute(request);
+ }
+ };
+
+ return ExecClient(getResponse, setOptions, request, options, configuration);
+ }
+
+ {{#supportsAsync}}
+ private Task> ExecAsync(RestRequest request, RequestOptions options, IReadableConfiguration configuration, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Action setOptions = (clientOptions) =>
+ {
+ //no extra options
+ };
+
+ Func> getResponse = (client) =>
+ {
+ Func>> action = async () =>
+ {
+ {{#supportsRetry}}
+ if (RetryConfiguration.AsyncRetryPolicy != null)
+ {
+ var policy = RetryConfiguration.AsyncRetryPolicy;
+ var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false);
+ return DeserializeRestResponseFromPolicy(client, request, policyResult);
+ }
+ else
+ {
+ {{/supportsRetry}}
+ return await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
+ {{#supportsRetry}}
+ }
+ {{/supportsRetry}}
+ };
+ return action().Result;
+ };
+
+ return Task.FromResult>(ExecClient(getResponse, setOptions, request, options, configuration));
+ }
+
+ #region IAsynchronousClient
+ ///
+ /// Make a HTTP GET request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> GetAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Get, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP POST request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PostAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Post, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP PUT request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PutAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Put, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP DELETE request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> DeleteAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Delete, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP HEAD request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> HeadAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Head, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP OPTION request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> OptionsAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Options, path, options, config), options, config, cancellationToken);
+ }
+
+ ///
+ /// Make a HTTP PATCH request (async).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// Token that enables callers to cancel the request.
+ /// A Task containing ApiResponse
+ public Task> PatchAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, CancellationToken cancellationToken = default)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return ExecAsync(NewRequest(HttpMethod.Patch, path, options, config), options, config, cancellationToken);
+ }
+ #endregion IAsynchronousClient
+ {{/supportsAsync}}
+
+ #region ISynchronousClient
+ ///
+ /// Make a HTTP GET request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Get(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Get, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP POST request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Post(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Post, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP PUT request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Put(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Put, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP DELETE request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Delete(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Delete, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP HEAD request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Head(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Head, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP OPTION request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Options(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Options, path, options, config), options, config);
+ }
+
+ ///
+ /// Make a HTTP PATCH request (synchronous).
+ ///
+ /// The target path (or resource).
+ /// The additional request options.
+ /// A per-request configuration object. It is assumed that any merge with
+ /// GlobalConfiguration has been done before calling this method.
+ /// A Task containing ApiResponse
+ public ApiResponse Patch(string path, RequestOptions options, IReadableConfiguration configuration = null)
+ {
+ var config = configuration ?? GlobalConfiguration.Instance;
+ return Exec(NewRequest(HttpMethod.Patch, path, options, config), options, config);
+ }
+ #endregion ISynchronousClient
+ }
+}
diff --git a/generation/templates/ApiException.mustache b/generation/templates/ApiException.mustache
new file mode 100644
index 00000000..f7dadc55
--- /dev/null
+++ b/generation/templates/ApiException.mustache
@@ -0,0 +1,60 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// API Exception
+ ///
+ {{>visibility}} class ApiException : Exception
+ {
+ ///
+ /// Gets or sets the error code (HTTP status code)
+ ///
+ /// The error code (HTTP status code).
+ public int ErrorCode { get; set; }
+
+ ///
+ /// Gets or sets the error content (body json object)
+ ///
+ /// The error content (Http response body).
+ public object ErrorContent { get; private set; }
+
+ ///
+ /// Gets or sets the HTTP headers
+ ///
+ /// HTTP headers
+ public Multimap Headers { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ApiException() { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Error message.
+ public ApiException(int errorCode, string message) : base(message)
+ {
+ this.ErrorCode = errorCode;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Error message.
+ /// Error content.
+ /// HTTP Headers.
+ public ApiException(int errorCode, string message, object errorContent = null, Multimap headers = null) : base(message)
+ {
+ this.ErrorCode = errorCode;
+ this.ErrorContent = errorContent;
+ this.Headers = headers;
+ }
+ }
+
+}
diff --git a/generation/templates/ApiResponse.mustache b/generation/templates/ApiResponse.mustache
new file mode 100644
index 00000000..161c2acd
--- /dev/null
+++ b/generation/templates/ApiResponse.mustache
@@ -0,0 +1,158 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Provides a non-generic contract for the ApiResponse wrapper.
+ ///
+ public interface IApiResponse
+ {
+ ///
+ /// The data type of
+ ///
+ Type ResponseType { get; }
+
+ ///
+ /// The content of this response
+ ///
+ Object Content { get; }
+
+ ///
+ /// Gets or sets the status code (HTTP status code)
+ ///
+ /// The status code.
+ HttpStatusCode StatusCode { get; }
+
+ ///
+ /// Gets or sets the HTTP headers
+ ///
+ /// HTTP headers
+ Multimap Headers { get; }
+
+ ///
+ /// Gets or sets any error text defined by the calling client.
+ ///
+ string ErrorText { get; set; }
+
+ ///
+ /// Gets or sets any cookies passed along on the response.
+ ///
+ List Cookies { get; set; }
+
+ ///
+ /// The raw content of this response
+ ///
+ string RawContent { get; }
+ }
+
+ ///
+ /// API Response
+ ///
+ public class ApiResponse : IApiResponse
+ {
+ #region Properties
+
+ ///
+ /// Gets or sets the status code (HTTP status code)
+ ///
+ /// The status code.
+ public HttpStatusCode StatusCode { get; }
+
+ ///
+ /// Gets or sets the HTTP headers
+ ///
+ /// HTTP headers
+ public Multimap Headers { get; }
+
+ ///
+ /// Gets or sets the data (parsed HTTP body)
+ ///
+ /// The data.
+ public T Data { get; }
+
+ ///
+ /// Gets or sets any error text defined by the calling client.
+ ///
+ public string ErrorText { get; set; }
+
+ ///
+ /// Gets or sets any cookies passed along on the response.
+ ///
+ public List Cookies { get; set; }
+
+ ///
+ /// The content of this response
+ ///
+ public Type ResponseType
+ {
+ get { return typeof(T); }
+ }
+
+ ///
+ /// The data type of
+ ///
+ public object Content
+ {
+ get { return Data; }
+ }
+
+ ///
+ /// The raw content
+ ///
+ public string RawContent { get; }
+
+ #endregion Properties
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// HTTP headers.
+ /// Data (parsed HTTP body)
+ /// Raw content.
+ public ApiResponse(HttpStatusCode statusCode, Multimap headers, T data, string rawContent)
+ {
+ StatusCode = statusCode;
+ Headers = headers;
+ Data = data;
+ RawContent = rawContent;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// HTTP headers.
+ /// Data (parsed HTTP body)
+ public ApiResponse(HttpStatusCode statusCode, Multimap headers, T data) : this(statusCode, headers, data, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Data (parsed HTTP body)
+ /// Raw content.
+ public ApiResponse(HttpStatusCode statusCode, T data, string rawContent) : this(statusCode, null, data, rawContent)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// HTTP status code.
+ /// Data (parsed HTTP body)
+ public ApiResponse(HttpStatusCode statusCode, T data) : this(statusCode, data, null)
+ {
+ }
+
+ #endregion Constructors
+ }
+}
diff --git a/generation/templates/AssemblyInfo.mustache b/generation/templates/AssemblyInfo.mustache
new file mode 100644
index 00000000..d5d937dc
--- /dev/null
+++ b/generation/templates/AssemblyInfo.mustache
@@ -0,0 +1,40 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("{{packageTitle}}")]
+[assembly: AssemblyDescription("{{packageDescription}}")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("{{packageCompany}}")]
+[assembly: AssemblyProduct("{{packageProductName}}")]
+[assembly: AssemblyCopyright("{{packageCopyright}}")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("{{packageVersion}}")]
+[assembly: AssemblyFileVersion("{{packageVersion}}")]
+{{^supportsAsync}}
+// Settings which don't support asynchronous operations rely on non-public constructors
+// This is due to how RestSharp requires the type constraint `where T : new()` in places it probably shouldn't.
+[assembly: InternalsVisibleTo("RestSharp")]
+[assembly: InternalsVisibleTo("NewtonSoft.Json")]
+[assembly: InternalsVisibleTo("JsonSubTypes")]
+{{/supportsAsync}}
diff --git a/generation/templates/ClientUtils.mustache b/generation/templates/ClientUtils.mustache
new file mode 100644
index 00000000..5e43c3d0
--- /dev/null
+++ b/generation/templates/ClientUtils.mustache
@@ -0,0 +1,261 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Text.RegularExpressions;
+{{#useCompareNetObjects}}
+using KellermanSoftware.CompareNetObjects;
+{{/useCompareNetObjects}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Utility functions providing some benefit to API client consumers.
+ ///
+ public static class ClientUtils
+ {
+ {{#useCompareNetObjects}}
+ ///
+ /// An instance of CompareLogic.
+ ///
+ public static CompareLogic compareLogic;
+
+ ///
+ /// Static constructor to initialise compareLogic.
+ ///
+ static ClientUtils()
+ {
+ {{#equatable}}
+ ComparisonConfig comparisonConfig = new{{^net70OrLater}} ComparisonConfig{{/net70OrLater}}();
+ comparisonConfig.UseHashCodeIdentifier = true;
+ {{/equatable}}
+ compareLogic = new{{^net70OrLater}} CompareLogic{{/net70OrLater}}({{#equatable}}comparisonConfig{{/equatable}});
+ }
+
+ {{/useCompareNetObjects}}
+ ///
+ /// Sanitize filename by removing the path
+ ///
+ /// Filename
+ /// Filename
+ public static string SanitizeFilename(string filename)
+ {
+ Match match = Regex.Match(filename, @".*[/\\](.*)$");
+ return match.Success ? match.Groups[1].Value : filename;
+ }
+
+ ///
+ /// Convert params to key/value pairs.
+ /// Use collectionFormat to properly format lists and collections.
+ ///
+ /// The swagger-supported collection format, one of: csv, tsv, ssv, pipes, multi
+ /// Key name.
+ /// Value object.
+ /// A multimap of keys with 1..n associated values.
+ public static Multimap ParameterToMultiMap(string collectionFormat, string name, object value)
+ {
+ var parameters = new Multimap();
+
+ if (value is ICollection collection && collectionFormat == "multi")
+ {
+ foreach (var item in collection)
+ {
+ parameters.Add(name, ParameterToString(item));
+ }
+ }
+ else if (value is IDictionary dictionary)
+ {
+ if(collectionFormat == "deepObject") {
+ foreach (DictionaryEntry entry in dictionary)
+ {
+ parameters.Add(name + "[" + entry.Key + "]", ParameterToString(entry.Value));
+ }
+ }
+ else {
+ foreach (DictionaryEntry entry in dictionary)
+ {
+ parameters.Add(entry.Key.ToString(), ParameterToString(entry.Value));
+ }
+ }
+ }
+ else
+ {
+ parameters.Add(name, ParameterToString(value));
+ }
+
+ return parameters;
+ }
+
+ ///
+ /// If parameter is DateTime, output in a formatted string (default ISO 8601), customizable with Configuration.DateTime.
+ /// If parameter is a list, join the list with ",".
+ /// Otherwise just return the string.
+ ///
+ /// The parameter (header, path, query, form).
+ /// An optional configuration instance, providing formatting options used in processing.
+ /// Formatted string.
+ public static string ParameterToString(object obj, IReadableConfiguration configuration = null)
+ {
+ if (obj is DateTime dateTime)
+ // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+ // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+ // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+ // For example: 2009-06-15T13:45:30.0000000
+ return dateTime.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+ if (obj is DateTimeOffset dateTimeOffset)
+ // Return a formatted date string - Can be customized with Configuration.DateTimeFormat
+ // Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
+ // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
+ // For example: 2009-06-15T13:45:30.0000000
+ return dateTimeOffset.ToString((configuration ?? GlobalConfiguration.Instance).DateTimeFormat);
+ if (obj is bool boolean)
+ return boolean ? "true" : "false";
+ if (obj is ICollection collection) {
+ List entries = new List();
+ foreach (var entry in collection)
+ entries.Add(ParameterToString(entry, configuration));
+ return string.Join(",", entries);
+ }
+ if (obj is Enum && HasEnumMemberAttrValue(obj))
+ return GetEnumMemberAttrValue(obj);
+
+ return Convert.ToString(obj, CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Serializes the given object when not null. Otherwise return null.
+ ///
+ /// The object to serialize.
+ /// Serialized string.
+ public static string Serialize(object obj)
+ {
+ return obj != null ? Newtonsoft.Json.JsonConvert.SerializeObject(obj) : null;
+ }
+
+ ///
+ /// Encode string in base64 format.
+ ///
+ /// string to be encoded.
+ /// Encoded string.
+ public static string Base64Encode(string text)
+ {
+ return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(text));
+ }
+
+ ///
+ /// Convert stream to byte array
+ ///
+ /// Input stream to be converted
+ /// Byte array
+ public static byte[] ReadAsBytes(Stream inputStream)
+ {
+ using (var ms = new MemoryStream())
+ {
+ inputStream.CopyTo(ms);
+ return ms.ToArray();
+ }
+ }
+
+ ///
+ /// Select the Content-Type header's value from the given content-type array:
+ /// if JSON type exists in the given array, use it;
+ /// otherwise use the first one defined in 'consumes'
+ ///
+ /// The Content-Type array to select from.
+ /// The Content-Type header to use.
+ public static string SelectHeaderContentType(string[] contentTypes)
+ {
+ if (contentTypes.Length == 0)
+ return null;
+
+ foreach (var contentType in contentTypes)
+ {
+ if (IsJsonMime(contentType))
+ return contentType;
+ }
+
+ return contentTypes[0]; // use the first content type specified in 'consumes'
+ }
+
+ ///
+ /// Select the Accept header's value from the given accepts array:
+ /// if JSON exists in the given array, use it;
+ /// otherwise use all of them (joining into a string)
+ ///
+ /// The accepts array to select from.
+ /// The Accept header to use.
+ public static string SelectHeaderAccept(string[] accepts)
+ {
+ if (accepts.Length == 0)
+ return null;
+
+ if (accepts.Contains("application/json", StringComparer.OrdinalIgnoreCase))
+ return "application/json";
+
+ return string.Join(",", accepts);
+ }
+
+ ///
+ /// Provides a case-insensitive check that a provided content type is a known JSON-like content type.
+ ///
+ public static readonly Regex JsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$");
+
+ ///
+ /// Check if the given MIME is a JSON MIME.
+ /// JSON MIME examples:
+ /// application/json
+ /// application/json; charset=UTF8
+ /// APPLICATION/JSON
+ /// application/vnd.company+json
+ ///
+ /// MIME
+ /// Returns True if MIME type is json.
+ public static bool IsJsonMime(string mime)
+ {
+ if (string.IsNullOrWhiteSpace(mime)) return false;
+
+ return JsonRegex.IsMatch(mime) || mime.Equals("application/json-patch+json");
+ }
+
+ ///
+ /// Is the Enum decorated with EnumMember Attribute
+ ///
+ ///
+ /// true if found
+ private static bool HasEnumMemberAttrValue(object enumVal)
+ {
+ if (enumVal == null)
+ throw new ArgumentNullException(nameof(enumVal));
+ var enumType = enumVal.GetType();
+ var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
+ var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType().FirstOrDefault();
+ if (attr != null) return true;
+ return false;
+ }
+
+ ///
+ /// Get the EnumMember value
+ ///
+ ///
+ /// EnumMember value as string otherwise null
+ private static string GetEnumMemberAttrValue(object enumVal)
+ {
+ if (enumVal == null)
+ throw new ArgumentNullException(nameof(enumVal));
+ var enumType = enumVal.GetType();
+ var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
+ var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType().FirstOrDefault();
+ if (attr != null)
+ {
+ return attr.Value;
+ }
+ return null;
+ }
+ }
+}
diff --git a/generation/templates/Configuration.mustache b/generation/templates/Configuration.mustache
new file mode 100644
index 00000000..a765ebf1
--- /dev/null
+++ b/generation/templates/Configuration.mustache
@@ -0,0 +1,730 @@
+{{>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
+{
+ ///
+ /// Represents a set of configuration settings
+ ///
+ {{>visibility}} class Configuration : IReadableConfiguration
+ {
+ #region Constants
+
+ ///
+ /// Version of the package.
+ ///
+ /// Version of the package.
+ public const string Version = "{{packageVersion}}";
+
+ ///
+ /// Identifier for ISO 8601 DateTime Format
+ ///
+ /// See https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8 for more information.
+ // ReSharper disable once InconsistentNaming
+ public const string ISO8601_DATETIME_FORMAT = "o";
+
+ #endregion Constants
+
+ #region Static Members
+
+ ///
+ /// Default creation of exceptions for a given method name and response object
+ ///
+ 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
+
+ ///
+ /// Defines the base path of the target API server.
+ /// Example: http://localhost:3000/v1/
+ ///
+ private string _basePath;
+
+ private bool _useDefaultCredentials = false;
+
+ ///
+ /// Gets or sets the API key based on the authentication name.
+ /// This is the key and value comprising the "secret" for accessing an API.
+ ///
+ /// The API key.
+ private IDictionary _apiKey;
+
+ ///
+ /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name.
+ ///
+ /// The prefix of the API key.
+ private IDictionary _apiKeyPrefix;
+
+ private string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
+ private string _tempFolderPath = Path.GetTempPath();
+ {{#servers.0}}
+
+ ///
+ /// Gets or sets the servers defined in the OpenAPI spec.
+ ///
+ /// The servers
+ private IList> _servers;
+ {{/servers.0}}
+
+ ///
+ /// Gets or sets the operation servers defined in the OpenAPI spec.
+ ///
+ /// The operation servers
+ private IReadOnlyDictionary>> _operationServers;
+
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// HttpSigning configuration
+ ///
+ private HttpSigningConfiguration _HttpSigningConfiguration = null;
+ {{/hasHttpSignatureMethods}}
+ #endregion Private Members
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ [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();
+ ApiKey = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ ApiKeyPrefix = new {{^net35}}Concurrent{{/net35}}Dictionary();
+ {{#servers}}
+ {{#-first}}
+ Servers = new List>()
+ {
+ {{/-first}}
+ {
+ new Dictionary {
+ {"url", "{{{url}}}"},
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+ {{#variables}}
+ {{#-first}}
+ {
+ "variables", new Dictionary {
+ {{/-first}}
+ {
+ "{{{name}}}", new Dictionary {
+ {"description", "{{{description}}}{{^description}}No description provided{{/description}}"},
+ {"default_value", {{#isString}}{{^isEnum}}@{{/isEnum}}{{/isString}}"{{{defaultValue}}}"},
+ {{#enumValues}}
+ {{#-first}}
+ {
+ "enum_values", new List() {
+ {{/-first}}
+ "{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ }
+ {{/-last}}
+ {{/enumValues}}
+ }
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ }
+ {{/-last}}
+ {{/variables}}
+ }
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ };
+ {{/-last}}
+ {{/servers}}
+ OperationServers = new Dictionary>>()
+ {
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+ {{#servers.0}}
+ {
+ "{{{classname}}}.{{{nickname}}}", new List>
+ {
+ {{#servers}}
+ {
+ new Dictionary
+ {
+ {"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;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
+ public Configuration(
+ IDictionary defaultHeaders,
+ IDictionary apiKey,
+ IDictionary 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
+
+ ///
+ /// Gets or sets the base path for API access.
+ ///
+ public virtual string BasePath
+ {
+ get { return _basePath; }
+ set { _basePath = value; }
+ }
+
+ ///
+ /// 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.
+ ///
+ public virtual bool UseDefaultCredentials
+ {
+ get { return _useDefaultCredentials; }
+ set { _useDefaultCredentials = value; }
+ }
+
+ ///
+ /// Gets or sets the default header.
+ ///
+ [Obsolete("Use DefaultHeaders instead.")]
+ public virtual IDictionary DefaultHeader
+ {
+ get
+ {
+ return DefaultHeaders;
+ }
+ set
+ {
+ DefaultHeaders = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the default headers.
+ ///
+ public virtual IDictionary DefaultHeaders { get; set; }
+
+ ///
+ /// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds.
+ ///
+ public virtual int Timeout { get; set; }
+
+ ///
+ /// Gets or sets the proxy
+ ///
+ /// Proxy.
+ public virtual WebProxy Proxy { get; set; }
+
+ ///
+ /// Gets or sets the HTTP user agent.
+ ///
+ /// Http user agent.
+ public virtual string UserAgent { get; set; }
+
+ ///
+ /// Gets or sets the username (HTTP basic authentication).
+ ///
+ /// The username.
+ public virtual string Username { get; set; }
+
+ ///
+ /// Gets or sets the password (HTTP basic authentication).
+ ///
+ /// The password.
+ public virtual string Password { get; set; }
+
+ ///
+ /// Gets the API key with prefix.
+ ///
+ /// API key identifier (authentication scheme).
+ /// API key with prefix.
+ 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;
+ }
+
+ ///
+ /// Gets or sets certificate collection to be sent with requests.
+ ///
+ /// X509 Certificate collection.
+ public X509CertificateCollection ClientCertificates { get; set; }
+
+ ///
+ /// Gets or sets the access token for OAuth2 authentication.
+ ///
+ /// This helper property simplifies code generation.
+ ///
+ /// The access token.
+ public virtual string AccessToken { get; set; }
+
+ {{#useRestSharp}}
+ {{#hasOAuthMethods}}
+ ///
+ /// Gets or sets the token URL for OAuth2 authentication.
+ ///
+ /// The OAuth Token URL.
+ public virtual string OAuthTokenUrl { get; set; }
+
+ ///
+ /// Gets or sets the client ID for OAuth2 authentication.
+ ///
+ /// The OAuth Client ID.
+ public virtual string OAuthClientId { get; set; }
+
+ ///
+ /// Gets or sets the client secret for OAuth2 authentication.
+ ///
+ /// The OAuth Client Secret.
+ public virtual string OAuthClientSecret { get; set; }
+
+ ///
+ /// Gets or sets the flow for OAuth2 authentication.
+ ///
+ /// The OAuth Flow.
+ public virtual OAuthFlow? OAuthFlow { get; set; }
+
+ {{/hasOAuthMethods}}
+ {{/useRestSharp}}
+ ///
+ /// Gets or sets the temporary folder path to store the files downloaded from the server.
+ ///
+ /// Folder path.
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// 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
+ ///
+ /// The DateTimeFormat string
+ 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;
+ }
+ }
+
+ ///
+ /// 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:
+ ///
+ /// ApiKeyPrefix["Authorization"] = "Bearer";
+ ///
+ /// … where ApiKey["Authorization"] would then be used to set the value of your bearer token.
+ ///
+ ///
+ /// OAuth2 workflows should set tokens via AccessToken.
+ ///
+ ///
+ /// The prefix of the API key.
+ public virtual IDictionary ApiKeyPrefix
+ {
+ get { return _apiKeyPrefix; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("ApiKeyPrefix collection may not be null.");
+ }
+ _apiKeyPrefix = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the API key based on the authentication name.
+ ///
+ /// The API key.
+ public virtual IDictionary ApiKey
+ {
+ get { return _apiKey; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("ApiKey collection may not be null.");
+ }
+ _apiKey = value;
+ }
+ }
+ {{#servers.0}}
+
+ ///
+ /// Gets or sets the servers.
+ ///
+ /// The servers.
+ public virtual IList> Servers
+ {
+ get { return _servers; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("Servers may not be null.");
+ }
+ _servers = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the operation servers.
+ ///
+ /// The operation servers.
+ public virtual IReadOnlyDictionary>> OperationServers
+ {
+ get { return _operationServers; }
+ set
+ {
+ if (value == null)
+ {
+ throw new InvalidOperationException("Operation servers may not be null.");
+ }
+ _operationServers = value;
+ }
+ }
+
+ ///
+ /// Returns URL based on server settings without providing values
+ /// for the variables
+ ///
+ /// Array index of the server settings.
+ /// The server URL.
+ public string GetServerUrl(int index)
+ {
+ return GetServerUrl(Servers, index, null);
+ }
+
+ ///
+ /// Returns URL based on server settings.
+ ///
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The server URL.
+ public string GetServerUrl(int index, Dictionary inputVariables)
+ {
+ return GetServerUrl(Servers, index, inputVariables);
+ }
+
+ ///
+ /// Returns URL based on operation server settings.
+ ///
+ /// Operation associated with the request path.
+ /// Array index of the server settings.
+ /// The operation server URL.
+ public string GetOperationServerUrl(string operation, int index)
+ {
+ return GetOperationServerUrl(operation, index, null);
+ }
+
+ ///
+ /// Returns URL based on operation server settings.
+ ///
+ /// Operation associated with the request path.
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The operation server URL.
+ public string GetOperationServerUrl(string operation, int index, Dictionary inputVariables)
+ {
+ if (operation != null && OperationServers.TryGetValue(operation, out var operationServer))
+ {
+ return GetServerUrl(operationServer, index, inputVariables);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns URL based on server settings.
+ ///
+ /// Dictionary of server settings.
+ /// Array index of the server settings.
+ /// Dictionary of the variables and the corresponding values.
+ /// The server URL.
+ private string GetServerUrl(IList> servers, int index, Dictionary 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();
+ }
+
+ IReadOnlyDictionary server = servers[index];
+ string url = (string)server["url"];
+
+ if (server.ContainsKey("variables"))
+ {
+ // go through each variable and assign a value
+ foreach (KeyValuePair variable in (IReadOnlyDictionary)server["variables"])
+ {
+
+ IReadOnlyDictionary serverVariables = (IReadOnlyDictionary)(variable.Value);
+
+ if (inputVariables.ContainsKey(variable.Key))
+ {
+ if (((List)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)serverVariables["enum_values"]}");
+ }
+ }
+ else
+ {
+ // use default value
+ url = url.Replace("{" + variable.Key + "}", (string)serverVariables["default_value"]);
+ }
+ }
+ }
+
+ return url;
+ }
+ {{/servers.0}}
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// Gets and Sets the HttpSigningConfiguration
+ ///
+ public HttpSigningConfiguration HttpSigningConfiguration
+ {
+ get { return _HttpSigningConfiguration; }
+ set { _HttpSigningConfiguration = value; }
+ }
+ {{/hasHttpSignatureMethods}}
+
+ ///
+ /// Gets and Sets the RemoteCertificateValidationCallback
+ ///
+ public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
+
+ #endregion Properties
+
+ #region Methods
+
+ ///
+ /// Returns a string with essential information for debugging.
+ ///
+ 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;
+ }
+
+ ///
+ /// Add Api Key Header.
+ ///
+ /// Api Key name.
+ /// Api Key value.
+ ///
+ public void AddApiKey(string key, string value)
+ {
+ ApiKey[key] = value;
+ }
+
+ ///
+ /// Sets the API key prefix.
+ ///
+ /// Api Key name.
+ /// Api Key value.
+ public void AddApiKeyPrefix(string key, string value)
+ {
+ ApiKeyPrefix[key] = value;
+ }
+
+ #endregion Methods
+
+ #region Static Members
+ ///
+ /// Merge configurations.
+ ///
+ /// First configuration.
+ /// Second configuration.
+ /// Merged configuration.
+ public static IReadableConfiguration MergeConfigurations(IReadableConfiguration first, IReadableConfiguration second)
+ {
+ if (second == null) return first ?? GlobalConfiguration.Instance;
+
+ Dictionary apiKey = first.ApiKey.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ Dictionary apiKeyPrefix = first.ApiKeyPrefix.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ Dictionary 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,
+ 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
+ }
+}
diff --git a/generation/templates/ExceptionFactory.mustache b/generation/templates/ExceptionFactory.mustache
new file mode 100644
index 00000000..4a141f6f
--- /dev/null
+++ b/generation/templates/ExceptionFactory.mustache
@@ -0,0 +1,14 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// A delegate to ExceptionFactory method
+ ///
+ /// Method name
+ /// Response
+ /// Exceptions
+ {{>visibility}} delegate Exception ExceptionFactory(string methodName, IApiResponse response);
+}
diff --git a/generation/templates/GlobalConfiguration.mustache b/generation/templates/GlobalConfiguration.mustache
new file mode 100644
index 00000000..93a9ab8a
--- /dev/null
+++ b/generation/templates/GlobalConfiguration.mustache
@@ -0,0 +1,59 @@
+{{>partial_header}}
+
+using System.Collections.Generic;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// provides a compile-time extension point for globally configuring
+ /// API Clients.
+ ///
+ ///
+ /// A customized implementation via partial class may reside in another file and may
+ /// be excluded from automatic generation via a .openapi-generator-ignore file.
+ ///
+ {{>visibility}} partial class GlobalConfiguration : Configuration
+ {
+ #region Private Members
+
+ private static readonly object GlobalConfigSync = new { };
+ private static IReadableConfiguration _globalConfiguration;
+
+ #endregion Private Members
+
+ #region Constructors
+
+ ///
+ private GlobalConfiguration()
+ {
+ }
+
+ ///
+ public GlobalConfiguration(IDictionary defaultHeader, IDictionary apiKey, IDictionary apiKeyPrefix, string basePath = "http://localhost:3000/api") : base(defaultHeader, apiKey, apiKeyPrefix, basePath)
+ {
+ }
+
+ static GlobalConfiguration()
+ {
+ Instance = new GlobalConfiguration();
+ }
+
+ #endregion Constructors
+
+ ///
+ /// Gets or sets the default Configuration.
+ ///
+ /// Configuration.
+ public static IReadableConfiguration Instance
+ {
+ get { return _globalConfiguration; }
+ set
+ {
+ lock (GlobalConfigSync)
+ {
+ _globalConfiguration = value;
+ }
+ }
+ }
+ }
+}
diff --git a/generation/templates/HttpMethod.mustache b/generation/templates/HttpMethod.mustache
new file mode 100644
index 00000000..904a042a
--- /dev/null
+++ b/generation/templates/HttpMethod.mustache
@@ -0,0 +1,25 @@
+{{>partial_header}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Http methods supported by swagger
+ ///
+ public enum HttpMethod
+ {
+ /// HTTP GET request.
+ Get,
+ /// HTTP POST request.
+ Post,
+ /// HTTP PUT request.
+ Put,
+ /// HTTP DELETE request.
+ Delete,
+ /// HTTP HEAD request.
+ Head,
+ /// HTTP OPTIONS request.
+ Options,
+ /// HTTP PATCH request.
+ Patch
+ }
+}
diff --git a/generation/templates/HttpSigningConfiguration.mustache b/generation/templates/HttpSigningConfiguration.mustache
new file mode 100644
index 00000000..9566e1f1
--- /dev/null
+++ b/generation/templates/HttpSigningConfiguration.mustache
@@ -0,0 +1,803 @@
+{{>partial_header}}
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Cryptography;
+using System.Text;
+using System.Web;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Class for HttpSigning auth related parameter and methods
+ ///
+ public class HttpSigningConfiguration
+ {
+ ///
+ /// Initialize the HashAlgorithm and SigningAlgorithm to default value
+ ///
+ public HttpSigningConfiguration()
+ {
+ HashAlgorithm = HashAlgorithmName.SHA256;
+ SigningAlgorithm = "PKCS1-v15";
+ }
+
+ ///
+ ///Gets the Api keyId
+ ///
+ public string KeyId { get; set; }
+
+ ///
+ /// Gets the Key file path
+ ///
+ public string KeyFilePath { get; set; }
+
+ ///
+ /// Specify the API key in the form of a string, either configure the KeyString property or configure the KeyFilePath property.
+ ///
+ public string KeyString { get; set; }
+
+ ///
+ /// Gets the key pass phrase for password protected key
+ ///
+ public SecureString KeyPassPhrase { get; set; }
+
+ ///
+ /// Gets the HTTP signing header
+ ///
+ public List HttpSigningHeader { get; set; }
+
+ ///
+ /// Gets the hash algorithm sha256 or sha512
+ ///
+ public HashAlgorithmName HashAlgorithm { get; set; }
+
+ ///
+ /// Gets the signing algorithm
+ ///
+ public string SigningAlgorithm { get; set; }
+
+ ///
+ /// Gets the Signature validity period in seconds
+ ///
+ public int SignatureValidityPeriod { get; set; }
+
+ private enum PrivateKeyType
+ {
+ None = 0,
+ RSA = 1,
+ ECDSA = 2,
+ }
+
+ ///
+ /// Gets the Headers for HttpSigning
+ ///
+ /// Base path
+ /// HTTP method
+ /// Path
+ /// Request options
+ /// Http signed headers
+ public Dictionary GetHttpSignedHeader(string basePath,string method, string path, RequestOptions requestOptions)
+ {
+ const string HEADER_REQUEST_TARGET = "(request-target)";
+ //The time when the HTTP signature expires. The API server should reject HTTP requests
+ //that have expired.
+ const string HEADER_EXPIRES = "(expires)";
+ //The 'Date' header.
+ const string HEADER_DATE = "Date";
+ //The 'Host' header.
+ const string HEADER_HOST = "Host";
+ //The time when the HTTP signature was generated.
+ const string HEADER_CREATED = "(created)";
+ //When the 'Digest' header is included in the HTTP signature, the client automatically
+ //computes the digest of the HTTP request body, per RFC 3230.
+ const string HEADER_DIGEST = "Digest";
+ //The 'Authorization' header is automatically generated by the client. It includes
+ //the list of signed headers and a base64-encoded signature.
+ const string HEADER_AUTHORIZATION = "Authorization";
+
+ //Read the api key from the file
+ if(File.Exists(KeyFilePath))
+ {
+ this.KeyString = ReadApiKeyFromFile(KeyFilePath);
+ }
+ else if(string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided. Supply it using either KeyFilePath or KeyString");
+ }
+
+ //Hash table to store singed headers
+ var HttpSignedRequestHeader = new Dictionary();
+ var HttpSignatureHeader = new Dictionary();
+
+ if (HttpSigningHeader.Count == 0)
+ {
+ HttpSigningHeader.Add("(created)");
+ }
+
+ if (requestOptions.PathParameters != null)
+ {
+ foreach (var pathParam in requestOptions.PathParameters)
+ {
+ var tempPath = path.Replace(pathParam.Key, "0");
+ path = string.Format(tempPath, pathParam.Value);
+ }
+ }
+
+ var httpValues = HttpUtility.ParseQueryString(string.Empty);
+ foreach (var parameter in requestOptions.QueryParameters)
+ {
+#if (NETCOREAPP)
+ if (parameter.Value.Count > 1)
+ { // array
+ foreach (var value in parameter.Value)
+ {
+ httpValues.Add(HttpUtility.UrlEncode(parameter.Key) + "[]", value);
+ }
+ }
+ else
+ {
+ httpValues.Add(HttpUtility.UrlEncode(parameter.Key), parameter.Value[0]);
+ }
+#else
+ if (parameter.Value.Count > 1)
+ { // array
+ foreach (var value in parameter.Value)
+ {
+ httpValues.Add(parameter.Key + "[]", value);
+ }
+ }
+ else
+ {
+ httpValues.Add(parameter.Key, parameter.Value[0]);
+ }
+#endif
+ }
+ var uriBuilder = new UriBuilder(string.Concat(basePath, path));
+ uriBuilder.Query = httpValues.ToString().Replace("+", "%20");
+
+ var dateTime = DateTime.Now;
+ string Digest = string.Empty;
+
+ //get the body
+ string requestBody = string.Empty;
+ if (requestOptions.Data != null)
+ {
+ var serializerSettings = new JsonSerializerSettings();
+ requestBody = JsonConvert.SerializeObject(requestOptions.Data, serializerSettings);
+ }
+
+ if (HashAlgorithm == HashAlgorithmName.SHA256)
+ {
+ var bodyDigest = GetStringHash(HashAlgorithm, requestBody);
+ Digest = string.Format("SHA-256={0}", Convert.ToBase64String(bodyDigest));
+ }
+ else if (HashAlgorithm == HashAlgorithmName.SHA512)
+ {
+ var bodyDigest = GetStringHash(HashAlgorithm, requestBody);
+ Digest = string.Format("SHA-512={0}", Convert.ToBase64String(bodyDigest));
+ }
+ else
+ {
+ throw new Exception(string.Format("{0} not supported", HashAlgorithm));
+ }
+
+ foreach (var header in HttpSigningHeader)
+ {
+ if (header.Equals(HEADER_REQUEST_TARGET))
+ {
+ var targetUrl = string.Format("{0} {1}{2}", method.ToLower(), uriBuilder.Path, uriBuilder.Query);
+ HttpSignatureHeader.Add(header.ToLower(), targetUrl);
+ }
+ else if (header.Equals(HEADER_EXPIRES))
+ {
+ var expireDateTime = dateTime.AddSeconds(SignatureValidityPeriod);
+ HttpSignatureHeader.Add(header.ToLower(), GetUnixTime(expireDateTime).ToString());
+ }
+ else if (header.Equals(HEADER_DATE))
+ {
+ var utcDateTime = dateTime.ToUniversalTime().ToString("r");
+ HttpSignatureHeader.Add(header.ToLower(), utcDateTime);
+ HttpSignedRequestHeader.Add(HEADER_DATE, utcDateTime);
+ }
+ else if (header.Equals(HEADER_HOST))
+ {
+ HttpSignatureHeader.Add(header.ToLower(), uriBuilder.Host);
+ HttpSignedRequestHeader.Add(HEADER_HOST, uriBuilder.Host);
+ }
+ else if (header.Equals(HEADER_CREATED))
+ {
+ HttpSignatureHeader.Add(header.ToLower(), GetUnixTime(dateTime).ToString());
+ }
+ else if (header.Equals(HEADER_DIGEST))
+ {
+ HttpSignedRequestHeader.Add(HEADER_DIGEST, Digest);
+ HttpSignatureHeader.Add(header.ToLower(), Digest);
+ }
+ else
+ {
+ bool isHeaderFound = false;
+ foreach (var item in requestOptions.HeaderParameters)
+ {
+ if (string.Equals(item.Key, header, StringComparison.OrdinalIgnoreCase))
+ {
+ HttpSignatureHeader.Add(header.ToLower(), item.Value.ToString());
+ isHeaderFound = true;
+ break;
+ }
+ }
+ if (!isHeaderFound)
+ {
+ throw new Exception(string.Format("Cannot sign HTTP request.Request does not contain the {0} header.",header));
+ }
+ }
+
+ }
+ var headersKeysString = string.Join(" ", HttpSignatureHeader.Keys);
+ var headerValuesList = new List();
+
+ foreach (var keyVal in HttpSignatureHeader)
+ {
+ headerValuesList.Add(string.Format("{0}: {1}", keyVal.Key, keyVal.Value));
+ }
+ //Concatenate headers value separated by new line
+ var headerValuesString = string.Join("\n", headerValuesList);
+ var signatureStringHash = GetStringHash(HashAlgorithm, headerValuesString);
+ string headerSignatureStr = null;
+ var keyType = GetKeyType(KeyString);
+
+ if (keyType == PrivateKeyType.RSA)
+ {
+ headerSignatureStr = GetRSASignature(signatureStringHash);
+ }
+ else if (keyType == PrivateKeyType.ECDSA)
+ {
+ headerSignatureStr = GetECDSASignature(signatureStringHash);
+ }
+ else
+ {
+ throw new Exception(string.Format("Private key type {0} not supported", keyType));
+ }
+ const string cryptographicScheme = "hs2019";
+ var authorizationHeaderValue = string.Format("Signature keyId=\"{0}\",algorithm=\"{1}\"",
+ KeyId, cryptographicScheme);
+
+ if (HttpSignatureHeader.ContainsKey(HEADER_CREATED))
+ {
+ authorizationHeaderValue += string.Format(",created={0}", HttpSignatureHeader[HEADER_CREATED]);
+ }
+
+ if (HttpSignatureHeader.ContainsKey(HEADER_EXPIRES))
+ {
+ authorizationHeaderValue += string.Format(",expires={0}", HttpSignatureHeader[HEADER_EXPIRES]);
+ }
+
+ authorizationHeaderValue += string.Format(",headers=\"{0}\",signature=\"{1}\"",
+ headersKeysString, headerSignatureStr);
+ HttpSignedRequestHeader.Add(HEADER_AUTHORIZATION, authorizationHeaderValue);
+ return HttpSignedRequestHeader;
+ }
+
+ private byte[] GetStringHash(HashAlgorithmName hashAlgorithmName, string stringToBeHashed)
+ {
+ HashAlgorithm{{nrt?}} hashAlgorithm = null;
+
+ if (hashAlgorithmName == HashAlgorithmName.SHA1)
+ hashAlgorithm = SHA1.Create();
+
+ if (hashAlgorithmName == HashAlgorithmName.SHA256)
+ hashAlgorithm = SHA256.Create();
+
+ if (hashAlgorithmName == HashAlgorithmName.SHA512)
+ hashAlgorithm = SHA512.Create();
+
+ if (hashAlgorithmName == HashAlgorithmName.MD5)
+ hashAlgorithm = MD5.Create();
+
+ if (hashAlgorithm == null)
+ throw new NullReferenceException($"{ nameof(hashAlgorithm) } was null.");
+
+ byte[] bytes = Encoding.UTF8.GetBytes(stringToBeHashed);
+ byte[] stringHash = hashAlgorithm.ComputeHash(bytes);
+ return stringHash;
+ }
+
+ private int GetUnixTime(DateTime date2)
+ {
+ DateTime date1 = new DateTime(1970, 01, 01);
+ TimeSpan timeSpan = date2 - date1;
+ return (int)timeSpan.TotalSeconds;
+ }
+
+ private string GetRSASignature(byte[] stringToSign)
+ {
+ if (string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+ RSA rsa = GetRSAProviderFromPemFile(KeyString, KeyPassPhrase);
+ if (SigningAlgorithm == "RSASSA-PSS")
+ {
+ var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pss);
+ return Convert.ToBase64String(signedbytes);
+ }
+ else if (SigningAlgorithm == "PKCS1-v15")
+ {
+ var signedbytes = rsa.SignHash(stringToSign, HashAlgorithm, RSASignaturePadding.Pkcs1);
+ return Convert.ToBase64String(signedbytes);
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+
+ ///
+ /// Gets the ECDSA signature
+ ///
+ ///
+ /// ECDSA signature
+ private string GetECDSASignature(byte[] dataToSign)
+ {
+ {{#net60OrLater}}
+ if (!File.Exists(KeyFilePath) && string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+
+ var keyStr = KeyString;
+ const string ecKeyHeader = "-----BEGIN EC PRIVATE KEY-----";
+ const string ecKeyFooter = "-----END EC PRIVATE KEY-----";
+ var ecKeyBase64String = keyStr.Replace(ecKeyHeader, "").Replace(ecKeyFooter, "").Trim();
+ var keyBytes = System.Convert.FromBase64String(ecKeyBase64String);
+ var ecdsa = ECDsa.Create();
+
+ var byteCount = 0;
+ if (KeyPassPhrase != null)
+ {
+ IntPtr unmanagedString = IntPtr.Zero;
+ try
+ {
+ // convert secure string to byte array
+ unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(KeyPassPhrase);
+ ecdsa.ImportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(Marshal.PtrToStringUni(unmanagedString)), keyBytes, out byteCount);
+ }
+ finally
+ {
+ if (unmanagedString != IntPtr.Zero)
+ {
+ Marshal.ZeroFreeBSTR(unmanagedString);
+ }
+ }
+ }
+ else
+ ecdsa.ImportPkcs8PrivateKey(keyBytes, out byteCount);
+
+ var derBytes = ecdsa.SignHash(dataToSign, DSASignatureFormat.Rfc3279DerSequence);
+ var signedString = System.Convert.ToBase64String(derBytes);
+
+ return signedString;
+ {{/net60OrLater}}
+ {{^net60OrLater}}
+ throw new Exception("ECDSA signing is supported only on NETCOREAPP3_0 and above");
+ {{/net60OrLater}}
+ }
+
+ ///
+ /// Convert ANS1 format to DER format. Not recommended to use because it generate inavlid signature occationally.
+ ///
+ ///
+ ///
+ private byte[] ConvertToECDSAANS1Format(byte[] signedBytes)
+ {
+ var derBytes = new List();
+ byte derLength = 68; //default length for ECDSA code signing bit 0x44
+ byte rbytesLength = 32; //R length 0x20
+ byte sbytesLength = 32; //S length 0x20
+ var rBytes = new List();
+ var sBytes = new List();
+ for (int i = 0; i < 32; i++)
+ {
+ rBytes.Add(signedBytes[i]);
+ }
+ for (int i = 32; i < 64; i++)
+ {
+ sBytes.Add(signedBytes[i]);
+ }
+
+ if (rBytes[0] > 0x7F)
+ {
+ derLength++;
+ rbytesLength++;
+ var tempBytes = new List();
+ tempBytes.AddRange(rBytes);
+ rBytes.Clear();
+ rBytes.Add(0x00);
+ rBytes.AddRange(tempBytes);
+ }
+
+ if (sBytes[0] > 0x7F)
+ {
+ derLength++;
+ sbytesLength++;
+ var tempBytes = new List();
+ tempBytes.AddRange(sBytes);
+ sBytes.Clear();
+ sBytes.Add(0x00);
+ sBytes.AddRange(tempBytes);
+
+ }
+
+ derBytes.Add(48); //start of the sequence 0x30
+ derBytes.Add(derLength); //total length r length, type and r bytes
+
+ derBytes.Add(2); //tag for integer
+ derBytes.Add(rbytesLength); //length of r
+ derBytes.AddRange(rBytes);
+
+ derBytes.Add(2); //tag for integer
+ derBytes.Add(sbytesLength); //length of s
+ derBytes.AddRange(sBytes);
+ return derBytes.ToArray();
+ }
+
+ private RSACryptoServiceProvider GetRSAProviderFromPemFile(string keyString, SecureString keyPassPhrase = null)
+ {
+ if (string.IsNullOrEmpty(KeyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+
+ const string pempubheader = "-----BEGIN PUBLIC KEY-----";
+ const string pempubfooter = "-----END PUBLIC KEY-----";
+ bool isPrivateKeyFile = true;
+ byte[] pemkey = null;
+ string pemstr = keyString;
+
+ if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
+ {
+ isPrivateKeyFile = false;
+ }
+
+ if (isPrivateKeyFile)
+ {
+ pemkey = ConvertPrivateKeyToBytes(pemstr, keyPassPhrase);
+ if (pemkey == null)
+ {
+ return null;
+ }
+ return DecodeRSAPrivateKey(pemkey);
+ }
+ return null;
+ }
+
+ private byte[] ConvertPrivateKeyToBytes(string instr, SecureString keyPassPhrase = null)
+ {
+ const string pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
+ const string pemprivfooter = "-----END RSA PRIVATE KEY-----";
+ string pemstr = instr.Trim();
+ byte[] binkey;
+
+ if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
+ {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder(pemstr);
+ sb.Replace(pemprivheader, "");
+ sb.Replace(pemprivfooter, "");
+ string pvkstr = sb.ToString().Trim();
+
+ try
+ { // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
+ binkey = Convert.FromBase64String(pvkstr);
+ return binkey;
+ }
+ catch (System.FormatException)
+ {
+ StringReader str = new StringReader(pvkstr);
+
+ //-------- read PEM encryption info. lines and extract salt -----
+ if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
+ {
+ return null;
+ }
+ string saltline = str.ReadLine();
+ if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
+ {
+ return null;
+ }
+ string saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
+ byte[] salt = new byte[saltstr.Length / 2];
+ for (int i = 0; i < salt.Length; i++)
+ salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
+ if (str.ReadLine() != "")
+ {
+ return null;
+ }
+
+ //------ remaining b64 data is encrypted RSA key ----
+ string encryptedstr = str.ReadToEnd();
+
+ try
+ { //should have b64 encrypted RSA key now
+ binkey = Convert.FromBase64String(encryptedstr);
+ }
+ catch (System.FormatException)
+ { //data is not in base64 format
+ return null;
+ }
+
+ byte[] deskey = GetEncryptedKey(salt, keyPassPhrase, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
+ if (deskey == null)
+ {
+ return null;
+ }
+
+ //------ Decrypt the encrypted 3des-encrypted RSA private key ------
+ byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
+ return rsakey;
+ }
+ }
+
+ private RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
+ {
+ byte[] bytesModulus, bytesE, bytesD, bytesP, bytesQ, bytesDP, bytesDQ, bytesIQ;
+
+ // --------- Set up stream to decode the asn.1 encoded RSA private key ------
+ MemoryStream mem = new MemoryStream(privkey);
+ BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
+ byte bt = 0;
+ ushort twobytes = 0;
+ int elems = 0;
+ try
+ {
+ twobytes = binr.ReadUInt16();
+ if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
+ {
+ binr.ReadByte(); //advance 1 byte
+ }
+ else if (twobytes == 0x8230)
+ {
+ binr.ReadInt16(); //advance 2 bytes
+ }
+ else
+ {
+ return null;
+ }
+
+ twobytes = binr.ReadUInt16();
+ if (twobytes != 0x0102) //version number
+ {
+ return null;
+ }
+ bt = binr.ReadByte();
+ if (bt != 0x00)
+ {
+ return null;
+ }
+
+ //------ all private key components are Integer sequences ----
+ elems = GetIntegerSize(binr);
+ bytesModulus = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesE = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesD = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesP = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesQ = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesDP = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesDQ = binr.ReadBytes(elems);
+
+ elems = GetIntegerSize(binr);
+ bytesIQ = binr.ReadBytes(elems);
+
+ // ------- create RSACryptoServiceProvider instance and initialize with public key -----
+ RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
+ RSAParameters RSAparams = new RSAParameters();
+ RSAparams.Modulus = bytesModulus;
+ RSAparams.Exponent = bytesE;
+ RSAparams.D = bytesD;
+ RSAparams.P = bytesP;
+ RSAparams.Q = bytesQ;
+ RSAparams.DP = bytesDP;
+ RSAparams.DQ = bytesDQ;
+ RSAparams.InverseQ = bytesIQ;
+ RSA.ImportParameters(RSAparams);
+ return RSA;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ finally
+ {
+ binr.Close();
+ }
+ }
+
+ private int GetIntegerSize(BinaryReader binr)
+ {
+ byte bt = 0;
+ byte lowbyte = 0x00;
+ byte highbyte = 0x00;
+ int count = 0;
+ bt = binr.ReadByte();
+ if (bt != 0x02) //expect integer
+ {
+ return 0;
+ }
+ bt = binr.ReadByte();
+
+ if (bt == 0x81)
+ {
+ count = binr.ReadByte(); // data size in next byte
+ }
+ else if (bt == 0x82)
+ {
+ highbyte = binr.ReadByte(); // data size in next 2 bytes
+ lowbyte = binr.ReadByte();
+ byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+ count = BitConverter.ToInt32(modint, 0);
+ }
+ else
+ {
+ count = bt; // we already have the data size
+ }
+ while (binr.ReadByte() == 0x00)
+ {
+ //remove high order zeros in data
+ count -= 1;
+ }
+ binr.BaseStream.Seek(-1, SeekOrigin.Current);
+ //last ReadByte wasn't a removed zero, so back up a byte
+ return count;
+ }
+
+ private byte[] GetEncryptedKey(byte[] salt, SecureString secpswd, int count, int miter)
+ {
+ IntPtr unmanagedPswd = IntPtr.Zero;
+ const int HASHLENGTH = 16; //MD5 bytes
+ byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store concatenated Mi hashed results
+
+ byte[] psbytes = new byte[secpswd.Length];
+ unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
+ Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
+ Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
+
+ // --- concatenate salt and pswd bytes into fixed data array ---
+ byte[] data00 = new byte[psbytes.Length + salt.Length];
+ Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
+ Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
+
+ // ---- do multi-hashing and concatenate results D1, D2 ... into keymaterial bytes ----
+ MD5 md5 = MD5.Create();
+ byte[] result = null;
+ byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
+
+ for (int j = 0; j < miter; j++)
+ {
+ // ---- Now hash consecutively for count times ------
+ if (j == 0)
+ {
+ result = data00; //initialize
+ }
+ else
+ {
+ Array.Copy(result, hashtarget, result.Length);
+ Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
+ result = hashtarget;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ result = md5.ComputeHash(result);
+ }
+ Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //concatenate to keymaterial
+ }
+ byte[] deskey = new byte[24];
+ Array.Copy(keymaterial, deskey, deskey.Length);
+
+ Array.Clear(psbytes, 0, psbytes.Length);
+ Array.Clear(data00, 0, data00.Length);
+ Array.Clear(result, 0, result.Length);
+ Array.Clear(hashtarget, 0, hashtarget.Length);
+ Array.Clear(keymaterial, 0, keymaterial.Length);
+ return deskey;
+ }
+
+ private byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
+ {
+ MemoryStream memst = new MemoryStream();
+ TripleDES alg = TripleDES.Create();
+ alg.Key = desKey;
+ alg.IV = IV;
+ try
+ {
+ CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
+ cs.Write(cipherData, 0, cipherData.Length);
+ cs.Close();
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ byte[] decryptedData = memst.ToArray();
+ return decryptedData;
+ }
+
+ ///
+ /// Detect the key type from the pem file.
+ ///
+ /// api key in string format
+ /// Private Key Type
+ private PrivateKeyType GetKeyType(string keyString)
+ {
+ string[] key = null;
+
+ if (string.IsNullOrEmpty(keyString))
+ {
+ throw new Exception("No API key has been provided.");
+ }
+
+ const string ecPrivateKeyHeader = "BEGIN EC PRIVATE KEY";
+ const string ecPrivateKeyFooter = "END EC PRIVATE KEY";
+ const string rsaPrivateKeyHeader = "BEGIN RSA PRIVATE KEY";
+ const string rsaPrivateFooter = "END RSA PRIVATE KEY";
+ //var pkcs8Header = "BEGIN PRIVATE KEY";
+ //var pkcs8Footer = "END PRIVATE KEY";
+ PrivateKeyType keyType;
+ key = KeyString.TrimEnd().Split('\n');
+
+ if (key[0].Contains(rsaPrivateKeyHeader) &&
+ key[key.Length - 1].ToString().Contains(rsaPrivateFooter))
+ {
+ keyType = PrivateKeyType.RSA;
+ }
+ else if (key[0].Contains(ecPrivateKeyHeader) &&
+ key[key.Length - 1].ToString().Contains(ecPrivateKeyFooter))
+ {
+ keyType = PrivateKeyType.ECDSA;
+ }
+ else
+ {
+ throw new Exception("The key file path does not exist or key is invalid or key is not supported");
+ }
+ return keyType;
+ }
+
+ ///
+ /// Read the api key form the api key file path and stored it in KeyString property.
+ ///
+ /// api key file path
+ private string ReadApiKeyFromFile(string apiKeyFilePath)
+ {
+ string apiKeyString = null;
+
+ if(File.Exists(apiKeyFilePath))
+ {
+ apiKeyString = File.ReadAllText(apiKeyFilePath);
+ }
+ else
+ {
+ throw new Exception("Provided API key file path does not exists.");
+ }
+ return apiKeyString;
+ }
+ }
+}
diff --git a/generation/templates/IApiAccessor.mustache b/generation/templates/IApiAccessor.mustache
new file mode 100644
index 00000000..a269f56e
--- /dev/null
+++ b/generation/templates/IApiAccessor.mustache
@@ -0,0 +1,29 @@
+{{>partial_header}}
+
+using System;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Represents configuration aspects required to interact with the API endpoints.
+ ///
+ {{>visibility}} interface IApiAccessor
+ {
+ ///
+ /// Gets or sets the configuration object
+ ///
+ /// An instance of the Configuration
+ IReadableConfiguration Configuration { get; set; }
+
+ ///
+ /// Gets the base path of the API client.
+ ///
+ /// The base path
+ string GetBasePath();
+
+ ///
+ /// Provides a factory method hook for the creation of exceptions.
+ ///
+ ExceptionFactory ExceptionFactory { get; set; }
+ }
+}
diff --git a/generation/templates/IAsynchronousClient.mustache b/generation/templates/IAsynchronousClient.mustache
new file mode 100644
index 00000000..f0c88fae
--- /dev/null
+++ b/generation/templates/IAsynchronousClient.mustache
@@ -0,0 +1,92 @@
+{{>partial_header}}
+
+using System;
+using System.Threading.Tasks;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Contract for Asynchronous RESTful API interactions.
+ ///
+ /// This interface allows consumers to provide a custom API accessor client.
+ ///
+ public interface IAsynchronousClient
+ {
+ ///
+ /// Executes a non-blocking call to some using the GET http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> GetAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// Executes a non-blocking call to some using the POST http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> PostAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// Executes a non-blocking call to some using the PUT http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> PutAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// Executes a non-blocking call to some using the DELETE http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> DeleteAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// Executes a non-blocking call to some using the HEAD http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> HeadAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// Executes a non-blocking call to some using the OPTIONS http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> OptionsAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// Executes a non-blocking call to some using the PATCH http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// Cancellation Token to cancel the request.
+ /// The return type.
+ /// A task eventually representing the response data, decorated with
+ Task> PatchAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+ }
+}
diff --git a/generation/templates/IReadableConfiguration.mustache b/generation/templates/IReadableConfiguration.mustache
new file mode 100644
index 00000000..78c998b3
--- /dev/null
+++ b/generation/templates/IReadableConfiguration.mustache
@@ -0,0 +1,172 @@
+{{>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
+{
+ ///
+ /// Represents a readable-only configuration contract.
+ ///
+ public interface IReadableConfiguration
+ {
+ ///
+ /// Gets the access token.
+ ///
+ /// Access token.
+ string AccessToken { get; }
+
+ {{#useRestSharp}}
+ {{#hasOAuthMethods}}
+ ///
+ /// Gets the OAuth token URL.
+ ///
+ /// OAuth Token URL.
+ string OAuthTokenUrl { get; }
+
+ ///
+ /// Gets the OAuth client ID.
+ ///
+ /// OAuth Client ID.
+ string OAuthClientId { get; }
+
+ ///
+ /// Gets the OAuth client secret.
+ ///
+ /// OAuth Client Secret.
+ string OAuthClientSecret { get; }
+
+ ///
+ /// Gets the OAuth flow.
+ ///
+ /// OAuth Flow.
+ OAuthFlow? OAuthFlow { get; }
+
+ {{/hasOAuthMethods}}
+ {{/useRestSharp}}
+ ///
+ /// Gets the API key.
+ ///
+ /// API key.
+ IDictionary ApiKey { get; }
+
+ ///
+ /// Gets the API key prefix.
+ ///
+ /// API key prefix.
+ IDictionary ApiKeyPrefix { get; }
+
+ ///
+ /// Gets the base path.
+ ///
+ /// Base path.
+ string BasePath { get; }
+
+ ///
+ /// Gets the date time format.
+ ///
+ /// Date time format.
+ string DateTimeFormat { get; }
+
+ ///
+ /// Gets the default header.
+ ///
+ /// Default header.
+ [Obsolete("Use DefaultHeaders instead.")]
+ IDictionary DefaultHeader { get; }
+
+ ///
+ /// Gets the default headers.
+ ///
+ /// Default headers.
+ IDictionary DefaultHeaders { get; }
+
+ ///
+ /// Gets the temp folder path.
+ ///
+ /// Temp folder path.
+ string TempFolderPath { get; }
+
+ ///
+ /// Gets the HTTP connection timeout (in milliseconds)
+ ///
+ /// HTTP connection timeout.
+ int Timeout { get; }
+
+ ///
+ /// Gets the proxy.
+ ///
+ /// Proxy.
+ WebProxy Proxy { get; }
+
+ ///
+ /// Gets the user agent.
+ ///
+ /// User agent.
+ string UserAgent { get; }
+
+ ///
+ /// Gets the username.
+ ///
+ /// Username.
+ string Username { get; }
+
+ ///
+ /// Gets the password.
+ ///
+ /// Password.
+ string Password { get; }
+
+ ///
+ /// 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.
+ ///
+ bool UseDefaultCredentials { get; }
+
+ ///
+ /// Get the servers associated with the operation.
+ ///
+ /// Operation servers.
+ IReadOnlyDictionary>> OperationServers { get; }
+
+ ///
+ /// Gets the API key with prefix.
+ ///
+ /// API key identifier (authentication scheme).
+ /// API key with prefix.
+ string GetApiKeyWithPrefix(string apiKeyIdentifier);
+
+ ///
+ /// Gets the Operation server url at the provided index.
+ ///
+ /// Operation server name.
+ /// Index of the operation server settings.
+ ///
+ string GetOperationServerUrl(string operation, int index);
+
+ ///
+ /// Gets certificate collection to be sent with requests.
+ ///
+ /// X509 Certificate collection.
+ X509CertificateCollection ClientCertificates { get; }
+ {{#hasHttpSignatureMethods}}
+
+ ///
+ /// Gets the HttpSigning configuration
+ ///
+ HttpSigningConfiguration HttpSigningConfiguration { get; }
+ {{/hasHttpSignatureMethods}}
+
+ ///
+ /// Callback function for handling the validation of remote certificates. Useful for certificate pinning and
+ /// overriding certificate errors in the scope of a request.
+ ///
+ RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; }
+ }
+}
diff --git a/generation/templates/ISynchronousClient.mustache b/generation/templates/ISynchronousClient.mustache
new file mode 100644
index 00000000..c09bfbfe
--- /dev/null
+++ b/generation/templates/ISynchronousClient.mustache
@@ -0,0 +1,85 @@
+{{>partial_header}}
+
+using System;
+using System.IO;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Contract for Synchronous RESTful API interactions.
+ ///
+ /// This interface allows consumers to provide a custom API accessor client.
+ ///
+ public interface ISynchronousClient
+ {
+ ///
+ /// Executes a blocking call to some using the GET http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Get(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+ ///
+ /// Executes a blocking call to some using the POST http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Post(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+ ///
+ /// Executes a blocking call to some using the PUT http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Put(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+ ///
+ /// Executes a blocking call to some using the DELETE http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Delete(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+ ///
+ /// Executes a blocking call to some using the HEAD http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Head(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+ ///
+ /// Executes a blocking call to some using the OPTIONS http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Options(string path, RequestOptions options, IReadableConfiguration configuration = null);
+
+ ///
+ /// Executes a blocking call to some using the PATCH http verb.
+ ///
+ /// The relative path to invoke.
+ /// The request parameters to pass along to the client.
+ /// Per-request configurable settings.
+ /// The return type.
+ /// The response data, decorated with
+ ApiResponse Patch(string path, RequestOptions options, IReadableConfiguration configuration = null);
+ }
+}
diff --git a/generation/templates/JsonSubTypesTests.mustache b/generation/templates/JsonSubTypesTests.mustache
new file mode 100644
index 00000000..55b1d518
--- /dev/null
+++ b/generation/templates/JsonSubTypesTests.mustache
@@ -0,0 +1,125 @@
+{{>partial_header}}
+
+using System.Collections.Generic;
+using System.Linq;
+using JsonSubTypes;
+using Newtonsoft.Json;
+using NUnit.Framework;
+
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.{{modelPackage}};
+using {{packageName}}.Client;
+
+namespace {{packageName}}.Test.Client
+{
+ public class JsonSubTypesTests
+ {
+ [Test]
+ public void TestSimpleJsonSubTypesExample()
+ {
+ var animal =
+ JsonConvert.DeserializeObject("{\"Kind\":\"Dog\",\"Breed\":\"Jack Russell Terrier\"}");
+ Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed);
+ }
+
+ [Test]
+ public void DeserializeObjectWithCustomMapping()
+ {
+ var animal =
+ JsonConvert.DeserializeObject("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}");
+ Assert.AreEqual("Jack Russell Terrier", (animal as Dog2)?.Breed);
+ }
+
+ [Test]
+ public void DeserializeObjectMappingByPropertyPresence()
+ {
+ string json =
+ "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
+ "{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," +
+ "{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]";
+
+
+ var persons = JsonConvert.DeserializeObject>(json);
+ Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
+ }
+ }
+
+ [JsonConverter(typeof(JsonSubtypes), "Kind")]
+ public interface IAnimal
+ {
+ string Kind { get; }
+ }
+
+ public class Dog : IAnimal
+ {
+ public Dog()
+ {
+ Kind = "Dog";
+ }
+
+ public string Kind { get; }
+ public string Breed { get; set; }
+ }
+
+ class Cat : IAnimal
+ {
+ public Cat()
+ {
+ Kind = "Cat";
+ }
+
+ public string Kind { get; }
+ bool Declawed { get; set; }
+ }
+
+ [JsonConverter(typeof(JsonSubtypes), "Sound")]
+ [JsonSubtypes.KnownSubType(typeof(Dog2), "Bark")]
+ [JsonSubtypes.KnownSubType(typeof(Cat2), "Meow")]
+ public class Animal2
+ {
+ public virtual string Sound { get; }
+ public string Color { get; set; }
+ }
+
+ public class Dog2 : Animal2
+ {
+ public Dog2()
+ {
+ Sound = "Bark";
+ }
+
+ public override string Sound { get; }
+ public string Breed { get; set; }
+ }
+
+ public class Cat2 : Animal2
+ {
+ public Cat2()
+ {
+ Sound = "Meow";
+ }
+
+ public override string Sound { get; }
+ public bool Declawed { get; set; }
+ }
+
+ [JsonConverter(typeof(JsonSubtypes))]
+ [JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")]
+ [JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")]
+ public class Person
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ }
+
+ public class Employee : Person
+ {
+ public string Department { get; set; }
+ public string JobTitle { get; set; }
+ }
+
+ public class Artist : Person
+ {
+ public string Skill { get; set; }
+ }
+}
diff --git a/generation/templates/Multimap.mustache b/generation/templates/Multimap.mustache
new file mode 100644
index 00000000..8624af00
--- /dev/null
+++ b/generation/templates/Multimap.mustache
@@ -0,0 +1,287 @@
+{{>partial_header}}
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// A dictionary in which one key has many associated values.
+ ///
+ /// The type of the key
+ /// The type of the value associated with the key.
+ public class Multimap : IDictionary>
+ {
+ #region Private Fields
+
+ private readonly Dictionary> _dictionary;
+
+ #endregion Private Fields
+
+ #region Constructors
+
+ ///
+ /// Empty Constructor.
+ ///
+ public Multimap()
+ {
+ _dictionary = new Dictionary>();
+ }
+
+ ///
+ /// Constructor with comparer.
+ ///
+ ///
+ public Multimap(IEqualityComparer comparer)
+ {
+ _dictionary = new Dictionary>(comparer);
+ }
+
+ #endregion Constructors
+
+ #region Enumerators
+
+ ///
+ /// To get the enumerator.
+ ///
+ /// Enumerator
+ public IEnumerator>> GetEnumerator()
+ {
+ return _dictionary.GetEnumerator();
+ }
+
+ ///
+ /// To get the enumerator.
+ ///
+ /// Enumerator
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _dictionary.GetEnumerator();
+ }
+
+ #endregion Enumerators
+
+ #region Public Members
+ ///
+ /// Add values to Multimap
+ ///
+ /// Key value pair
+ public void Add(KeyValuePair> item)
+ {
+ if (!TryAdd(item.Key, item.Value))
+ throw new InvalidOperationException("Could not add values to Multimap.");
+ }
+
+ ///
+ /// Add Multimap to Multimap
+ ///
+ /// Multimap
+ public void Add(Multimap multimap)
+ {
+ foreach (var item in multimap)
+ {
+ if (!TryAdd(item.Key, item.Value))
+ throw new InvalidOperationException("Could not add values to Multimap.");
+ }
+ }
+
+ ///
+ /// Clear Multimap
+ ///
+ public void Clear()
+ {
+ _dictionary.Clear();
+ }
+
+ ///
+ /// Determines whether Multimap contains the specified item.
+ ///
+ /// Key value pair
+ /// Method needs to be implemented
+ /// true if the Multimap contains the item; otherwise, false.
+ public bool Contains(KeyValuePair> item)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Copy items of the Multimap to an array,
+ /// starting at a particular array index.
+ ///
+ /// The array that is the destination of the items copied
+ /// from Multimap. The array must have zero-based indexing.
+ /// The zero-based index in array at which copying begins.
+ /// Method needs to be implemented
+ public void CopyTo(KeyValuePair>[] array, int arrayIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Removes the specified item from the Multimap.
+ ///
+ /// Key value pair
+ /// true if the item is successfully removed; otherwise, false.
+ /// Method needs to be implemented
+ public bool Remove(KeyValuePair> item)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets the number of items contained in the Multimap.
+ ///
+ public int Count => _dictionary.Count;
+
+ ///
+ /// Gets a value indicating whether the Multimap is read-only.
+ ///
+ public bool IsReadOnly => false;
+
+ ///
+ /// Adds an item with the provided key and value to the Multimap.
+ ///
+ /// The object to use as the key of the item to add.
+ /// The object to use as the value of the item to add.
+ /// Thrown when couldn't add the value to Multimap.
+ public void Add(TKey key, IList value)
+ {
+ if (value != null && value.Count > 0)
+ {
+ if (_dictionary.TryGetValue(key, out var list))
+ {
+ foreach (var k in value) list.Add(k);
+ }
+ else
+ {
+ list = new List(value);
+ if (!TryAdd(key, list))
+ throw new InvalidOperationException("Could not add values to Multimap.");
+ }
+ }
+ }
+
+ ///
+ /// Determines whether the Multimap contains an item with the specified key.
+ ///
+ /// The key to locate in the Multimap.
+ /// true if the Multimap contains an item with
+ /// the key; otherwise, false.
+ public bool ContainsKey(TKey key)
+ {
+ return _dictionary.ContainsKey(key);
+ }
+
+ ///
+ /// Removes item with the specified key from the Multimap.
+ ///
+ /// The key to locate in the Multimap.
+ /// true if the item is successfully removed; otherwise, false.
+ public bool Remove(TKey key)
+ {
+ return TryRemove(key, out var _);
+ }
+
+ ///
+ /// Gets the value associated with the specified key.
+ ///
+ /// The key whose value to get.
+ /// When this method returns, the value associated with the specified key, if the
+ /// key is found; otherwise, the default value for the type of the value parameter.
+ /// This parameter is passed uninitialized.
+ /// true if the object that implements Multimap contains
+ /// an item with the specified key; otherwise, false.
+ public bool TryGetValue(TKey key, out IList value)
+ {
+ return _dictionary.TryGetValue(key, out value);
+ }
+
+ ///
+ /// Gets or sets the item with the specified key.
+ ///
+ /// The key of the item to get or set.
+ /// The value of the specified key.
+ public IList this[TKey key]
+ {
+ get => _dictionary[key];
+ set => _dictionary[key] = value;
+ }
+
+ ///
+ /// Gets a System.Collections.Generic.ICollection containing the keys of the Multimap.
+ ///
+ public ICollection Keys => _dictionary.Keys;
+
+ ///
+ /// Gets a System.Collections.Generic.ICollection containing the values of the Multimap.
+ ///
+ public ICollection> Values => _dictionary.Values;
+
+ ///
+ /// Copy the items of the Multimap to an System.Array,
+ /// starting at a particular System.Array index.
+ ///
+ /// The one-dimensional System.Array that is the destination of the items copied
+ /// from Multimap. The System.Array must have zero-based indexing.
+ /// The zero-based index in array at which copying begins.
+ public void CopyTo(Array array, int index)
+ {
+ ((ICollection)_dictionary).CopyTo(array, index);
+ }
+
+ ///
+ /// Adds an item with the provided key and value to the Multimap.
+ ///
+ /// The object to use as the key of the item to add.
+ /// The object to use as the value of the item to add.
+ /// Thrown when couldn't add value to Multimap.
+ public void Add(TKey key, TValue value)
+ {
+ if (value != null)
+ {
+ if (_dictionary.TryGetValue(key, out var list))
+ {
+ list.Add(value);
+ }
+ else
+ {
+ list = new List { value };
+ if (!TryAdd(key, list))
+ throw new InvalidOperationException("Could not add value to Multimap.");
+ }
+ }
+ }
+
+ #endregion Public Members
+
+ #region Private Members
+
+ /**
+ * Helper method to encapsulate generator differences between dictionary types.
+ */
+ private bool TryRemove(TKey key, out IList value)
+ {
+ _dictionary.TryGetValue(key, out value);
+ return _dictionary.Remove(key);
+ }
+
+ /**
+ * Helper method to encapsulate generator differences between dictionary types.
+ */
+ private bool TryAdd(TKey key, IList value)
+ {
+ try
+ {
+ _dictionary.Add(key, value);
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ #endregion Private Members
+ }
+}
diff --git a/generation/templates/NullConditionalParameter.mustache b/generation/templates/NullConditionalParameter.mustache
new file mode 100644
index 00000000..d8ad9266
--- /dev/null
+++ b/generation/templates/NullConditionalParameter.mustache
@@ -0,0 +1 @@
+{{#isNullable}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}}{{/isNullable}}
\ No newline at end of file
diff --git a/generation/templates/NullConditionalProperty.mustache b/generation/templates/NullConditionalProperty.mustache
new file mode 100644
index 00000000..7dcafa80
--- /dev/null
+++ b/generation/templates/NullConditionalProperty.mustache
@@ -0,0 +1 @@
+{{#lambda.first}}{{#isNullable}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/isNullable}}{{^required}}{{nrt?}}{{^nrt}}{{#vendorExtensions.x-is-value-type}}?{{/vendorExtensions.x-is-value-type}}{{/nrt}} {{/required}}{{/lambda.first}}
\ No newline at end of file
diff --git a/generation/templates/OpenAPIDateConverter.mustache b/generation/templates/OpenAPIDateConverter.mustache
new file mode 100644
index 00000000..d7905102
--- /dev/null
+++ b/generation/templates/OpenAPIDateConverter.mustache
@@ -0,0 +1,21 @@
+{{>partial_header}}
+using Newtonsoft.Json.Converters;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Formatter for 'date' openapi formats ss defined by full-date - RFC3339
+ /// see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#data-types
+ ///
+ public class OpenAPIDateConverter : IsoDateTimeConverter
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public OpenAPIDateConverter()
+ {
+ // full-date = date-fullyear "-" date-month "-" date-mday
+ DateTimeFormat = "yyyy-MM-dd";
+ }
+ }
+}
diff --git a/generation/templates/README.mustache b/generation/templates/README.mustache
new file mode 100644
index 00000000..9b6c23b9
--- /dev/null
+++ b/generation/templates/README.mustache
@@ -0,0 +1,267 @@
+# {{packageName}} - the C# library for the {{appName}}
+
+{{#appDescriptionWithNewLines}}
+{{{.}}}
+{{/appDescriptionWithNewLines}}
+
+This C# SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
+
+- API version: {{appVersion}}
+- SDK version: {{packageVersion}}
+{{^hideGenerationTimestamp}}
+- Build date: {{generatedDate}}
+{{/hideGenerationTimestamp}}
+- Generator version: {{generatorVersion}}
+- Build package: {{generatorClass}}
+{{#infoUrl}}
+ For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
+{{/infoUrl}}
+
+
+## Frameworks supported
+{{#netStandard}}
+- .NET Core >=1.0
+- .NET Framework >=4.6
+- Mono/Xamarin >=vNext
+{{/netStandard}}
+
+
+## Dependencies
+
+{{#useRestSharp}}
+- [RestSharp](https://www.nuget.org/packages/RestSharp) - 106.13.0 or later
+{{/useRestSharp}}
+- [Json.NET](https://www.nuget.org/packages/Newtonsoft.Json/) - 13.0.2 or later
+- [JsonSubTypes](https://www.nuget.org/packages/JsonSubTypes/) - 1.8.0 or later
+{{#useCompareNetObjects}}
+- [CompareNETObjects](https://www.nuget.org/packages/CompareNETObjects) - 4.61.0 or later
+{{/useCompareNetObjects}}
+{{#validatable}}
+- [System.ComponentModel.Annotations](https://www.nuget.org/packages/System.ComponentModel.Annotations) - 5.0.0 or later
+{{/validatable}}
+
+The DLLs included in the package may not be the latest version. We recommend using [NuGet](https://docs.nuget.org/consume/installing-nuget) to obtain the latest version of the packages:
+```
+{{#useRestSharp}}
+Install-Package RestSharp
+{{/useRestSharp}}
+Install-Package Newtonsoft.Json
+Install-Package JsonSubTypes
+{{#validatable}}
+Install-Package System.ComponentModel.Annotations
+{{/validatable}}
+{{#useCompareNetObjects}}
+Install-Package CompareNETObjects
+{{/useCompareNetObjects}}
+```
+{{#useRestSharp}}
+
+NOTE: RestSharp versions greater than 105.1.0 have a bug which causes file uploads to fail. See [RestSharp#742](https://github.com/restsharp/RestSharp/issues/742).
+NOTE: RestSharp for .Net Core creates a new socket for each api call, which can lead to a socket exhaustion problem. See [RestSharp#1406](https://github.com/restsharp/RestSharp/issues/1406).
+
+{{/useRestSharp}}
+
+## Installation
+{{#netStandard}}
+Generate the DLL using your preferred tool (e.g. `dotnet build`)
+{{/netStandard}}
+{{^netStandard}}
+Run the following command to generate the DLL
+- [Mac/Linux] `/bin/sh build.sh`
+- [Windows] `build.bat`
+{{/netStandard}}
+
+Then include the DLL (under the `bin` folder) in the C# project, and use the namespaces:
+```csharp
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.Client;
+using {{packageName}}.{{modelPackage}};
+```
+{{^netStandard}}
+
+## Packaging
+
+A `.nuspec` is included with the project. You can follow the Nuget quickstart to [create](https://docs.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package#create-the-package) and [publish](https://docs.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package#publish-the-package) packages.
+
+This `.nuspec` uses placeholders from the `.csproj`, so build the `.csproj` directly:
+
+```
+nuget pack -Build -OutputDirectory out {{packageName}}.csproj
+```
+
+Then, publish to a [local feed](https://docs.microsoft.com/en-us/nuget/hosting-packages/local-feeds) or [other host](https://docs.microsoft.com/en-us/nuget/hosting-packages/overview) and consume the new package via Nuget as usual.
+
+{{/netStandard}}
+
+## Usage
+
+To use the API client with a HTTP proxy, setup a `System.Net.WebProxy`
+```csharp
+Configuration c = new Configuration();
+System.Net.WebProxy webProxy = new System.Net.WebProxy("http://myProxyUrl:80/");
+webProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
+c.Proxy = webProxy;
+```
+{{#useHttpClient}}
+
+### Connections
+Each ApiClass (properly the ApiClient inside it) will create an instance of HttpClient. It will use that for the entire lifecycle and dispose it when called the Dispose method.
+
+To better manager the connections it's a common practice to reuse the HttpClient and HttpClientHandler (see [here](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net) for details). To use your own HttpClient instance just pass it to the ApiClass constructor.
+
+```csharp
+HttpClientHandler yourHandler = new HttpClientHandler();
+HttpClient yourHttpClient = new HttpClient(yourHandler);
+var api = new YourApiClass(yourHttpClient, yourHandler);
+```
+
+If you want to use an HttpClient and don't have access to the handler, for example in a DI context in Asp.net Core when using IHttpClientFactory.
+
+```csharp
+HttpClient yourHttpClient = new HttpClient();
+var api = new YourApiClass(yourHttpClient);
+```
+You'll loose some configuration settings, the features affected are: Setting and Retrieving Cookies, Client Certificates, Proxy settings. You need to either manually handle those in your setup of the HttpClient or they won't be available.
+
+Here an example of DI setup in a sample web project:
+
+```csharp
+services.AddHttpClient(httpClient =>
+ new PetApi(httpClient));
+```
+
+{{/useHttpClient}}
+
+
+## Getting Started
+
+```csharp
+using System.Collections.Generic;
+using System.Diagnostics;
+{{#useHttpClient}}
+using System.Net.Http;
+{{/useHttpClient}}
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.Client;
+using {{packageName}}.{{modelPackage}};
+
+namespace Example
+{
+ public class {{operationId}}Example
+ {
+ public static void Main()
+ {
+{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}}
+ Configuration config = new Configuration();
+ config.BasePath = "{{{basePath}}}";
+ {{#hasAuthMethods}}
+ {{#authMethods}}
+ {{#isBasicBasic}}
+ // Configure HTTP basic authorization: {{{name}}}
+ config.Username = "YOUR_USERNAME";
+ config.Password = "YOUR_PASSWORD";
+ {{/isBasicBasic}}
+ {{#isBasicBearer}}
+ // Configure Bearer token for authorization: {{{name}}}
+ config.AccessToken = "YOUR_BEARER_TOKEN";
+ {{/isBasicBearer}}
+ {{#isApiKey}}
+ // Configure API key authorization: {{{name}}}
+ config.ApiKey.Add("{{{keyParamName}}}", "YOUR_API_KEY");
+ // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
+ // config.ApiKeyPrefix.Add("{{{keyParamName}}}", "Bearer");
+ {{/isApiKey}}
+ {{#isOAuth}}
+ // Configure OAuth2 access token for authorization: {{{name}}}
+ config.AccessToken = "YOUR_ACCESS_TOKEN";
+ {{/isOAuth}}
+ {{/authMethods}}
+
+ {{/hasAuthMethods}}
+ {{#useHttpClient}}
+ // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes
+ HttpClient httpClient = new HttpClient();
+ HttpClientHandler httpClientHandler = new HttpClientHandler();
+ var apiInstance = new {{classname}}(httpClient, config, httpClientHandler);
+ {{/useHttpClient}}
+ {{^useHttpClient}}
+ var apiInstance = new {{classname}}(config);
+ {{/useHttpClient}}
+ {{#allParams}}
+ {{#isPrimitiveType}}
+ var {{paramName}} = {{{example}}}; // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+ {{/isPrimitiveType}}
+ {{^isPrimitiveType}}
+ var {{paramName}} = new {{{dataType}}}(); // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+ {{/isPrimitiveType}}
+ {{/allParams}}
+
+ try
+ {
+ {{#summary}}
+ // {{{.}}}
+ {{/summary}}
+ {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}}
+ Debug.WriteLine(result);{{/returnType}}
+ }
+ catch (ApiException e)
+ {
+ Debug.Print("Exception when calling {{classname}}.{{operationId}}: " + e.Message );
+ Debug.Print("Status Code: "+ e.ErrorCode);
+ Debug.Print(e.StackTrace);
+ }
+{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
+ }
+ }
+}
+```
+
+
+## Documentation for API Endpoints
+
+All URIs are relative to *{{{basePath}}}*
+
+Class | Method | HTTP request | Description
+------------ | ------------- | ------------- | -------------
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{{summary}}}
+{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
+
+
+## Documentation for Models
+
+{{#modelPackage}}
+{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
+{{/model}}{{/models}}
+{{/modelPackage}}
+{{^modelPackage}}
+No model defined in this package
+{{/modelPackage}}
+
+
+## Documentation for Authorization
+
+{{^authMethods}}Endpoints do not require authorization.{{/authMethods}}
+{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}}
+{{#authMethods}}
+
+### {{name}}
+
+{{#isApiKey}}- **Type**: API key
+- **API key parameter name**: {{keyParamName}}
+- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
+{{/isApiKey}}
+{{#isBasicBasic}}- **Type**: HTTP basic authentication
+{{/isBasicBasic}}
+{{#isBasicBearer}}- **Type**: Bearer Authentication
+{{/isBasicBearer}}
+{{#isHttpSignature}}- **Type**: HTTP signature authentication
+{{/isHttpSignature}}
+{{#isOAuth}}- **Type**: OAuth
+- **Flow**: {{flow}}
+- **Authorization URL**: {{authorizationUrl}}
+- **Scopes**: {{^scopes}}N/A{{/scopes}}
+{{#scopes}} - {{scope}}: {{description}}
+{{/scopes}}
+{{/isOAuth}}
+
+{{/authMethods}}
diff --git a/generation/templates/ReadOnlyDictionary.mustache b/generation/templates/ReadOnlyDictionary.mustache
new file mode 100644
index 00000000..1299b243
--- /dev/null
+++ b/generation/templates/ReadOnlyDictionary.mustache
@@ -0,0 +1,137 @@
+{{>partial_header}}
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace {{packageName}}.Client
+{
+ public class ReadOnlyDictionary : IDictionary
+ {
+ private IDictionary _dictionaryImplementation;
+ public IEnumerator> GetEnumerator()
+ {
+ return _dictionaryImplementation.GetEnumerator();
+ }
+
+ public ReadOnlyDictionary()
+ {
+ _dictionaryImplementation = new Dictionary();
+ }
+
+ public ReadOnlyDictionary(IDictionary dictionaryImplementation)
+ {
+ if (dictionaryImplementation == null) throw new ArgumentNullException("dictionaryImplementation");
+ _dictionaryImplementation = dictionaryImplementation;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable) _dictionaryImplementation).GetEnumerator();
+ }
+
+ public void Add(KeyValuePair item)
+ {
+ throw new ReadonlyOperationException("This instance is readonly.");
+ }
+
+ public void Clear()
+ {
+ throw new ReadonlyOperationException("This instance is readonly.");
+ }
+
+ public bool Contains(KeyValuePair item)
+ {
+ return _dictionaryImplementation.Contains(item);
+ }
+
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ _dictionaryImplementation.CopyTo(array, arrayIndex);
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ throw new ReadonlyOperationException("This instance is readonly.");
+ }
+
+ public int Count
+ {
+ get { return _dictionaryImplementation.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return true; }
+ }
+
+ public void Add(T key, K value)
+ {
+ throw new ReadonlyOperationException("This instance is readonly.");
+ }
+
+ public bool ContainsKey(T key)
+ {
+ return _dictionaryImplementation.ContainsKey(key);
+ }
+
+ public bool Remove(T key)
+ {
+ throw new ReadonlyOperationException("This instance is readonly.");
+ }
+
+ public bool TryGetValue(T key, out K value)
+ {
+ return _dictionaryImplementation.TryGetValue(key, out value);
+ }
+
+ public K this[T key]
+ {
+ get { return _dictionaryImplementation[key]; }
+ set
+ {
+ throw new ReadonlyOperationException("This instance is readonly.");
+
+ }
+ }
+
+ public ICollection Keys
+ {
+ get { return _dictionaryImplementation.Keys; }
+ }
+
+ public ICollection Values
+ {
+ get { return _dictionaryImplementation.Values; }
+ }
+ }
+
+ [Serializable]
+ public class ReadonlyOperationException : Exception
+ {
+ //
+ // For guidelines regarding the creation of new exception types, see
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
+ // and
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
+ //
+
+ public ReadonlyOperationException()
+ {
+ }
+
+ public ReadonlyOperationException(string message) : base(message)
+ {
+ }
+
+ public ReadonlyOperationException(string message, Exception inner) : base(message, inner)
+ {
+ }
+
+ protected ReadonlyOperationException(
+ SerializationInfo info,
+ StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/generation/templates/RequestOptions.mustache b/generation/templates/RequestOptions.mustache
new file mode 100644
index 00000000..cfc14692
--- /dev/null
+++ b/generation/templates/RequestOptions.mustache
@@ -0,0 +1,87 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// A container for generalized request inputs. This type allows consumers to extend the request functionality
+ /// by abstracting away from the default (built-in) request framework (e.g. RestSharp).
+ ///
+ public class RequestOptions
+ {
+ ///
+ /// Parameters to be bound to path parts of the Request's URL
+ ///
+ public Dictionary PathParameters { get; set; }
+
+ ///
+ /// Query parameters to be applied to the request.
+ /// Keys may have 1 or more values associated.
+ ///
+ public Multimap QueryParameters { get; set; }
+
+ ///
+ /// Header parameters to be applied to the request.
+ /// Keys may have 1 or more values associated.
+ ///
+ public Multimap HeaderParameters { get; set; }
+
+ ///
+ /// Form parameters to be sent along with the request.
+ ///
+ public Dictionary FormParameters { get; set; }
+
+ {{#supportsFileParameters}}
+ ///
+ /// File parameters to be sent along with the request.
+ ///
+ public Multimap FileParameters { get; set; }
+ {{/supportsFileParameters}}
+
+ ///
+ /// Cookies to be sent along with the request.
+ ///
+ public List Cookies { get; set; }
+
+ ///
+ /// Operation associated with the request path.
+ ///
+ public string Operation { get; set; }
+
+ ///
+ /// Index associated with the operation.
+ ///
+ public int OperationIndex { get; set; }
+
+ ///
+ /// Any data associated with a request body.
+ ///
+ public Object Data { get; set; }
+
+ {{#hasOAuthMethods}}
+ ///
+ /// If request should be authenticated with OAuth.
+ ///
+ public bool OAuth { get; set; }
+
+ {{/hasOAuthMethods}}
+ ///
+ /// Constructs a new instance of
+ ///
+ public RequestOptions()
+ {
+ PathParameters = new Dictionary();
+ QueryParameters = new Multimap();
+ HeaderParameters = new Multimap();
+ FormParameters = new Dictionary();
+ {{#supportsFileParameters}}
+ FileParameters = new Multimap();
+ {{/supportsFileParameters}}
+ Cookies = new List();
+ }
+ }
+}
diff --git a/generation/templates/RetryConfiguration.mustache b/generation/templates/RetryConfiguration.mustache
new file mode 100644
index 00000000..93ba14d9
--- /dev/null
+++ b/generation/templates/RetryConfiguration.mustache
@@ -0,0 +1,41 @@
+{{>partial_header}}
+
+using Polly;
+{{#useRestSharp}}
+using RestSharp;
+{{/useRestSharp}}
+{{#useHttpClient}}
+using System.Net.Http;
+{{/useHttpClient}}
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// Configuration class to set the polly retry policies to be applied to the requests.
+ ///
+ public static class RetryConfiguration
+ {
+{{#useRestSharp}}
+ ///
+ /// Retry policy
+ ///
+ public static Policy RetryPolicy { get; set; }
+
+ ///
+ /// Async retry policy
+ ///
+ public static AsyncPolicy AsyncRetryPolicy { get; set; }
+{{/useRestSharp}}
+{{#useHttpClient}}
+ ///
+ /// Retry policy
+ ///
+ public static Policy RetryPolicy { get; set; }
+
+ ///
+ /// Async retry policy
+ ///
+ public static AsyncPolicy AsyncRetryPolicy { get; set; }
+{{/useHttpClient}}
+ }
+}
diff --git a/generation/templates/Solution.mustache b/generation/templates/Solution.mustache
new file mode 100644
index 00000000..112cc3dc
--- /dev/null
+++ b/generation/templates/Solution.mustache
@@ -0,0 +1,27 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio {{^netStandard}}2012{{/netStandard}}{{#netStandard}}14{{/netStandard}}
+VisualStudioVersion = {{^netStandard}}12.0.0.0{{/netStandard}}{{#netStandard}}14.0.25420.1{{/netStandard}}
+MinimumVisualStudioVersion = {{^netStandard}}10.0.0.1{{/netStandard}}{{#netStandard}}10.0.40219.1{{/netStandard}}
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{packageName}}", "src\{{packageName}}\{{packageName}}.csproj", "{{packageGuid}}"
+EndProject
+{{^excludeTests}}Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{testPackageName}}", "src\{{testPackageName}}\{{testPackageName}}.csproj", "{19F1DEBC-DE5E-4517-8062-F000CD499087}"
+EndProject
+{{/excludeTests}}Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {{packageGuid}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {{packageGuid}}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {{packageGuid}}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {{packageGuid}}.Release|Any CPU.Build.0 = Release|Any CPU
+ {19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
\ No newline at end of file
diff --git a/generation/templates/TestProject.mustache b/generation/templates/TestProject.mustache
new file mode 100644
index 00000000..2a27f6a9
--- /dev/null
+++ b/generation/templates/TestProject.mustache
@@ -0,0 +1,35 @@
+
+
+
+
+ false
+ Properties
+ {{testPackageName}}
+ {{testPackageName}}
+ {{testTargetFramework}}
+ false
+ 512
+
+
+
+
+
+
+
+
+ {{packageGuid}}
+ {{packageName}}
+
+
+
diff --git a/generation/templates/ValidateRegex.mustache b/generation/templates/ValidateRegex.mustache
new file mode 100644
index 00000000..15cf626d
--- /dev/null
+++ b/generation/templates/ValidateRegex.mustache
@@ -0,0 +1,6 @@
+// {{{name}}} ({{{dataType}}}) pattern
+Regex regex{{{name}}} = new Regex(@"{{{vendorExtensions.x-regex}}}"{{#vendorExtensions.x-modifiers}}{{#-first}}, {{/-first}}RegexOptions.{{{.}}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}});
+if (!regex{{{name}}}.Match(this.{{{name}}}{{#isUuid}}.ToString(){{/isUuid}}).Success)
+{
+ yield return new System.ComponentModel.DataAnnotations.ValidationResult("Invalid value for {{{name}}}, must match a pattern of " + regex{{{name}}}, new [] { "{{{name}}}" });
+}
\ No newline at end of file
diff --git a/generation/templates/WebRequestPathBuilder.mustache b/generation/templates/WebRequestPathBuilder.mustache
new file mode 100644
index 00000000..cc811ae4
--- /dev/null
+++ b/generation/templates/WebRequestPathBuilder.mustache
@@ -0,0 +1,45 @@
+{{>partial_header}}
+using System;
+using System.Collections.Generic;
+
+namespace {{packageName}}.Client
+{
+ ///
+ /// A URI builder
+ ///
+ class WebRequestPathBuilder
+ {
+ private string _baseUrl;
+ private string _path;
+ private string _query = "?";
+ public WebRequestPathBuilder(string baseUrl, string path)
+ {
+ _baseUrl = baseUrl;
+ _path = path;
+ }
+
+ public void AddPathParameters(Dictionary parameters)
+ {
+ foreach (var parameter in parameters)
+ {
+ _path = _path.Replace("{" + parameter.Key + "}", Uri.EscapeDataString(parameter.Value));
+ }
+ }
+
+ public void AddQueryParameters(Multimap parameters)
+ {
+ foreach (var parameter in parameters)
+ {
+ foreach (var value in parameter.Value)
+ {
+ _query = _query + parameter.Key + "=" + Uri.EscapeDataString(value) + "&";
+ }
+ }
+ }
+
+ public string GetFullUri()
+ {
+ return _baseUrl + _path + _query.Substring(0, _query.Length - 1);
+ }
+ }
+}
diff --git a/generation/templates/api.mustache b/generation/templates/api.mustache
new file mode 100644
index 00000000..6494627b
--- /dev/null
+++ b/generation/templates/api.mustache
@@ -0,0 +1,798 @@
+{{>partial_header}}
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net;
+using System.Net.Mime;
+using {{packageName}}.Client;
+{{#hasOAuthMethods}}using {{packageName}}.Client.Auth;
+{{/hasOAuthMethods}}
+{{#hasImport}}using {{packageName}}.{{modelPackage}};
+{{/hasImport}}
+
+namespace {{packageName}}.{{apiPackage}}
+{
+ {{#operations}}
+
+ ///
+ /// Represents a collection of functions to interact with the API endpoints
+ ///
+ {{>visibility}} interface {{interfacePrefix}}{{classname}}Sync : IApiAccessor
+ {
+ #region Synchronous Operations
+ {{#operation}}
+ ///
+ /// {{summary}}
+ ///
+ {{#notes}}
+ ///
+ /// {{.}}
+ ///
+ {{/notes}}
+ /// Thrown when fails to make API call
+ {{#allParams}}/// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}/// Index associated with the operation.
+ /// {{returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ {{{returnType}}}{{^returnType}}void{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0);
+
+ ///
+ /// {{summary}}
+ ///
+ ///
+ /// {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}/// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}/// Index associated with the operation.
+ /// ApiResponse of {{returnType}}{{^returnType}}Object(void){{/returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0);
+ {{/operation}}
+ #endregion Synchronous Operations
+ }
+
+ {{#supportsAsync}}
+ ///
+ /// Represents a collection of functions to interact with the API endpoints
+ ///
+ {{>visibility}} interface {{interfacePrefix}}{{classname}}Async : IApiAccessor
+ {
+ #region Asynchronous Operations
+ {{#operation}}
+ ///
+ /// {{summary}}
+ ///
+ ///
+ /// {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}
+ /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}
+ /// Index associated with the operation.
+ /// Cancellation Token to cancel the request.
+ /// Task of {{returnType}}{{^returnType}}void{{/returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ {{#returnType}}System.Threading.Tasks.Task<{{{.}}}>{{/returnType}}{{^returnType}}System.Threading.Tasks.Task{{/returnType}} {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+
+ ///
+ /// {{summary}}
+ ///
+ ///
+ /// {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}
+ /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}
+ /// Index associated with the operation.
+ /// Cancellation Token to cancel the request.
+ /// Task of ApiResponse{{#returnType}} ({{.}}){{/returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ System.Threading.Tasks.Task> {{operationId}}WithHttpInfoAsync({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
+ {{/operation}}
+ #endregion Asynchronous Operations
+ }
+ {{/supportsAsync}}
+
+ ///
+ /// Represents a collection of functions to interact with the API endpoints
+ ///
+ {{>visibility}} interface {{interfacePrefix}}{{classname}} : {{interfacePrefix}}{{classname}}Sync{{#supportsAsync}}, {{interfacePrefix}}{{classname}}Async{{/supportsAsync}}
+ {
+
+ }
+
+ ///
+ /// Represents a collection of functions to interact with the API endpoints
+ ///
+ {{>visibility}} partial class {{classname}} : {{interfacePrefix}}{{classname}}
+ {
+ private {{packageName}}.Client.ExceptionFactory _exceptionFactory = (name, response) => null;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public {{classname}}() : this((string)null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public {{classname}}(string basePath)
+ {
+ this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+ {{packageName}}.Client.GlobalConfiguration.Instance,
+ new {{packageName}}.Client.Configuration { BasePath = basePath }
+ );
+ this.Client = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+ {{#supportsAsync}}
+ this.AsynchronousClient = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+ {{/supportsAsync}}
+ this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// using Configuration object
+ ///
+ /// An instance of Configuration
+ ///
+ public {{classname}}({{packageName}}.Client.Configuration configuration)
+ {
+ if (configuration == null) throw new ArgumentNullException("configuration");
+
+ this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
+ {{packageName}}.Client.GlobalConfiguration.Instance,
+ configuration
+ );
+ this.Client = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+ {{#supportsAsync}}
+ this.AsynchronousClient = new {{packageName}}.Client.ApiClient(this.Configuration.BasePath);
+ {{/supportsAsync}}
+ ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// using a Configuration object and client instance.
+ ///
+ /// The client interface for synchronous API access.{{#supportsAsync}}
+ /// The client interface for asynchronous API access.{{/supportsAsync}}
+ /// The configuration object.
+ public {{classname}}({{packageName}}.Client.ISynchronousClient client, {{#supportsAsync}}{{packageName}}.Client.IAsynchronousClient asyncClient, {{/supportsAsync}}{{packageName}}.Client.IReadableConfiguration configuration)
+ {
+ if (client == null) throw new ArgumentNullException("client");
+ {{#supportsAsync}}
+ if (asyncClient == null) throw new ArgumentNullException("asyncClient");
+ {{/supportsAsync}}
+ if (configuration == null) throw new ArgumentNullException("configuration");
+
+ this.Client = client;
+ {{#supportsAsync}}
+ this.AsynchronousClient = asyncClient;
+ {{/supportsAsync}}
+ this.Configuration = configuration;
+ this.ExceptionFactory = {{packageName}}.Client.Configuration.DefaultExceptionFactory;
+ }
+
+ {{#supportsAsync}}
+ ///
+ /// The client for accessing this underlying API asynchronously.
+ ///
+ public {{packageName}}.Client.IAsynchronousClient AsynchronousClient { get; set; }
+ {{/supportsAsync}}
+
+ ///
+ /// The client for accessing this underlying API synchronously.
+ ///
+ public {{packageName}}.Client.ISynchronousClient Client { get; set; }
+
+ ///
+ /// Gets the base path of the API client.
+ ///
+ /// The base path
+ public string GetBasePath()
+ {
+ return this.Configuration.BasePath;
+ }
+
+ ///
+ /// Gets or sets the configuration object
+ ///
+ /// An instance of the Configuration
+ public {{packageName}}.Client.IReadableConfiguration Configuration { get; set; }
+
+ ///
+ /// Provides a factory method hook for the creation of exceptions.
+ ///
+ public {{packageName}}.Client.ExceptionFactory ExceptionFactory
+ {
+ get
+ {
+ if (_exceptionFactory != null && _exceptionFactory.GetInvocationList().Length > 1)
+ {
+ throw new InvalidOperationException("Multicast delegate for ExceptionFactory is unsupported.");
+ }
+ return _exceptionFactory;
+ }
+ set { _exceptionFactory = value; }
+ }
+
+ {{#operation}}
+ ///
+ /// {{summary}} {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}/// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}/// Index associated with the operation.
+ /// {{returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ public {{{returnType}}}{{^returnType}}void{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0)
+ {
+ {{#returnType}}{{packageName}}.Client.ApiResponse<{{{returnType}}}> localVarResponse = {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});
+ return localVarResponse.Data;{{/returnType}}{{^returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{/returnType}}
+ }
+
+ ///
+ /// {{summary}} {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}/// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}/// Index associated with the operation.
+ /// ApiResponse of {{returnType}}{{^returnType}}Object(void){{/returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ public {{packageName}}.Client.ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}> {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0)
+ {
+ {{#allParams}}
+ {{#required}}
+ {{^vendorExtensions.x-csharp-value-type}}
+ // verify the required parameter '{{paramName}}' is set
+ if ({{paramName}} == null)
+ {
+ throw new {{packageName}}.Client.ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");
+ }
+
+ {{/vendorExtensions.x-csharp-value-type}}
+ {{/required}}
+ {{/allParams}}
+ {{packageName}}.Client.RequestOptions localVarRequestOptions = new {{packageName}}.Client.RequestOptions();
+
+ string[] _contentTypes = new string[] {
+ {{#consumes}}
+ "{{{mediaType}}}"{{^-last}},{{/-last}}
+ {{/consumes}}
+ };
+
+ // to determine the Accept header
+ string[] _accepts = new string[] {
+ {{#produces}}
+ "{{{mediaType}}}"{{^-last}},{{/-last}}
+ {{/produces}}
+ };
+
+ var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
+ if (localVarContentType != null)
+ {
+ localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
+ }
+
+ var localVarAccept = {{packageName}}.Client.ClientUtils.SelectHeaderAccept(_accepts);
+ if (localVarAccept != null)
+ {
+ localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
+ }
+
+ {{#pathParams}}
+ {{#required}}
+ localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+ }
+ {{/required}}
+ {{/pathParams}}
+ {{#queryParams}}
+ {{#required}}
+ {{#isDeepObject}}
+ {{#items.vars}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}.{{name}}));
+ {{/items.vars}}
+ {{^items}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+ {{/items}}
+ {{/isDeepObject}}
+ {{^isDeepObject}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+ {{/isDeepObject}}
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ {{#isDeepObject}}
+ {{#items.vars}}
+ if ({{paramName}}.{{name}} != null)
+ {
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{paramName}}[{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}]", {{paramName}}.{{name}}));
+ }
+ {{/items.vars}}
+ {{^items}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+ {{/items}}
+ {{/isDeepObject}}
+ {{^isDeepObject}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+ {{/isDeepObject}}
+ }
+ {{/required}}
+ {{/queryParams}}
+ {{#headerParams}}
+ {{#required}}
+ localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+ }
+ {{/required}}
+ {{/headerParams}}
+ {{#formParams}}
+ {{#required}}
+ {{#isFile}}
+ {{#isArray}}
+ {{#supportsFileParameters}}
+ foreach (var file in {{paramName}})
+ {
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", file);
+ }
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{^isArray}}
+ {{#supportsFileParameters}}
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{/isFile}}
+ {{^isFile}}
+ localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.{{#isPrimitiveType}}ParameterToString{{/isPrimitiveType}}{{^isPrimitiveType}}Serialize{{/isPrimitiveType}}({{paramName}})); // form parameter
+ {{/isFile}}
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ {{#isFile}}
+ {{#isArray}}
+ {{#supportsFileParameters}}
+ foreach (var file in {{paramName}})
+ {
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", file);
+ }
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{^isArray}}
+ {{#supportsFileParameters}}
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{/isFile}}
+ {{^isFile}}
+ localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.{{#isPrimitiveType}}ParameterToString{{/isPrimitiveType}}{{^isPrimitiveType}}Serialize{{/isPrimitiveType}}({{paramName}})); // form parameter
+ {{/isFile}}
+ }
+ {{/required}}
+ {{/formParams}}
+ {{#bodyParam}}
+ localVarRequestOptions.Data = {{paramName}};
+ {{/bodyParam}}
+
+ localVarRequestOptions.Operation = "{{classname}}.{{operationId}}";
+ localVarRequestOptions.OperationIndex = operationIndex;
+
+ {{#authMethods}}
+ // authentication ({{name}}) required
+ {{#isApiKey}}
+ {{#isKeyInCookie}}
+ // cookie parameter support
+ if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+ {
+ localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+ }
+ {{/isKeyInCookie}}
+ {{#isKeyInHeader}}
+ if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+ {
+ localVarRequestOptions.HeaderParameters.Add("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"));
+ }
+ {{/isKeyInHeader}}
+ {{#isKeyInQuery}}
+ if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+ {
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("", "{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+ }
+ {{/isKeyInQuery}}
+ {{/isApiKey}}
+ {{#isBasicBasic}}
+ // http basic authentication required
+ if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
+ {
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+ }
+ {{/isBasicBasic}}
+ {{#isBasicBearer}}
+ // bearer authentication required
+ if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
+ {
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+ }
+ {{/isBasicBearer}}
+ {{#isOAuth}}
+ // oauth required
+ if (!localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
+ {
+ if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+ {
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+ }
+ {{#hasOAuthMethods}}
+ else if (!string.IsNullOrEmpty(this.Configuration.OAuthTokenUrl) &&
+ !string.IsNullOrEmpty(this.Configuration.OAuthClientId) &&
+ !string.IsNullOrEmpty(this.Configuration.OAuthClientSecret) &&
+ this.Configuration.OAuthFlow != null)
+ {
+ localVarRequestOptions.OAuth = true;
+ }
+ {{/hasOAuthMethods}}
+ }
+ {{/isOAuth}}
+ {{#isHttpSignature}}
+ if (this.Configuration.HttpSigningConfiguration != null)
+ {
+ var HttpSigningHeaders = this.Configuration.HttpSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
+ foreach (var headerItem in HttpSigningHeaders)
+ {
+ if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
+ {
+ localVarRequestOptions.HeaderParameters[headerItem.Key] = new List() { headerItem.Value };
+ }
+ else
+ {
+ localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
+ }
+ }
+ }
+ {{/isHttpSignature}}
+ {{/authMethods}}
+
+ // make the HTTP request
+ var localVarResponse = this.Client.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}<{{{returnType}}}{{^returnType}}Object{{/returnType}}>("{{{path}}}", localVarRequestOptions, this.Configuration);
+ if (this.ExceptionFactory != null)
+ {
+ Exception _exception = this.ExceptionFactory("{{operationId}}", localVarResponse);
+ if (_exception != null)
+ {
+ throw _exception;
+ }
+ }
+
+ return localVarResponse;
+ }
+
+ {{#supportsAsync}}
+ ///
+ /// {{summary}} {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}
+ /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}
+ /// Index associated with the operation.
+ /// Cancellation Token to cancel the request.
+ /// Task of {{returnType}}{{^returnType}}void{{/returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ {{#returnType}}public async System.Threading.Tasks.Task<{{{.}}}>{{/returnType}}{{^returnType}}public async System.Threading.Tasks.Task{{/returnType}} {{operationId}}Async({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ {{#returnType}}{{packageName}}.Client.ApiResponse<{{{returnType}}}> localVarResponse = await {{operationId}}WithHttpInfoAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}operationIndex, cancellationToken).ConfigureAwait(false);
+ return localVarResponse.Data;{{/returnType}}{{^returnType}}await {{operationId}}WithHttpInfoAsync({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}operationIndex, cancellationToken).ConfigureAwait(false);{{/returnType}}
+ }
+
+ ///
+ /// {{summary}} {{notes}}
+ ///
+ /// Thrown when fails to make API call
+ {{#allParams}}
+ /// {{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}
+ {{/allParams}}
+ /// Index associated with the operation.
+ /// Cancellation Token to cancel the request.
+ /// Task of ApiResponse{{#returnType}} ({{.}}){{/returnType}}
+ {{#isDeprecated}}
+ [Obsolete]
+ {{/isDeprecated}}
+ public async System.Threading.Tasks.Task<{{packageName}}.Client.ApiResponse<{{{returnType}}}{{^returnType}}Object{{/returnType}}>> {{operationId}}WithHttpInfoAsync({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}int operationIndex = 0, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ {{#allParams}}
+ {{#required}}
+ {{^vendorExtensions.x-csharp-value-type}}
+ // verify the required parameter '{{paramName}}' is set
+ if ({{paramName}} == null)
+ {
+ throw new {{packageName}}.Client.ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");
+ }
+
+ {{/vendorExtensions.x-csharp-value-type}}
+ {{/required}}
+ {{/allParams}}
+
+ {{packageName}}.Client.RequestOptions localVarRequestOptions = new {{packageName}}.Client.RequestOptions();
+
+ string[] _contentTypes = new string[] {
+ {{#consumes}}
+ "{{{mediaType}}}"{{^-last}}, {{/-last}}
+ {{/consumes}}
+ };
+
+ // to determine the Accept header
+ string[] _accepts = new string[] {
+ {{#produces}}
+ "{{{mediaType}}}"{{^-last}},{{/-last}}
+ {{/produces}}
+ };
+
+ var localVarContentType = {{packageName}}.Client.ClientUtils.SelectHeaderContentType(_contentTypes);
+ if (localVarContentType != null)
+ {
+ localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType);
+ }
+
+ var localVarAccept = {{packageName}}.Client.ClientUtils.SelectHeaderAccept(_accepts);
+ if (localVarAccept != null)
+ {
+ localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept);
+ }
+
+ {{#constantParams}}
+ {{#isPathParam}}
+ // Set client side default value of Path Param "{{baseName}}".
+ localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{#_enum}}"{{{.}}}"{{/_enum}})); // Constant path parameter
+ {{/isPathParam}}
+ {{/constantParams}}
+ {{#pathParams}}
+ {{#required}}
+ localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ localVarRequestOptions.PathParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // path parameter
+ }
+ {{/required}}
+ {{/pathParams}}
+ {{#constantParams}}
+ {{#isQueryParam}}
+ // Set client side default value of Query Param "{{baseName}}".
+ localVarRequestOptions.QueryParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{#_enum}}"{{{.}}}"{{/_enum}})); // Constant query parameter
+ {{/isQueryParam}}
+ {{/constantParams}}
+ {{#queryParams}}
+ {{#required}}
+ {{#isDeepObject}}
+ {{#items.vars}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}.{{name}}));
+ {{/items.vars}}
+ {{^items}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+ {{/items}}
+ {{/isDeepObject}}
+ {{^isDeepObject}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+ {{/isDeepObject}}
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ {{#isDeepObject}}
+ {{#items.vars}}
+ if ({{paramName}}.{{name}} != null)
+ {
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{paramName}}[{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}]", {{paramName}}.{{name}}));
+ }
+ {{/items.vars}}
+ {{^items}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{baseName}}", {{paramName}}));
+ {{/items}}
+ {{/isDeepObject}}
+ {{^isDeepObject}}
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
+ {{/isDeepObject}}
+ }
+ {{/required}}
+ {{/queryParams}}
+ {{#constantParams}}
+ {{#isHeaderParam}}
+ // Set client side default value of Header Param "{{baseName}}".
+ localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{#_enum}}"{{{.}}}"{{/_enum}})); // Constant header parameter
+ {{/isHeaderParam}}
+ {{/constantParams}}
+ {{#headerParams}}
+ {{#required}}
+ localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ localVarRequestOptions.HeaderParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.ParameterToString({{paramName}})); // header parameter
+ }
+ {{/required}}
+ {{/headerParams}}
+ {{#formParams}}
+ {{#required}}
+ {{#isFile}}
+ {{#isArray}}
+ {{#supportsFileParameters}}
+ foreach (var file in {{paramName}})
+ {
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", file);
+ }
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{^isArray}}
+ {{#supportsFileParameters}}
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{/isFile}}
+ {{^isFile}}
+ localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.{{#isPrimitiveType}}ParameterToString{{/isPrimitiveType}}{{^isPrimitiveType}}Serialize{{/isPrimitiveType}}({{paramName}})); // form parameter
+ {{/isFile}}
+ {{/required}}
+ {{^required}}
+ if ({{paramName}} != null)
+ {
+ {{#isFile}}
+ {{#isArray}}
+ {{#supportsFileParameters}}
+ foreach (var file in {{paramName}})
+ {
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", file);
+ }
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{^isArray}}
+ {{#supportsFileParameters}}
+ localVarRequestOptions.FileParameters.Add("{{baseName}}", {{paramName}});
+ {{/supportsFileParameters}}
+ {{/isArray}}
+ {{/isFile}}
+ {{^isFile}}
+ localVarRequestOptions.FormParameters.Add("{{baseName}}", {{packageName}}.Client.ClientUtils.{{#isPrimitiveType}}ParameterToString{{/isPrimitiveType}}{{^isPrimitiveType}}Serialize{{/isPrimitiveType}}({{paramName}})); // form parameter
+ {{/isFile}}
+ }
+ {{/required}}
+ {{/formParams}}
+ {{#bodyParam}}
+ localVarRequestOptions.Data = {{paramName}};
+ {{/bodyParam}}
+
+ localVarRequestOptions.Operation = "{{classname}}.{{operationId}}";
+ localVarRequestOptions.OperationIndex = operationIndex;
+
+ {{#authMethods}}
+ // authentication ({{name}}) required
+ {{#isApiKey}}
+ {{#isKeyInCookie}}
+ // cookie parameter support
+ if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+ {
+ localVarRequestOptions.Cookies.Add(new Cookie("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+ }
+ {{/isKeyInCookie}}
+ {{#isKeyInHeader}}
+ if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+ {
+ localVarRequestOptions.HeaderParameters.Add("{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}"));
+ }
+ {{/isKeyInHeader}}
+ {{#isKeyInQuery}}
+ if (!string.IsNullOrEmpty(this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")))
+ {
+ localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("", "{{keyParamName}}", this.Configuration.GetApiKeyWithPrefix("{{keyParamName}}")));
+ }
+ {{/isKeyInQuery}}
+ {{/isApiKey}}
+ {{#isBasic}}
+ {{#isBasicBasic}}
+ // http basic authentication required
+ if (!string.IsNullOrEmpty(this.Configuration.Username) || !string.IsNullOrEmpty(this.Configuration.Password) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
+ {
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Basic " + {{packageName}}.Client.ClientUtils.Base64Encode(this.Configuration.Username + ":" + this.Configuration.Password));
+ }
+ {{/isBasicBasic}}
+ {{#isBasicBearer}}
+ // bearer authentication required
+ if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
+ {
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+ }
+ {{/isBasicBearer}}
+ {{/isBasic}}
+ {{#isOAuth}}
+ // oauth required
+ if (!localVarRequestOptions.HeaderParameters.ContainsKey("Authorization"))
+ {
+ if (!string.IsNullOrEmpty(this.Configuration.AccessToken))
+ {
+ localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken);
+ }
+ {{#hasOAuthMethods}}
+ else if (!string.IsNullOrEmpty(this.Configuration.OAuthTokenUrl) &&
+ !string.IsNullOrEmpty(this.Configuration.OAuthClientId) &&
+ !string.IsNullOrEmpty(this.Configuration.OAuthClientSecret) &&
+ this.Configuration.OAuthFlow != null)
+ {
+ localVarRequestOptions.OAuth = true;
+ }
+ {{/hasOAuthMethods}}
+ }
+ {{/isOAuth}}
+ {{#isHttpSignature}}
+ if (this.Configuration.HttpSigningConfiguration != null)
+ {
+ var HttpSigningHeaders = this.Configuration.HttpSigningConfiguration.GetHttpSignedHeader(this.Configuration.BasePath, "{{{httpMethod}}}", "{{{path}}}", localVarRequestOptions);
+ foreach (var headerItem in HttpSigningHeaders)
+ {
+ if (localVarRequestOptions.HeaderParameters.ContainsKey(headerItem.Key))
+ {
+ localVarRequestOptions.HeaderParameters[headerItem.Key] = new List() { headerItem.Value };
+ }
+ else
+ {
+ localVarRequestOptions.HeaderParameters.Add(headerItem.Key, headerItem.Value);
+ }
+ }
+ }
+ {{/isHttpSignature}}
+ {{/authMethods}}
+
+ // make the HTTP request
+ var localVarResponse = await this.AsynchronousClient.{{#lambda.titlecase}}{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}{{/lambda.titlecase}}Async<{{{returnType}}}{{^returnType}}Object{{/returnType}}>("{{{path}}}", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false);
+
+ if (this.ExceptionFactory != null)
+ {
+ Exception _exception = this.ExceptionFactory("{{operationId}}", localVarResponse);
+ if (_exception != null)
+ {
+ throw _exception;
+ }
+ }
+
+ return localVarResponse;
+ }
+
+ {{/supportsAsync}}
+ {{/operation}}
+ }
+ {{/operations}}
+}
diff --git a/generation/templates/api_doc.mustache b/generation/templates/api_doc.mustache
new file mode 100644
index 00000000..da165463
--- /dev/null
+++ b/generation/templates/api_doc.mustache
@@ -0,0 +1,162 @@
+# {{packageName}}.{{apiPackage}}.{{classname}}{{#description}}
+{{.}}{{/description}}
+
+All URIs are relative to *{{{basePath}}}*
+
+| Method | HTTP request | Description |
+|--------|--------------|-------------|
+{{#operations}}
+{{#operation}}
+| [**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} |
+{{/operation}}
+{{/operations}}
+
+{{#operations}}
+{{#operation}}
+
+# **{{{operationId}}}**
+> {{returnType}}{{^returnType}}void{{/returnType}} {{operationId}} ({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = null{{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+
+{{{summary}}}{{#notes}}
+
+{{{.}}}{{/notes}}
+
+### Example
+```csharp
+using System.Collections.Generic;
+using System.Diagnostics;
+{{#useHttpClient}}
+using System.Net.Http;
+{{/useHttpClient}}
+using {{packageName}}.{{apiPackage}};
+using {{packageName}}.Client;
+using {{packageName}}.{{modelPackage}};
+
+namespace Example
+{
+ public class {{operationId}}Example
+ {
+ public static void Main()
+ {
+ Configuration config = new Configuration();
+ config.BasePath = "{{{basePath}}}";
+ {{#hasAuthMethods}}
+ {{#authMethods}}
+ {{#isBasicBasic}}
+ // Configure HTTP basic authorization: {{{name}}}
+ config.Username = "YOUR_USERNAME";
+ config.Password = "YOUR_PASSWORD";
+ {{/isBasicBasic}}
+ {{#isBasicBearer}}
+ // Configure Bearer token for authorization: {{{name}}}
+ config.AccessToken = "YOUR_BEARER_TOKEN";
+ {{/isBasicBearer}}
+ {{#isApiKey}}
+ // Configure API key authorization: {{{name}}}
+ config.AddApiKey("{{{keyParamName}}}", "YOUR_API_KEY");
+ // Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
+ // config.AddApiKeyPrefix("{{{keyParamName}}}", "Bearer");
+ {{/isApiKey}}
+ {{#isOAuth}}
+ // Configure OAuth2 access token for authorization: {{{name}}}
+ config.AccessToken = "YOUR_ACCESS_TOKEN";
+ {{/isOAuth}}
+ {{/authMethods}}
+
+ {{/hasAuthMethods}}
+ {{#useHttpClient}}
+ // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes
+ HttpClient httpClient = new HttpClient();
+ HttpClientHandler httpClientHandler = new HttpClientHandler();
+ var apiInstance = new {{classname}}(httpClient, config, httpClientHandler);
+ {{/useHttpClient}}
+ {{^useHttpClient}}
+ var apiInstance = new {{classname}}(config);
+ {{/useHttpClient}}
+ {{#allParams}}
+ {{#isPrimitiveType}}
+ var {{paramName}} = {{{example}}}; // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+ {{/isPrimitiveType}}
+ {{^isPrimitiveType}}
+ var {{paramName}} = new {{{dataType}}}(); // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}
+ {{/isPrimitiveType}}
+ {{/allParams}}
+
+ try
+ {
+ {{#summary}}
+ // {{{.}}}
+ {{/summary}}
+ {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}}
+ Debug.WriteLine(result);{{/returnType}}
+ }
+ catch (ApiException e)
+ {
+ Debug.Print("Exception when calling {{classname}}.{{operationId}}: " + e.Message);
+ Debug.Print("Status Code: " + e.ErrorCode);
+ Debug.Print(e.StackTrace);
+ }
+ }
+ }
+}
+```
+
+#### Using the {{operationId}}WithHttpInfo variant
+This returns an ApiResponse object which contains the response data, status code and headers.
+
+```csharp
+try
+{
+ {{#summary}}
+ // {{{.}}}
+ {{/summary}}
+ {{#returnType}}ApiResponse<{{{.}}}> response = {{/returnType}}apiInstance.{{{operationId}}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}}
+ Debug.Write("Status Code: " + response.StatusCode);
+ Debug.Write("Response Headers: " + response.Headers);
+ Debug.Write("Response Body: " + response.Data);{{/returnType}}
+}
+catch (ApiException e)
+{
+ Debug.Print("Exception when calling {{classname}}.{{operationId}}WithHttpInfo: " + e.Message);
+ Debug.Print("Status Code: " + e.ErrorCode);
+ Debug.Print(e.StackTrace);
+}
+```
+
+### Parameters
+{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
+| Name | Type | Description | Notes |
+|------|------|-------------|-------|
+{{/-last}}
+{{/allParams}}
+{{#allParams}}
+| **{{paramName}}** | {{#isFile}}**{{dataType}}**{{/isFile}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isFile}}[**{{dataType}}**]({{#isContainer}}{{baseType}}{{/isContainer}}{{^isContainer}}{{dataType}}{{/isContainer}}.md){{/isFile}}{{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{.}}]{{/defaultValue}} |
+{{/allParams}}
+
+### Return type
+
+{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}}
+
+### Authorization
+
+{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}}
+
+### HTTP request headers
+
+ - **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
+ - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}}
+
+{{#responses.0}}
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+{{#responses}}
+| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} |
+{{/responses}}
+{{/responses.0}}
+
+[[Back to top]](#) [[Back to API list]](../{{#useGenericHost}}../{{/useGenericHost}}README.md#documentation-for-api-endpoints) [[Back to Model list]](../{{#useGenericHost}}../{{/useGenericHost}}README.md#documentation-for-models) [[Back to README]](../{{#useGenericHost}}../{{/useGenericHost}}README.md)
+
+{{/operation}}
+{{/operations}}
diff --git a/generation/templates/api_test.mustache b/generation/templates/api_test.mustache
new file mode 100644
index 00000000..78319978
--- /dev/null
+++ b/generation/templates/api_test.mustache
@@ -0,0 +1,77 @@
+{{>partial_header}}
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+{{#useRestSharp}}
+using RestSharp;
+{{/useRestSharp}}
+using Xunit;
+
+using {{packageName}}.Client;
+using {{packageName}}.{{apiPackage}};
+{{#hasImport}}
+// uncomment below to import models
+//using {{packageName}}.{{modelPackage}};
+{{/hasImport}}
+
+namespace {{packageName}}.Test.Api
+{
+ ///
+ /// Class for testing {{classname}}
+ ///
+ ///
+ /// This file is automatically generated by OpenAPI Generator (https://openapi-generator.tech).
+ /// Please update the test case below to test the API endpoint.
+ ///
+ public class {{classname}}Tests : IDisposable
+ {
+ {{^nonPublicApi}}
+ private {{classname}} instance;
+
+ {{/nonPublicApi}}
+ public {{classname}}Tests()
+ {
+ {{^nonPublicApi}}
+ instance = new {{classname}}();
+ {{/nonPublicApi}}
+ }
+
+ public void Dispose()
+ {
+ // Cleanup when everything is done.
+ }
+
+ ///
+ /// Test an instance of {{classname}}
+ ///
+ [Fact]
+ public void {{operationId}}InstanceTest()
+ {
+ // TODO uncomment below to test 'IsType' {{classname}}
+ //Assert.IsType<{{classname}}>(instance);
+ }
+ {{#operations}}
+ {{#operation}}
+
+ ///
+ /// Test {{operationId}}
+ ///
+ [Fact]
+ public void {{operationId}}Test()
+ {
+ // TODO uncomment below to test the method and replace null with proper value
+ {{#allParams}}
+ //{{{dataType}}} {{paramName}} = null;
+ {{/allParams}}
+ //{{#returnType}}var response = {{/returnType}}instance.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});
+ {{#returnType}}
+ //Assert.IsType<{{{.}}}>(response);
+ {{/returnType}}
+ }
+ {{/operation}}
+ {{/operations}}
+ }
+}
diff --git a/generation/templates/appveyor.mustache b/generation/templates/appveyor.mustache
new file mode 100644
index 00000000..eb85fc2a
--- /dev/null
+++ b/generation/templates/appveyor.mustache
@@ -0,0 +1,9 @@
+# auto-generated by OpenAPI Generator (https://github.com/OpenAPITools/openapi-generator)
+#
+image: Visual Studio 2019
+clone_depth: 1
+build_script:
+- dotnet build -c Release
+- dotnet test -c Release
+after_build:
+- dotnet pack .\src\{{{packageName}}}\{{{packageName}}}.csproj -o ../../output -c Release --no-build
diff --git a/generation/templates/auth/OAuthAuthenticator.mustache b/generation/templates/auth/OAuthAuthenticator.mustache
new file mode 100644
index 00000000..ae8f3c75
--- /dev/null
+++ b/generation/templates/auth/OAuthAuthenticator.mustache
@@ -0,0 +1,97 @@
+{{>partial_header}}
+
+using System;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using RestSharp;
+using RestSharp.Authenticators;
+
+namespace {{packageName}}.Client.Auth
+{
+ ///
+ /// An authenticator for OAuth2 authentication flows
+ ///
+ public class OAuthAuthenticator : AuthenticatorBase
+ {
+ readonly string _tokenUrl;
+ readonly string _clientId;
+ readonly string _clientSecret;
+ readonly string _grantType;
+ readonly JsonSerializerSettings _serializerSettings;
+ readonly IReadableConfiguration _configuration;
+
+ ///
+ /// Initialize the OAuth2 Authenticator
+ ///
+ public OAuthAuthenticator(
+ string tokenUrl,
+ string clientId,
+ string clientSecret,
+ OAuthFlow? flow,
+ JsonSerializerSettings serializerSettings,
+ IReadableConfiguration configuration) : base("")
+ {
+ _tokenUrl = tokenUrl;
+ _clientId = clientId;
+ _clientSecret = clientSecret;
+ _serializerSettings = serializerSettings;
+ _configuration = configuration;
+
+ switch (flow)
+ {
+ /*case OAuthFlow.ACCESS_CODE:
+ _grantType = "authorization_code";
+ break;
+ case OAuthFlow.IMPLICIT:
+ _grantType = "implicit";
+ break;
+ case OAuthFlow.PASSWORD:
+ _grantType = "password";
+ break;*/
+ case OAuthFlow.APPLICATION:
+ _grantType = "client_credentials";
+ break;
+ default:
+ break;
+ }
+ }
+
+ ///
+ /// Creates an authentication parameter from an access token.
+ ///
+ /// Access token to create a parameter from.
+ /// An authentication parameter.
+ protected override async ValueTask GetAuthenticationParameter(string accessToken)
+ {
+ var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token;
+ return new HeaderParameter(KnownHeaders.Authorization, token);
+ }
+
+ ///
+ /// Gets the token from the OAuth2 server.
+ ///
+ /// An authentication token.
+ async Task GetToken()
+ {
+ var client = new RestClient(_tokenUrl,
+ configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
+
+ var request = new RestRequest()
+ .AddParameter("grant_type", _grantType)
+ .AddParameter("client_id", _clientId)
+ .AddParameter("client_secret", _clientSecret);
+ var response = await client.PostAsync(request).ConfigureAwait(false);
+
+ // RFC6749 - token_type is case insensitive.
+ // RFC6750 - In Authorization header Bearer should be capitalized.
+ // Fix the capitalization irrespective of token_type casing.
+ switch (response.TokenType?.ToLower())
+ {
+ case "bearer":
+ return $"Bearer {response.AccessToken}";
+ default:
+ return $"{response.TokenType} {response.AccessToken}";
+ }
+ }
+ }
+}
diff --git a/generation/templates/auth/OAuthFlow.mustache b/generation/templates/auth/OAuthFlow.mustache
new file mode 100644
index 00000000..768ddd0a
--- /dev/null
+++ b/generation/templates/auth/OAuthFlow.mustache
@@ -0,0 +1,19 @@
+{{>partial_header}}
+
+namespace {{packageName}}.Client.Auth
+{
+ ///
+ /// Available flows for OAuth2 authentication
+ ///
+ public enum OAuthFlow
+ {
+ /// Authorization code flow
+ ACCESS_CODE,
+ /// Implicit flow
+ IMPLICIT,
+ /// Password flow
+ PASSWORD,
+ /// Client credentials flow
+ APPLICATION
+ }
+}
\ No newline at end of file
diff --git a/generation/templates/auth/TokenResponse.mustache b/generation/templates/auth/TokenResponse.mustache
new file mode 100644
index 00000000..f118b97a
--- /dev/null
+++ b/generation/templates/auth/TokenResponse.mustache
@@ -0,0 +1,14 @@
+{{>partial_header}}
+
+using Newtonsoft.Json;
+
+namespace {{packageName}}.Client.Auth
+{
+ class TokenResponse
+ {
+ [JsonProperty("token_type")]
+ public string TokenType { get; set; }
+ [JsonProperty("access_token")]
+ public string AccessToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/generation/templates/git_push.sh.mustache b/generation/templates/git_push.sh.mustache
new file mode 100644
index 00000000..0e3776ae
--- /dev/null
+++ b/generation/templates/git_push.sh.mustache
@@ -0,0 +1,57 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+ git_host="{{{gitHost}}}"
+ echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+ git_user_id="{{{gitUserId}}}"
+ echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+ git_repo_id="{{{gitRepoId}}}"
+ echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+ release_note="{{{releaseNote}}}"
+ echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=$(git remote)
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+ if [ "$GIT_TOKEN" = "" ]; then
+ echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+ git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+ else
+ git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
+ fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
diff --git a/generation/templates/gitignore.mustache b/generation/templates/gitignore.mustache
new file mode 100644
index 00000000..1ee53850
--- /dev/null
+++ b/generation/templates/gitignore.mustache
@@ -0,0 +1,362 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
diff --git a/generation/templates/libraries/generichost/AfterOperationDefaultImplementation.mustache b/generation/templates/libraries/generichost/AfterOperationDefaultImplementation.mustache
new file mode 100644
index 00000000..394c6572
--- /dev/null
+++ b/generation/templates/libraries/generichost/AfterOperationDefaultImplementation.mustache
@@ -0,0 +1,2 @@
+ if (!suppressDefaultLog)
+ Logger.LogInformation("{0,-9} | {1} | {3}", (apiResponseLocalVar.DownloadedAt - apiResponseLocalVar.RequestedAt).TotalSeconds, apiResponseLocalVar.StatusCode, apiResponseLocalVar.Path);
\ No newline at end of file
diff --git a/generation/templates/libraries/generichost/ApiException.mustache b/generation/templates/libraries/generichost/ApiException.mustache
new file mode 100644
index 00000000..c14c1010
--- /dev/null
+++ b/generation/templates/libraries/generichost/ApiException.mustache
@@ -0,0 +1,46 @@
+//
+{{>partial_header}}
+{{#nrt}}
+#nullable enable
+
+{{/nrt}}
+using System;
+
+namespace {{packageName}}.{{clientPackage}}
+{
+ ///
+ /// API Exception
+ ///
+ {{>visibility}} class ApiException : Exception
+ {
+ ///
+ /// The reason the api request failed
+ ///
+ public string{{nrt?}} ReasonPhrase { get; }
+
+ ///
+ /// The HttpStatusCode
+ ///
+ public System.Net.HttpStatusCode StatusCode { get; }
+
+ ///
+ /// The raw data returned by the api
+ ///
+ public string RawContent { get; }
+
+ ///
+ /// Construct the ApiException from parts of the response
+ ///
+ ///
+ ///
+ ///
+ public ApiException(string{{nrt?}} reasonPhrase, System.Net.HttpStatusCode statusCode, string rawContent) : base(reasonPhrase ?? rawContent)
+ {
+ ReasonPhrase = reasonPhrase;
+
+ StatusCode = statusCode;
+
+ RawContent = rawContent;
+ }
+ }
+}
diff --git a/generation/templates/libraries/generichost/ApiFactory.mustache b/generation/templates/libraries/generichost/ApiFactory.mustache
new file mode 100644
index 00000000..a445d216
--- /dev/null
+++ b/generation/templates/libraries/generichost/ApiFactory.mustache
@@ -0,0 +1,49 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using {{packageName}}.{{apiPackage}};
+
+namespace {{packageName}}.{{clientPackage}}
+{
+ ///
+ /// An IApiFactory interface
+ ///
+ {{>visibility}} interface IApiFactory
+ {
+ ///
+ /// A method to create an IApi of type IResult
+ ///
+ ///
+ ///
+ IResult Create() where IResult : IApi;
+ }
+
+ ///
+ /// An ApiFactory
+ ///
+ {{>visibility}} class ApiFactory : IApiFactory
+ {
+ ///
+ /// The service provider
+ ///
+ public IServiceProvider Services { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public ApiFactory(IServiceProvider services)
+ {
+ Services = services;
+ }
+
+ ///
+ /// A method to create an IApi of type IResult
+ ///
+ ///
+ ///
+ public IResult Create() where IResult : IApi
+ {
+ return Services.GetRequiredService();
+ }
+ }
+}
diff --git a/generation/templates/libraries/generichost/ApiKeyToken.mustache b/generation/templates/libraries/generichost/ApiKeyToken.mustache
new file mode 100644
index 00000000..273eb2cc
--- /dev/null
+++ b/generation/templates/libraries/generichost/ApiKeyToken.mustache
@@ -0,0 +1,56 @@
+//
+{{partial_header}}
+{{#nrt}}
+#nullable enable
+
+{{/nrt}}
+using System;
+
+namespace {{packageName}}.{{clientPackage}}
+{
+ ///
+ /// A token constructed from an apiKey.
+ ///
+ {{>visibility}} class ApiKeyToken : TokenBase
+ {
+ private string _raw;
+
+ ///
+ /// The header that this token will be used with.
+ ///
+ public ClientUtils.ApiKeyHeader Header { get; }
+
+ ///
+ /// Constructs an ApiKeyToken object.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ApiKeyToken(string value, ClientUtils.ApiKeyHeader header, string prefix = "Bearer ", TimeSpan? timeout = null) : base(timeout)
+ {
+ Header = header;
+ _raw = $"{ prefix }{ value }";
+ }
+
+ ///
+ /// Places the token in the header.
+ ///
+ ///
+ public virtual void UseInHeader(System.Net.Http.HttpRequestMessage request)
+ {
+ request.Headers.Add(ClientUtils.ApiKeyHeaderToString(Header), _raw);
+ }
+
+ ///
+ /// Places the token in the query.
+ ///
+ ///
+ ///
+ ///
+ public virtual void UseInQuery(System.Net.Http.HttpRequestMessage request, UriBuilder uriBuilder, System.Collections.Specialized.NameValueCollection parseQueryString)
+ {
+ parseQueryString[ClientUtils.ApiKeyHeaderToString(Header)] = Uri.EscapeDataString(_raw).ToString(){{nrt!}};
+ }
+ }
+}
\ No newline at end of file
diff --git a/generation/templates/libraries/generichost/ApiResponseEventArgs`1.mustache b/generation/templates/libraries/generichost/ApiResponseEventArgs`1.mustache
new file mode 100644
index 00000000..aea35fae
--- /dev/null
+++ b/generation/templates/libraries/generichost/ApiResponseEventArgs`1.mustache
@@ -0,0 +1,24 @@
+using System;
+
+namespace {{packageName}}.{{clientPackage}}
+{
+ ///
+ /// Useful for tracking server health
+ ///
+ {{>visibility}} class ApiResponseEventArgs : EventArgs
+ {
+ ///
+ /// The ApiResponse
+ ///
+ public ApiResponse ApiResponse { get; }
+
+ ///
+ /// The ApiResponseEventArgs
+ ///
+ ///
+ public ApiResponseEventArgs(ApiResponse apiResponse)
+ {
+ ApiResponse = apiResponse;
+ }
+ }
+}
diff --git a/generation/templates/libraries/generichost/ApiResponse`1.mustache b/generation/templates/libraries/generichost/ApiResponse`1.mustache
new file mode 100644
index 00000000..5b097c22
--- /dev/null
+++ b/generation/templates/libraries/generichost/ApiResponse`1.mustache
@@ -0,0 +1,170 @@
+//
+{{>partial_header}}
+{{#nrt}}
+#nullable enable
+
+{{/nrt}}
+using System;
+{{^netStandard}}
+using System.Diagnostics.CodeAnalysis;
+{{/netStandard}}
+using System.Net;
+
+namespace {{packageName}}.{{clientPackage}}
+{
+ ///
+ /// Provides a non-generic contract for the ApiResponse wrapper.
+ ///
+ {{>visibility}} partial interface IApiResponse
+ {
+ ///
+ /// The IsSuccessStatusCode from the api response
+ ///
+ bool IsSuccessStatusCode { get; }
+
+ ///
+ /// Gets the status code (HTTP status code)
+ ///
+ /// The status code.
+ HttpStatusCode StatusCode { get; }
+
+ ///
+ /// The raw content of this response.
+ ///
+ string RawContent { get; }
+
+ ///
+ /// The DateTime when the request was retrieved.
+ ///
+ DateTime DownloadedAt { get; }
+
+ ///
+ /// The headers contained in the api response
+ ///
+ System.Net.Http.Headers.HttpResponseHeaders Headers { get; }
+
+ ///
+ /// The path used when making the request.
+ ///
+ string Path { get; }
+
+ ///
+ /// The reason phrase contained in the api response
+ ///
+ string{{nrt?}} ReasonPhrase { get; }
+
+ ///
+ /// The DateTime when the request was sent.
+ ///
+ DateTime RequestedAt { get; }
+
+ ///
+ /// The Uri used when making the request.
+ ///
+ Uri{{nrt?}} RequestUri { get; }
+ }
+
+ ///
+ /// API Response
+ ///
+ {{>visibility}} partial class ApiResponse : IApiResponse
+ {
+ ///
+ /// Gets the status code (HTTP status code)
+ ///
+ /// The status code.
+ public HttpStatusCode StatusCode { get; }
+
+ ///
+ /// The raw data
+ ///
+ public string RawContent { get; protected set; }
+
+ ///
+ /// The IsSuccessStatusCode from the api response
+ ///
+ public bool IsSuccessStatusCode { get; }
+
+ ///
+ /// The reason phrase contained in the api response
+ ///
+ public string{{nrt?}} ReasonPhrase { get; }
+
+ ///
+ /// The headers contained in the api response
+ ///
+ public System.Net.Http.Headers.HttpResponseHeaders Headers { get; }
+
+ ///
+ /// The DateTime when the request was retrieved.
+ ///
+ public DateTime DownloadedAt { get; } = DateTime.UtcNow;
+
+ ///
+ /// The DateTime when the request was sent.
+ ///
+ public DateTime RequestedAt { get; }
+
+ ///
+ /// The path used when making the request.
+ ///
+ public string Path { get; }
+
+ ///
+ /// The Uri used when making the request.
+ ///
+ public Uri{{nrt?}} RequestUri { get; }
+
+ ///
+ /// The
+ ///
+ protected System.Text.Json.JsonSerializerOptions _jsonSerializerOptions;
+
+ ///
+ /// Construct the response using an HttpResponseMessage
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ApiResponse(System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage, string rawContent, string path, DateTime requestedAt, System.Text.Json.JsonSerializerOptions jsonSerializerOptions)
+ {
+ StatusCode = httpResponseMessage.StatusCode;
+ Headers = httpResponseMessage.Headers;
+ IsSuccessStatusCode = httpResponseMessage.IsSuccessStatusCode;
+ ReasonPhrase = httpResponseMessage.ReasonPhrase;
+ RawContent = rawContent;
+ Path = path;
+ RequestUri = httpRequestMessage.RequestUri;
+ RequestedAt = requestedAt;
+ _jsonSerializerOptions = jsonSerializerOptions;
+ OnCreated(httpRequestMessage, httpResponseMessage);
+ }
+
+ partial void OnCreated(System.Net.Http.HttpRequestMessage httpRequestMessage, System.Net.Http.HttpResponseMessage httpResponseMessage);
+ }
+ {{#x-http-statuses-with-return}}
+
+ ///
+ /// An interface for responses of type {{TType}}
+ ///
+ ///
+ {{>visibility}} interface I{{.}} : IApiResponse
+ {
+ ///
+ /// Deserializes the response if the response is {{.}}
+ ///
+ ///
+ TType {{.}}();
+
+ ///
+ /// Returns true if the response is {{.}} and the deserialized response is not null
+ ///
+ ///
+ ///
+ bool Try{{.}}({{#net60OrLater}}[NotNullWhen(true)]{{/net60OrLater}}out TType{{nrt?}} result);
+ }
+ {{/x-http-statuses-with-return}}
+}
diff --git a/generation/templates/libraries/generichost/ApiTestsBase.mustache b/generation/templates/libraries/generichost/ApiTestsBase.mustache
new file mode 100644
index 00000000..3292a1e8
--- /dev/null
+++ b/generation/templates/libraries/generichost/ApiTestsBase.mustache
@@ -0,0 +1,65 @@
+{{>partial_header}}
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using Microsoft.Extensions.Hosting;
+using {{packageName}}.{{clientPackage}};{{#hasImport}}
+using {{packageName}}.{{modelPackage}};{{/hasImport}}
+using {{packageName}}.Extensions;
+
+
+{{>testInstructions}}
+
+
+namespace {{packageName}}.Test.{{apiPackage}}
+{
+ ///
+ /// Base class for API tests
+ ///
+ public class ApiTestsBase
+ {
+ protected readonly IHost _host;
+
+ public ApiTestsBase(string[] args)
+ {
+ _host = CreateHostBuilder(args).Build();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
+ .Configure{{apiName}}((context, services, options) =>
+ {
+ {{#lambda.trimTrailingWithNewLine}}
+ {{#apiKeyMethods}}
+ string apiKeyTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found.");
+ ApiKeyToken apiKeyToken{{-index}} = new{{^net70OrLater}} ApiKeyToken{{/net70OrLater}}(apiKeyTokenValue{{-index}}, ClientUtils.ApiKeyHeader.{{#lambda.titlecase}}{{keyParamName}}{{/lambda.titlecase}}, timeout: TimeSpan.FromSeconds(1));
+ options.AddTokens(apiKeyToken{{-index}});
+
+ {{/apiKeyMethods}}
+ {{#httpBearerMethods}}
+ string bearerTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found.");
+ BearerToken bearerToken{{-index}} = new{{^net70OrLater}} BearerToken{{/net70OrLater}}(bearerTokenValue{{-index}}, timeout: TimeSpan.FromSeconds(1));
+ options.AddTokens(bearerToken{{-index}});
+
+ {{/httpBearerMethods}}
+ {{#httpBasicMethods}}
+ string basicTokenUsername{{-index}} = context.Configuration[""] ?? throw new Exception("Username not found.");
+ string basicTokenPassword{{-index}} = context.Configuration[""] ?? throw new Exception("Password not found.");
+ BasicToken basicToken{{-index}} = new{{^net70OrLater}} BasicToken{{/net70OrLater}}(basicTokenUsername{{-index}}, basicTokenPassword{{-index}}, timeout: TimeSpan.FromSeconds(1));
+ options.AddTokens(basicToken{{-index}});
+
+ {{/httpBasicMethods}}
+ {{#httpSignatureMethods}}
+ HttpSigningConfiguration config{{-index}} = new{{^net70OrLater}} HttpSigningConfiguration{{/net70OrLater}}("", "", null, new List(), HashAlgorithmName.SHA256, "", 0);
+ HttpSignatureToken httpSignatureToken{{-index}} = new{{^net70OrLater}} HttpSignatureToken{{/net70OrLater}}(config{{-index}}, timeout: TimeSpan.FromSeconds(1));
+ options.AddTokens(httpSignatureToken{{-index}});
+
+ {{/httpSignatureMethods}}
+ {{#oauthMethods}}
+ string oauthTokenValue{{-index}} = context.Configuration[""] ?? throw new Exception("Token not found.");
+ OAuthToken oauthToken{{-index}} = new{{^net70OrLater}} OAuthToken{{/net70OrLater}}(oauthTokenValue{{-index}}, timeout: TimeSpan.FromSeconds(1));
+ options.AddTokens(oauthToken{{-index}});
+ {{/oauthMethods}}
+ {{/lambda.trimTrailingWithNewLine}}
+ });
+ }
+}
diff --git a/generation/templates/libraries/generichost/AsModel.mustache b/generation/templates/libraries/generichost/AsModel.mustache
new file mode 100644
index 00000000..96bedfa4
--- /dev/null
+++ b/generation/templates/libraries/generichost/AsModel.mustache
@@ -0,0 +1,4 @@
+// This logic may be modified with the AsModel.mustache template
+return Is{{vendorExtensions.x-http-status}}
+ ? System.Text.Json.JsonSerializer.Deserialize<{{#isModel}}{{^containerType}}{{packageName}}.{{modelPackage}}.{{/containerType}}{{/isModel}}{{{dataType}}}>(RawContent, _jsonSerializerOptions)
+ : {{#net60OrLater}}null{{/net60OrLater}}{{^net60OrLater}}default{{/net60OrLater}};
diff --git a/generation/templates/libraries/generichost/Assembly.mustache b/generation/templates/libraries/generichost/Assembly.mustache
new file mode 100644
index 00000000..a22cc232
--- /dev/null
+++ b/generation/templates/libraries/generichost/Assembly.mustache
@@ -0,0 +1,2 @@
+[assembly: InternalsVisibleTo("{{packageName}}.Test")]
+
diff --git a/generation/templates/libraries/generichost/BasicToken.mustache b/generation/templates/libraries/generichost/BasicToken.mustache
new file mode 100644
index 00000000..4cb7023f
--- /dev/null
+++ b/generation/templates/libraries/generichost/BasicToken.mustache
@@ -0,0 +1,46 @@
+//
+{{partial_header}}
+{{#nrt}}
+#nullable enable
+
+{{/nrt}}
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace {{packageName}}.{{clientPackage}}
+{
+ ///