<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Flurl.Http;
using Newtonsoft.Json.Linq;

namespace ShopifySharp
{
    public abstract class ShopifyService
    {
        private static IRequestExecutionPolicy _ExecutionPolicy = new DefaultRequestExecutionPolicy();

        protected Uri _ShopUri { get; set; }

        protected string _AccessToken { get; set; }

        /// <summary>
        /// Creates a new instance of <see cref="ShopifyService" />.
        /// </summary>
        /// <param name="myShopifyUrl">The shop's *.myshopify.com URL.</param>
        /// <param name="shopAccessToken">An API access token for the shop.</param>
        protected ShopifyService(string myShopifyUrl, string shopAccessToken)
        {
            _ShopUri = BuildShopUri(myShopifyUrl);

            // TODO: If there's a global execution policy set, assign it here.
        }

        /// <summary>
        /// Attempts to build a shop API <see cref="Uri"/> for the given shop.
        /// </summary>
        /// <param name="myShopifyUrl">The shop's *.myshopify.com URL.</param>
        /// <exception cref="ShopifyException">Thrown if the given URL cannot be converted into a well-formed URI.</exception>
        /// <returns>The shop's API <see cref="Uri"/>.</returns>
        protected Uri BuildShopUri(string myShopifyUrl)
        {
            if (Uri.IsWellFormedUriString(myShopifyUrl, UriKind.Absolute) == false)
            {
                //Shopify typically returns the shop URL without a scheme. If the user is storing that as-is, the uri will not be well formed.
                //Try to fix that by adding a scheme and checking again.
                if (Uri.IsWellFormedUriString(Uri.UriSchemeHttps + Uri.SchemeDelimiter + myShopifyUrl, UriKind.Absolute) == false)
                {
                    throw new ShopifyException($"The given {nameof(myShopifyUrl)} cannot be converted into a well-formed URI.");
                }
                else
                {
                    myShopifyUrl = Uri.UriSchemeHttps + Uri.SchemeDelimiter + myShopifyUrl;
                }
            }

            UriBuilder builder = new UriBuilder(myShopifyUrl)
            {
                Path = "admin/",
                Scheme = Uri.UriSchemeHttps,
                Port = 443 //SSL port
            };

            return builder.Uri;
        }

        /// <summary>
        /// Sets the execution policy for this instance only.
        /// </summary>
        public static void SetExecutionPolicy(IRequestExecutionPolicy executionPolicy)
        {
            _ExecutionPolicy = executionPolicy;
        }

        /// <summary>
        /// Prepares a request to the path and appends the shop's access token header if applicable.    
        /// </summary>
        protected IFlurlClient PrepareRequest(string path)
        {
            var client = Flurl.Url.Combine(_ShopUri.ToString(), path).AllowAnyHttpStatus();

            if (! string.IsNullOrEmpty(_AccessToken))
            {
                client = client.WithHeader("X-Shopify-Access-Token", _AccessToken);
            }

            return client;
        }

        /// <summary>
        /// Executes a request and returns a JToken for querying. Throws an exception when the response is invalid. 
        /// Use this method when the expected response is a single line or simple object that doesn't warrant its own class.
        /// </summary>
        protected async Task<JToken> ExecuteRequestAsync(IFlurlClient baseRequest, HttpMethod method, HttpContent bodyContent = null)
        {
            return await _ExecutionPolicy.Run(baseRequest, bodyContent, async (request) =>
            {
                var response = await request.SendAsync(method, bodyContent);
                var rawResult = await request.GetStringAsync();

                //Check for and throw exception when necessary.
                CheckResponseExceptions(response, rawResult);

                var jtoken = JToken.Parse(rawResult);

                return new RequestResult<JToken>(response, jtoken, rawResult);
            });
        }

        /// <summary>
        /// Executes a request and returns the given type. Throws an exception when the response is invalid. 
        /// Use this method when the expected response is a single line or simple object that doesn't warrant its own class.
        /// </summary>
        protected async Task<T> ExecuteRequestAsync<T>(IFlurlClient baseRequest, HttpMethod method, HttpContent bodyContent = null, string rootElement = null) where T : new()
        {
            return await _ExecutionPolicy.Run<T>(baseRequest, bodyContent, async (request) =>
            {
                var response = await request.SendAsync(method, bodyContent);
                var rawResult = await request.GetStringAsync();
                
                //Check for and throw exception when necessary.
                CheckResponseExceptions(response, rawResult);

                var jtoken = JToken.Parse(rawResult);
                T result = jtoken.Value<T>(rootElement);
                
                return new RequestResult<T>(response, result, rawResult);
            });
        }

        /// <summary>
        /// Checks a response for exceptions or invalid status codes. Throws an exception when necessary.
        /// </summary>
        /// <param name="response">The response.</param>
        public static void CheckResponseExceptions(HttpResponseMessage response, string rawResponse)
        {
            if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
            {
                var errors = ParseErrorJson(rawResponse);
                var code = response.StatusCode;<commit_msg>Bugfix: pass httpcontent to execution policy.<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Flurl.Http;
using Newtonsoft.Json.Linq;

namespace ShopifySharp
{
    public abstract class ShopifyService
    {
        private static IRequestExecutionPolicy _ExecutionPolicy = new DefaultRequestExecutionPolicy();

        protected Uri _ShopUri { get; set; }

        protected string _AccessToken { get; set; }

        /// <summary>
        /// Creates a new instance of <see cref="ShopifyService" />.
        /// </summary>
        /// <param name="myShopifyUrl">The shop's *.myshopify.com URL.</param>
        /// <param name="shopAccessToken">An API access token for the shop.</param>
        protected ShopifyService(string myShopifyUrl, string shopAccessToken)
        {
            _ShopUri = BuildShopUri(myShopifyUrl);

            // TODO: If there's a global execution policy set, assign it here.
        }

        /// <summary>
        /// Attempts to build a shop API <see cref="Uri"/> for the given shop.
        /// </summary>
        /// <param name="myShopifyUrl">The shop's *.myshopify.com URL.</param>
        /// <exception cref="ShopifyException">Thrown if the given URL cannot be converted into a well-formed URI.</exception>
        /// <returns>The shop's API <see cref="Uri"/>.</returns>
        protected Uri BuildShopUri(string myShopifyUrl)
        {
            if (Uri.IsWellFormedUriString(myShopifyUrl, UriKind.Absolute) == false)
            {
                //Shopify typically returns the shop URL without a scheme. If the user is storing that as-is, the uri will not be well formed.
                //Try to fix that by adding a scheme and checking again.
                if (Uri.IsWellFormedUriString(Uri.UriSchemeHttps + Uri.SchemeDelimiter + myShopifyUrl, UriKind.Absolute) == false)
                {
                    throw new ShopifyException($"The given {nameof(myShopifyUrl)} cannot be converted into a well-formed URI.");
                }
                else
                {
                    myShopifyUrl = Uri.UriSchemeHttps + Uri.SchemeDelimiter + myShopifyUrl;
                }
            }

            UriBuilder builder = new UriBuilder(myShopifyUrl)
            {
                Path = "admin/",
                Scheme = Uri.UriSchemeHttps,
                Port = 443 //SSL port
            };

            return builder.Uri;
        }

        /// <summary>
        /// Sets the execution policy for this instance only.
        /// </summary>
        public static void SetExecutionPolicy(IRequestExecutionPolicy executionPolicy)
        {
            _ExecutionPolicy = executionPolicy;
        }

        /// <summary>
        /// Prepares a request to the path and appends the shop's access token header if applicable.    
        /// </summary>
        protected IFlurlClient PrepareRequest(string path)
        {
            var client = Flurl.Url.Combine(_ShopUri.ToString(), path).AllowAnyHttpStatus();

            if (! string.IsNullOrEmpty(_AccessToken))
            {
                client = client.WithHeader("X-Shopify-Access-Token", _AccessToken);
            }

            return client;
        }

        /// <summary>
        /// Executes a request and returns a JToken for querying. Throws an exception when the response is invalid. 
        /// Use this method when the expected response is a single line or simple object that doesn't warrant its own class.
        /// </summary>
        protected async Task<JToken> ExecuteRequestAsync(IFlurlClient baseRequest, HttpMethod method, HttpContent baseContent = null)
        {
            return await _ExecutionPolicy.Run(baseRequest, baseContent, async (request, content) =>
            {
                var response = await request.SendAsync(method, content);
                var rawResult = await request.GetStringAsync();

                //Check for and throw exception when necessary.
                CheckResponseExceptions(response, rawResult);

                var jtoken = JToken.Parse(rawResult);

                return new RequestResult<JToken>(response, jtoken, rawResult);
            });
        }

        /// <summary>
        /// Executes a request and returns the given type. Throws an exception when the response is invalid.
        /// Use this method when the expected response is a single line or simple object that doesn't warrant its own class.
        /// </summary>
        /// <param name="baseRequest">
        /// The request to be executed. Note that this request will be automatically disposed when the method returns.
        /// </param>
        /// <param name="method">
        /// HTTP method to be used by the request.
        /// </param>
        /// <param name="baseContent">
        /// Content that gets appended to the request body. Can be null. In most cases, you'll want to use <see cref="JsonContent"/>.
        /// </param>
        /// <remarks>
        /// This method will automatically dispose the <paramref name="baseRequest"/> when finished.
        /// </remarks>
        protected async Task<T> ExecuteRequestAsync<T>(IFlurlClient baseRequest, HttpMethod method, HttpContent baseContent = null, string rootElement = null) where T : new()
        {
            return await _ExecutionPolicy.Run<T>(baseRequest, baseContent, async (request, content) =>
            {
                var response = await request.SendAsync(method, content);
                var rawResult = await request.GetStringAsync();
                
                //Check for and throw exception when necessary.
                CheckResponseExceptions(response, rawResult);

                var jtoken = JToken.Parse(rawResult);
                T result = jtoken.Value<T>(rootElement);
                
                return new RequestResult<T>(response, result, rawResult);
            });
        }

        /// <summary>
        /// Checks a response for exceptions or invalid status codes. Throws an exception when necessary.
        /// </summary>
        /// <param name="response">The response.</param>
        public static void CheckResponseExceptions(HttpResponseMessage response, string rawResponse)
        {
            if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
            {
                var errors = ParseErrorJson(rawResponse);
                var code = response.StatusCode;
{'fraction_non_alphanumeric': 0.09306069876026404, 'fraction_numerical': 0.0004830140074062148, 'mean_word_length': 1.9712987323606792, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5816852', 'n_tokens_mistral': 3081, 'n_tokens_neox': 2951, 'n_words': 980}