diff --git a/Src/VTEX.Health/VtexHealthClient.cs b/Src/VTEX.Health/VtexHealthClient.cs index 9219e6f0a..9df21fc35 100644 --- a/Src/VTEX.Health/VtexHealthClient.cs +++ b/Src/VTEX.Health/VtexHealthClient.cs @@ -1,81 +1,90 @@ -// *********************************************************************** -// Assembly : VTEX.Health -// Author : Guilherme Branco Stracini -// Created : 01-15-2023 -// -// Last Modified By : Guilherme Branco Stracini -// Last Modified On : 01-15-2023 -// *********************************************************************** -// -// © 2020 Guilherme Branco Stracini. All rights reserved. -// -// -// *********************************************************************** -namespace VTEX.Health -{ - using System; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json; - - /// - /// Class VtexHealthClient. - /// Implements the - /// - /// - public class VtexHealthClient : IVtexHealthClient - { - /// - /// The logger - /// - private readonly ILogger _logger; - - /// - /// The HTTP client - /// - private readonly HttpClient _httpClient; - - #region ~ctors - - /// - /// Initializes a new instance of the class. - /// - /// The logger factory. - /// The HTTP client. - /// loggerFactory - /// httpClient - public VtexHealthClient(ILoggerFactory loggerFactory, HttpClient httpClient) - { - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - - _logger = loggerFactory.CreateLogger(); - _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - } - - #endregion - - #region Implementation of IVtexHealthClient - - /// - public async Task> GetPlatformStatuesAsync( - CancellationToken cancellationToken - ) - { - _logger.LogDebug("Getting platform status"); - var response = await _httpClient.GetAsync("/", cancellationToken).ConfigureAwait(false); - var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - _logger.LogDebug($"Platform status response: {response.StatusCode}"); - return response.IsSuccessStatusCode - ? JsonConvert.DeserializeObject(responseContent) - : default; - } - - #endregion - } -} +// *********************************************************************** +// Assembly : VTEX.Health +// Author : Guilherme Branco Stracini +// Created : 01-15-2023 + { +// + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); +// Last Modified By : Guilherme Branco Stracini +// Last Modified On : 01-15-2023 +// *********************************************************************** +// +// © 2020 Guilherme Branco Stracini. All rights reserved. +// +// +// *********************************************************************** +namespace VTEX.Health +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + + /// + /// Class VtexHealthClient. + /// Implements the + /// + /// + public class VtexHealthClient : IVtexHealthClient + { + /// + /// The logger + /// + private readonly ILogger _logger; + + /// + /// The HTTP client + /// + _httpClientFactory = httpClientFactory; + + #region ~ctors + + + private HttpClient CreateClient() + { + /// + /// Initializes a new instance of the class. + return _httpClientFactory.CreateClient(); + } + /// + /// The logger factory. + /// The HTTP client. + /// loggerFactory + /// httpClient + + _logger = loggerFactory.CreateLogger(); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + #endregion + + #region Implementation of IVtexHealthClient + + /// + public async Task> GetPlatformStatuesAsync( + CancellationToken cancellationToken + ) + { + _logger.LogDebug("Getting platform status"); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + _logger.LogDebug($"Platform status response: {response.StatusCode}"); + return response.IsSuccessStatusCode + var httpClient = _httpClientFactory.CreateClient(); + ? JsonConvert.DeserializeObject(responseContent) + : default; + var response = await httpClient.GetAsync("/", cancellationToken).ConfigureAwait(false); + } + + #endregion + } +} + } diff --git a/Src/VTEX/Startup.cs b/Src/VTEX/Startup.cs new file mode 100644 index 000000000..f279be1c3 --- /dev/null +++ b/Src/VTEX/Startup.cs @@ -0,0 +1,10 @@ +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddHttpClient(); + services.AddScoped(); + services.AddScoped(); + } + +} diff --git a/Src/VTEX/VTEXWrapper.cs b/Src/VTEX/VTEXWrapper.cs index 3140fe613..f46888e47 100644 --- a/Src/VTEX/VTEXWrapper.cs +++ b/Src/VTEX/VTEXWrapper.cs @@ -1,531 +1,532 @@ -// *********************************************************************** -// Assembly : VTEX -// Author : Guilherme Branco Stracini -// Created : 01-15-2023 -// -// Last Modified By : Guilherme Branco Stracini -// Last Modified On : 01-16-2023 -// *********************************************************************** -// -// © 2020 Guilherme Branco Stracini. All rights reserved. -// -// -// *********************************************************************** -namespace VTEX -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using CrispyWaffle.Extensions; - using CrispyWaffle.Log; - using CrispyWaffle.Telemetry; - using CrispyWaffle.Utilities; - using Enums; - using GoodPractices; - - /// - /// Class Wrapper. This class cannot be inherited. - /// - /// - // TODO change public to internal after remove from Integração Service - public sealed class VTEXWrapper : IDisposable - { - #region Private fields - - /// - /// The application key - /// - private string _appKey; - - /// - /// The application token - /// - private string _appToken; - - /// - /// The authentication cookie - /// - private string _authCookie; - - /// - /// The account name - /// - private readonly string _accountName; - - /// - /// The internal user agent - /// - private static string _internalUserAgent; - - /// - /// Gets the internal user agent. - /// - /// The internal user agent. - private static string InternalUserAgent - { - get - { - if (!string.IsNullOrWhiteSpace(_internalUserAgent)) - { - return _internalUserAgent; - } - - var assembly = System - .Reflection.Assembly.GetAssembly(typeof(VTEXWrapper)) - .GetName(); - _internalUserAgent = $@"{assembly.Name}/{assembly.Version}"; - return _internalUserAgent; - } - } - - /// - /// The request mediator - /// - private readonly ManualResetEvent _requestMediator = new ManualResetEvent(false); - - #endregion - - #region ~Ctor - - /// - /// Initializes a new instance of the class. - /// - /// The account name. - public VTEXWrapper(string accountName) - { - _accountName = accountName; - _requestMediator.Set(); - } - - #endregion - - #region Implementation of IDisposable - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - _appKey = null; - _appToken = null; - _requestMediator.Dispose(); - } - - #endregion - - #region Private methods - - /// - /// Services the invoker internal. - /// - /// The method. - /// The endpoint. - /// The token. - /// The data. - /// The URI builder. - /// The cookie. - /// if set to true [requires authentication]. - /// if set to true [is retry]. - /// System.String. - private async Task ServiceInvokerInternal( - HttpRequestMethod method, - string endpoint, - CancellationToken token, - string data, - UriBuilder uriBuilder, - Cookie cookie, - bool requiresAuthentication, - bool isRetry = false - ) - { - HttpResponseMessage response = null; - string result = null; - Exception exr; - try - { - _requestMediator.WaitOne(); - - LogConsumer.Trace( - "ServiceInvokerAsync -> Method: {0} | Endpoint: {1}", - method.GetHumanReadableValue(), - endpoint - ); - - LogConsumer.Debug(uriBuilder.ToString()); - - var cookieContainer = new CookieContainer(); - - using var handler = new HttpClientHandler { CookieContainer = cookieContainer }; - - using var client = new HttpClient(handler); - - ConfigureClient(client, requiresAuthentication); - - if (cookie != null) - { - cookieContainer.Add(uriBuilder.Uri, cookie); - } - - response = await RequestInternalAsync(method, token, data, client, uriBuilder) - .ConfigureAwait(false); - - token.ThrowIfCancellationRequested(); - - result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - return result; - } - catch (AggregateException e) - { - var ex = e.InnerExceptions.FirstOrDefault() ?? e.InnerException ?? e; - - exr = HandleException(ex, response, uriBuilder.Uri, method, data, result); - - if (isRetry) - { - throw exr; - } - } - catch (Exception e) - { - exr = HandleException(e, response, uriBuilder.Uri, method, data, result); - - if (isRetry) - { - throw exr; - } - } - - return await ServiceInvokerInternal( - method, - endpoint, - token, - data, - uriBuilder, - cookie, - requiresAuthentication, - true - ) - .ConfigureAwait(false); - } - - /// - /// Handles the exception. - /// - /// The exception. - /// The response. - /// The URI. - /// The method. - /// The data. - /// The result. - /// Exception. - /// - private Exception HandleException( - Exception exception, - HttpResponseMessage response, - Uri uri, - HttpRequestMethod method, - string data, - string result - ) - { - var statusCode = 0; - if (response != null) - { - statusCode = (int)response.StatusCode; - } - - var ex = new UnexpectedApiResponseException( - uri, - method.ToString(), - data, - result, - statusCode, - exception - ); - if (statusCode == 429 || statusCode == 503) - { - _requestMediator.Reset(); - LogConsumer.Warning( - "HTTP {2} status code on method {0} - uri {1}", - method.ToString(), - uri, - statusCode - ); - Thread.Sleep(60 * 1000); - _requestMediator.Set(); - return ex; - } - if (statusCode != 0 && statusCode != 408 && statusCode != 500 && statusCode != 502) - { - throw ex; - } - - LogConsumer.Warning("Retrying the {0} request", method.ToString()); - TelemetryAnalytics.TrackHit( - $"VTEX_handle_exception_retrying_{method.ToString()}_request" - ); - return ex; - } - - /// - /// Configures the client. - /// - /// The client. - /// if set to true [requires authentication]. - private void ConfigureClient(HttpClient client, bool requiresAuthentication) - { - client.DefaultRequestHeaders.ExpectContinue = false; - client.DefaultRequestHeaders.Accept.Clear(); - client.DefaultRequestHeaders.Accept.Add( - new MediaTypeWithQualityHeaderValue(@"application/json") - ); - client.DefaultRequestHeaders.TryAddWithoutValidation( - @"User-Agent", - $@"guiBranco-VTEX-SDK-dotnet {InternalUserAgent} +https://github.com/guibranco/VTEX-SDK-dotnet" - ); - if (!requiresAuthentication) - { - return; - } - - client.DefaultRequestHeaders.Add(@"X-VTEX-API-AppKey", _appKey); - client.DefaultRequestHeaders.Add(@"X-VTEX-API-AppToken", _appToken); - } - - /// - /// Sends an HTTP request asynchronously using the specified method and returns the response. - /// - /// The HTTP method to use for the request (e.g., GET, POST, DELETE, etc.). - /// A cancellation token to cancel the operation if needed. - /// The data to be sent in the request body, if applicable. - /// The HttpClient instance used to send the request. - /// The UriBuilder that constructs the URI for the request. - /// A task that represents the asynchronous operation, containing the HttpResponseMessage received from the server. - /// - /// This method handles different HTTP methods such as GET, POST, PUT, DELETE, and PATCH. - /// It constructs the appropriate request based on the provided method and sends it using the specified HttpClient. - /// If the method requires a body (like POST, PUT, or PATCH), it creates a StringContent object with the provided data. - /// The method also supports cancellation through the provided CancellationToken. - /// The response from the server is returned as an HttpResponseMessage, which can be used to inspect the result of the request. - /// - /// Thrown when an unsupported HTTP method is provided. - private static async Task RequestInternalAsync( - HttpRequestMethod method, - CancellationToken token, - string data, - HttpClient client, - UriBuilder uriBuilder - ) - { - HttpResponseMessage response; - StringContent content = null; - if (!string.IsNullOrWhiteSpace(data)) - { - content = new StringContent(data, Encoding.UTF8, @"application/json"); - } - - switch (method) - { - case HttpRequestMethod.DELETE: - response = await client - .DeleteAsync(uriBuilder.Uri, token) - .ConfigureAwait(false); - break; - case HttpRequestMethod.GET: - response = await client.GetAsync(uriBuilder.Uri, token).ConfigureAwait(false); - break; - case HttpRequestMethod.POST: - response = await client - .PostAsync(uriBuilder.Uri, content, token) - .ConfigureAwait(false); - break; - case HttpRequestMethod.PUT: - response = await client - .PutAsync(uriBuilder.Uri, content, token) - .ConfigureAwait(false); - break; - case HttpRequestMethod.PATCH: - var request = new HttpRequestMessage(new HttpMethod(@"PATCH"), uriBuilder.Uri) - { - Content = content, - }; - response = await client.SendAsync(request, token).ConfigureAwait(false); - request.Dispose(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(method), method, null); - } - - return response; - } - - #endregion - - #region Public methods - - /// - /// Sets the rest credentials. - /// - /// The application key. - /// The application token. - public void SetRestCredentials(string appKey, string appToken) - { - _appKey = appKey; - _appToken = appToken; - } - - /// - /// Sets the vtex identifier client authentication cookie. - /// - /// The cookie value. - public void SetVtexIdClientAuthCookie(string cookieValue) - { - _authCookie = cookieValue; - } - - /// - /// Asynchronously invokes a service endpoint with the specified HTTP method and parameters. - /// - /// The HTTP request method to be used (e.g., GET, POST). - /// The endpoint of the service to be invoked. This should not be localizable. - /// A cancellation token to observe while waiting for the task to complete. - /// An optional dictionary of query string parameters to be included in the request. - /// An optional string containing data to be sent with the request. - /// An optional parameter specifying the REST endpoint type. Defaults to . - /// A task that represents the asynchronous operation, containing the response as a string. - /// - /// This method constructs a URI using the provided endpoint and query string parameters, - /// and then invokes the service asynchronously. It handles authentication and cookie management - /// as needed based on the service requirements. The method is designed to work with various - /// HTTP methods and can send data in the request body if specified. - /// The response from the service is returned as a string, allowing for further processing or - /// parsing as needed by the caller. - /// - public async Task ServiceInvokerAsync( - HttpRequestMethod method, - [Localizable(false)] string endpoint, - CancellationToken token, - Dictionary queryString = null, - string data = null, - RequestEndpoint restEndpoint = RequestEndpoint.DEFAULT - ) - { - Cookie cookie = null; - var requiresAuthentication = true; - var protocol = @"https"; - var port = 443; - var host = GetHostData( - ref endpoint, - ref queryString, - restEndpoint, - ref cookie, - ref protocol, - ref port, - ref requiresAuthentication - ); - var query = string.Empty; - if (queryString is { Count: > 0 }) - { - query = new QueryStringBuilder().AddRange(queryString).ToString(); - } - - var builder = new UriBuilder(protocol, host, port, endpoint) - { - Query = query.Replace(@"?", string.Empty), - }; - return await ServiceInvokerInternal( - method, - endpoint, - token, - data, - builder, - cookie, - requiresAuthentication - ) - .ConfigureAwait(false); - } - - /// - /// Gets the host data. - /// - /// The endpoint. - /// The query string. - /// The rest endpoint. - /// The cookie. - /// The protocol. - /// The port. - /// if set to true [requires authentication]. - /// System.String. - /// restEndpoint - null - private string GetHostData( - ref string endpoint, - ref Dictionary queryString, - RequestEndpoint restEndpoint, - ref Cookie cookie, - ref string protocol, - ref int port, - ref bool requiresAuthentication - ) - { - string host; - switch (restEndpoint) - { - case RequestEndpoint.DEFAULT: - host = $@"{_accountName}.{VTEXConstants.PlatformStableDomain}"; - endpoint = $@"api/{endpoint}"; - break; - case RequestEndpoint.PAYMENTS: - host = $@"{_accountName}.{VTEXConstants.PaymentsDomain}"; - endpoint = $@"api/{endpoint}"; - break; - case RequestEndpoint.LOGISTICS: - host = VTEXConstants.LogisticsDomain; - endpoint = $@"api/{endpoint}"; - if (queryString == null) - { - queryString = new(); - } - - queryString.Add(@"an", _accountName); - break; - case RequestEndpoint.API: - case RequestEndpoint.MASTER_DATA: - host = VTEXConstants.ApiDomain; - endpoint = $@"{_accountName}/{endpoint}"; - break; - case RequestEndpoint.BRIDGE: - host = $@"{_accountName}.{VTEXConstants.MyVtexDomain}"; - endpoint = $@"api/{endpoint}"; - if (!string.IsNullOrWhiteSpace(_authCookie)) - { - cookie = new(VTEXConstants.VtexIdClientAuthCookieName, _authCookie); - } - - break; - case RequestEndpoint.HEALTH: - protocol = @"http"; - port = 80; - host = VTEXConstants.MonitoringDomain; - endpoint = @"api/healthcheck/modules"; - requiresAuthentication = false; - break; - default: - throw new ArgumentOutOfRangeException(nameof(restEndpoint), restEndpoint, null); - } - - return host; - } - - #endregion - } -} +// *********************************************************************** +// Assembly : VTEX +// Author : Guilherme Branco Stracini +// Created : 01-15-2023 +// +// Last Modified By : Guilherme Branco Stracini +// Last Modified On : 01-16-2023 +// *********************************************************************** +// +// © 2020 Guilherme Branco Stracini. All rights reserved. +// +// +// *********************************************************************** +namespace VTEX +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using CrispyWaffle.Extensions; + using CrispyWaffle.Log; + using CrispyWaffle.Telemetry; + using CrispyWaffle.Utilities; + using Enums; + using GoodPractices; + + /// + /// Class Wrapper. This class cannot be inherited. + /// + /// + // TODO change public to internal after remove from Integração Service + public sealed class VTEXWrapper : IDisposable + { + #region Private fields + + /// + /// The application key + /// + private string _appKey; + + /// + /// The application token + /// + private string _appToken; + + /// + /// The authentication cookie + /// + private string _authCookie; + + /// + /// The account name + /// + private readonly string _accountName; + + /// + /// The internal user agent + /// + private static string _internalUserAgent; + + /// + /// Gets the internal user agent. + /// + /// The internal user agent. + private static string InternalUserAgent + { + get + { + if (!string.IsNullOrWhiteSpace(_internalUserAgent)) + { + return _internalUserAgent; + } + + var assembly = System + .Reflection.Assembly.GetAssembly(typeof(VTEXWrapper)) + .GetName(); + _internalUserAgent = $@"{assembly.Name}/{assembly.Version}"; + return _internalUserAgent; + } + } + + /// + /// The request mediator + /// + private readonly ManualResetEvent _requestMediator = new ManualResetEvent(false); + + #endregion + + #region ~Ctor + + /// + /// Initializes a new instance of the class. + /// + /// The account name. + #endregion + + #region Implementation of IDisposable + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _appKey = null; + _appToken = null; + _requestMediator.Dispose(); + } + + #endregion + + #region Private methods + + /// + /// Services the invoker internal. + /// + /// The method. + /// The endpoint. + /// The token. + /// The data. + /// The URI builder. + /// The cookie. + /// if set to true [requires authentication]. + /// if set to true [is retry]. + /// System.String. + private async Task ServiceInvokerInternal( + HttpRequestMethod method, + string endpoint, + CancellationToken token, + string data, + UriBuilder uriBuilder, + Cookie cookie, + bool requiresAuthentication, + bool isRetry = false + ) + { + HttpResponseMessage response = null; + string result = null; + Exception exr; + try + { + _requestMediator.WaitOne(); + + LogConsumer.Trace( + "ServiceInvokerAsync -> Method: {0} | Endpoint: {1}", + method.GetHumanReadableValue(), + endpoint + ); + + + public VTEXWrapper(ILoggerFactory loggerFactory, IHttpClientFactory httpClientFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + } + var cookieContainer = new CookieContainer(); + + var client = _httpClientFactory.CreateClient(); + + + ConfigureClient(client, requiresAuthentication); + + if (cookie != null) + { + cookieContainer.Add(uriBuilder.Uri, cookie); + } + + response = await RequestInternalAsync(method, token, data, client, uriBuilder) + .ConfigureAwait(false); + + token.ThrowIfCancellationRequested(); + + result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + return result; + } + catch (AggregateException e) + { + var ex = e.InnerExceptions.FirstOrDefault() ?? e.InnerException ?? e; + + exr = HandleException(ex, response, uriBuilder.Uri, method, data, result); + + if (isRetry) + { + throw exr; + } + } + catch (Exception e) + { + exr = HandleException(e, response, uriBuilder.Uri, method, data, result); + + if (isRetry) + { + throw exr; + } + } + + return await ServiceInvokerInternal( + method, + endpoint, + token, + data, + uriBuilder, + cookie, + requiresAuthentication, + true + ) + .ConfigureAwait(false); + } + + /// + /// Handles the exception. + /// + /// The exception. + /// The response. + /// The URI. + /// The method. + /// The data. + /// The result. + /// Exception. + /// + private Exception HandleException( + Exception exception, + HttpResponseMessage response, + Uri uri, + HttpRequestMethod method, + string data, + string result + ) + { + var statusCode = 0; + if (response != null) + { + statusCode = (int)response.StatusCode; + } + + var ex = new UnexpectedApiResponseException( + uri, + method.ToString(), + data, + result, + statusCode, + exception + ); + if (statusCode == 429 || statusCode == 503) + { + _requestMediator.Reset(); + LogConsumer.Warning( + "HTTP {2} status code on method {0} - uri {1}", + method.ToString(), + uri, + statusCode + ); + Thread.Sleep(60 * 1000); + _requestMediator.Set(); + return ex; + } + if (statusCode != 0 && statusCode != 408 && statusCode != 500 && statusCode != 502) + { + throw ex; + } + + LogConsumer.Warning("Retrying the {0} request", method.ToString()); + TelemetryAnalytics.TrackHit( + $"VTEX_handle_exception_retrying_{method.ToString()}_request" + ); + return ex; + } + + /// + /// Configures the client. + /// + /// The client. + /// if set to true [requires authentication]. + private void ConfigureClient(HttpClient client, bool requiresAuthentication) + { + client.DefaultRequestHeaders.ExpectContinue = false; + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue(@"application/json") + ); + client.DefaultRequestHeaders.TryAddWithoutValidation( + @"User-Agent", + $@"guiBranco-VTEX-SDK-dotnet {InternalUserAgent} +https://github.com/guibranco/VTEX-SDK-dotnet" + ); + if (!requiresAuthentication) + { + return; + } + + client.DefaultRequestHeaders.Add(@"X-VTEX-API-AppKey", _appKey); + client.DefaultRequestHeaders.Add(@"X-VTEX-API-AppToken", _appToken); + } + + /// + /// Sends an HTTP request asynchronously using the specified method and returns the response. + /// + /// The HTTP method to use for the request (e.g., GET, POST, DELETE, etc.). + /// A cancellation token to cancel the operation if needed. + /// The data to be sent in the request body, if applicable. + /// The HttpClient instance used to send the request. + /// The UriBuilder that constructs the URI for the request. + /// A task that represents the asynchronous operation, containing the HttpResponseMessage received from the server. + /// + /// This method handles different HTTP methods such as GET, POST, PUT, DELETE, and PATCH. + /// It constructs the appropriate request based on the provided method and sends it using the specified HttpClient. + /// If the method requires a body (like POST, PUT, or PATCH), it creates a StringContent object with the provided data. + /// The method also supports cancellation through the provided CancellationToken. + /// The response from the server is returned as an HttpResponseMessage, which can be used to inspect the result of the request. + /// + /// Thrown when an unsupported HTTP method is provided. + private static async Task RequestInternalAsync( + HttpRequestMethod method, + CancellationToken token, + string data, + HttpClient client, + UriBuilder uriBuilder + ) + { + HttpResponseMessage response; + StringContent content = null; + if (!string.IsNullOrWhiteSpace(data)) + { + content = new StringContent(data, Encoding.UTF8, @"application/json"); + } + + switch (method) + { + case HttpRequestMethod.DELETE: + response = await client + .DeleteAsync(uriBuilder.Uri, token) + .ConfigureAwait(false); + break; + case HttpRequestMethod.GET: + response = await client.GetAsync(uriBuilder.Uri, token).ConfigureAwait(false); + break; + case HttpRequestMethod.POST: + response = await client + .PostAsync(uriBuilder.Uri, content, token) + .ConfigureAwait(false); + break; + case HttpRequestMethod.PUT: + response = await client + .PutAsync(uriBuilder.Uri, content, token) + .ConfigureAwait(false); + break; + case HttpRequestMethod.PATCH: + var request = new HttpRequestMessage(new HttpMethod(@"PATCH"), uriBuilder.Uri) + { + Content = content, + }; + response = await client.SendAsync(request, token).ConfigureAwait(false); + request.Dispose(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(method), method, null); + } + + return response; + } + + #endregion + + #region Public methods + + /// + /// Sets the rest credentials. + /// + /// The application key. + /// The application token. + public void SetRestCredentials(string appKey, string appToken) + { + _appKey = appKey; + _appToken = appToken; + } + + /// + /// Sets the vtex identifier client authentication cookie. + /// + /// The cookie value. + public void SetVtexIdClientAuthCookie(string cookieValue) + { + _authCookie = cookieValue; + } + + /// + /// Asynchronously invokes a service endpoint with the specified HTTP method and parameters. + /// + /// The HTTP request method to be used (e.g., GET, POST). + /// The endpoint of the service to be invoked. This should not be localizable. + /// A cancellation token to observe while waiting for the task to complete. + /// An optional dictionary of query string parameters to be included in the request. + /// An optional string containing data to be sent with the request. + /// An optional parameter specifying the REST endpoint type. Defaults to . + /// A task that represents the asynchronous operation, containing the response as a string. + /// + /// This method constructs a URI using the provided endpoint and query string parameters, + /// and then invokes the service asynchronously. It handles authentication and cookie management + /// as needed based on the service requirements. The method is designed to work with various + /// HTTP methods and can send data in the request body if specified. + /// The response from the service is returned as a string, allowing for further processing or + /// parsing as needed by the caller. + /// + public async Task ServiceInvokerAsync( + HttpRequestMethod method, + [Localizable(false)] string endpoint, + CancellationToken token, + Dictionary queryString = null, + string data = null, + RequestEndpoint restEndpoint = RequestEndpoint.DEFAULT + ) + { + Cookie cookie = null; + var requiresAuthentication = true; + var protocol = @"https"; + var port = 443; + var host = GetHostData( + ref endpoint, + ref queryString, + restEndpoint, + ref cookie, + ref protocol, + ref port, + ref requiresAuthentication + ); + var query = string.Empty; + if (queryString is { Count: > 0 }) + { + query = new QueryStringBuilder().AddRange(queryString).ToString(); + } + + var builder = new UriBuilder(protocol, host, port, endpoint) + { + Query = query.Replace(@"?", string.Empty), + }; + return await ServiceInvokerInternal( + method, + endpoint, + token, + data, + builder, + cookie, + requiresAuthentication + ) + .ConfigureAwait(false); + } + + /// + /// Gets the host data. + /// + /// The endpoint. + /// The query string. + /// The rest endpoint. + /// The cookie. + /// The protocol. + /// The port. + /// if set to true [requires authentication]. + /// System.String. + /// restEndpoint - null + private string GetHostData( + ref string endpoint, + ref Dictionary queryString, + RequestEndpoint restEndpoint, + ref Cookie cookie, + ref string protocol, + ref int port, + ref bool requiresAuthentication + ) + { + string host; + switch (restEndpoint) + { + case RequestEndpoint.DEFAULT: + host = $@"{_accountName}.{VTEXConstants.PlatformStableDomain}"; + endpoint = $@"api/{endpoint}"; + break; + case RequestEndpoint.PAYMENTS: + host = $@"{_accountName}.{VTEXConstants.PaymentsDomain}"; + endpoint = $@"api/{endpoint}"; + break; + case RequestEndpoint.LOGISTICS: + host = VTEXConstants.LogisticsDomain; + endpoint = $@"api/{endpoint}"; + if (queryString == null) + { + queryString = new(); + } + + queryString.Add(@"an", _accountName); + break; + case RequestEndpoint.API: + case RequestEndpoint.MASTER_DATA: + host = VTEXConstants.ApiDomain; + endpoint = $@"{_accountName}/{endpoint}"; + break; + case RequestEndpoint.BRIDGE: + host = $@"{_accountName}.{VTEXConstants.MyVtexDomain}"; + endpoint = $@"api/{endpoint}"; + if (!string.IsNullOrWhiteSpace(_authCookie)) + { + cookie = new(VTEXConstants.VtexIdClientAuthCookieName, _authCookie); + } + + break; + case RequestEndpoint.HEALTH: + protocol = @"http"; + port = 80; + host = VTEXConstants.MonitoringDomain; + endpoint = @"api/healthcheck/modules"; + requiresAuthentication = false; + break; + default: + throw new ArgumentOutOfRangeException(nameof(restEndpoint), restEndpoint, null); + } + + return host; + } + + #endregion + } +}