/// <summary>
        /// Updates
        /// </summary>
        /// <param name="sObjectTypeName">SObject name, e.g. "Account"</param>
        /// <param name="objectId">Id of Object to update</param>
        /// <param name="sObject">Object to update</param>
        /// <param name="customHeaders">Custom headers to include in request (Optional). await The HeaderFormatter helper class can be used to generate the custom header as needed.</param>
        /// <returns>void, API returns 204/NoContent</returns>
        /// <exception cref="ForceApiException">Thrown when update fails</exception>
        public async Task UpdateRecord <T>(string sObjectTypeName, string objectId, T sObject, Dictionary <string, string> customHeaders = null)
        {
            Dictionary <string, string> headers = new Dictionary <string, string>();

            //Add call options
            Dictionary <string, string> callOptions = HeaderFormatter.SforceCallOptions(_clientName);

            headers.AddRange(callOptions);

            //Add custom headers if specified
            if (customHeaders != null)
            {
                headers.AddRange(customHeaders);
            }

            var uri = UriFormatter.SObjectRows(_instanceUrl, _apiVersion, sObjectTypeName, objectId);

            using (var httpClient = new HttpClient())
            {
                JsonClient client = new JsonClient(_accessToken, httpClient);

                await client.HttpPatchAsync <object>(sObject, uri, headers);

                return;
            }
        }
        public void CallOptionsHeaderWithDefaultNamespaceParameter()
        {
            Dictionary <string, string> customHeaders = HeaderFormatter.SforceCallOptions(defaultNamespace: "Test");

            Assert.Single(customHeaders);

            var header = customHeaders.First();

            string result = string.Format("{0}: {1}", header.Key, header.Value);

            Assert.Equal("Sforce-Call-Options: client=ForceClient, defaultNamespace=Test", result);
        }
        /// <summary>
        /// Get SObject by ID
        /// </summary>
        /// <param name="sObjectTypeName">SObject name, e.g. "Account"</param>
        /// <param name="objectId">SObject ID</param>
        /// <param name="fields">(optional) List of fields to retrieve, if not supplied, all fields are retrieved.</param>
        public async Task <T> GetObjectById <T>(string sObjectTypeName, string objectId, List <string> fields = null)
        {
            Dictionary <string, string> headers = HeaderFormatter.SforceCallOptions(_clientName);
            var uri = UriFormatter.SObjectRows(_instanceUrl, _apiVersion, sObjectTypeName, objectId, fields);

            using (var httpClient = new HttpClient())
            {
                JsonClient client = new JsonClient(_accessToken, httpClient);

                return(await client.HttpGetAsync <T>(uri, headers));
            }
        }
        public void CallOptionsHeaderWithClientParameter()
        {
            Dictionary <string, string> customHeaders = HeaderFormatter.SforceCallOptions("Test");

            Assert.Equal(1, customHeaders.Count);

            var header = customHeaders.First();

            string result = string.Format("{0}: {1}", header.Key, header.Value);

            Assert.Equal("Sforce-Call-Options: client=Test", result);
        }
        /// <summary>
        /// Delete record
        /// </summary>
        /// <param name="sObjectTypeName">SObject name, e.g. "Account"</param>
        /// <param name="objectId">Id of Object to update</param>
        /// <returns>void, API returns 204/NoContent</returns>
        /// <exception cref="ForceApiException">Thrown when update fails</exception>
        public async Task DeleteRecord(string sObjectTypeName, string objectId)
        {
            Dictionary <string, string> headers = HeaderFormatter.SforceCallOptions(_clientName);
            var uri = UriFormatter.SObjectRows(_instanceUrl, _apiVersion, sObjectTypeName, objectId);

            using (var httpClient = new HttpClient())
            {
                JsonClient client = new JsonClient(_accessToken, httpClient);

                await client.HttpDeleteAsync <object>(uri, headers);

                return;
            }
        }
        /// <summary>
        /// Retrieve (basic) metadata for an object.
        /// <para>Use the SObject Basic Information resource to retrieve metadata for an object.</para>
        /// </summary>
        /// <param name="objectTypeName">SObject name, e.g. Account</param>
        /// <returns>Returns SObjectMetadataBasic with basic object metadata and a list of recently created items.</returns>
        public async Task <SObjectBasicInfo> GetObjectBasicInfo(string objectTypeName)
        {
            try
            {
                Dictionary <string, string> headers = HeaderFormatter.SforceCallOptions(_clientName);

                //need to get the token from user_authentication
                using (var httpClient = new HttpClient())
                {
                    var client = new JsonClient(_accessToken, httpClient);
                    return(await client.HttpGetAsync <SObjectBasicInfo>(_uri, headers));
                }
            }
            catch (Exception ex)
            {
                return(null);
            }
        }
        /// <summary>
        /// Get a basic SOQL COUNT() query result
        /// <para>The query must start with SELECT COUNT() FROM, with no named field in the count clause. COUNT() must be the only element in the SELECT list.</para>
        /// </summary>
        /// <param name="queryString">SOQL query string starting with SELECT COUNT() FROM</param>
        /// <param name="queryAll">True if deleted records are to be included</param>
        /// <returns>The <see cref="Task{Int}"/> returning the count</returns>
        public async Task <int> CountQuery(string queryString, bool queryAll = false)
        {
            // https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_count.htm
            // COUNT() must be the only element in the SELECT list.

            Dictionary <string, string> headers = HeaderFormatter.SforceCallOptions(_clientName);

            if (!queryString.Replace(" ", "").ToLower().StartsWith("selectcount()from"))
            {
                throw new ForceApiException("CountQueryAsync may only be used with a query starting with SELECT COUNT() FROM");
            }
            using (var httpClient = new HttpClient())
            {
                var jsonClient = new JsonClient(_accessToken, httpClient);
                var uri        = UriFormatter.Query(_instanceUrl, _apiVersion, queryString);
                var qr         = await jsonClient.HttpGetAsync <QueryResult <object> >(uri, headers);

                return(qr.TotalSize);
            }
        }
        //soql
        /// <summary>
        /// Retrieve records using a SOQL query.
        /// <para>Will automatically retrieve the complete result set if split into batches. If you want to limit results, use the LIMIT operator in your query.</para>
        /// </summary>
        /// <param name="queryString">SOQL query string, without any URL escaping/encoding</param>
        /// <param name="queryAll">True if deleted records are to be included</param>
        /// <returns>List{T} of results</returns>
        public async Task <List <T> > Query <T>(string queryString, bool queryAll = false)
        {
#if DEBUG
            Stopwatch sw = new Stopwatch();
            sw.Start();
#endif
            try
            {
                Dictionary <string, string> headers = HeaderFormatter.SforceCallOptions(_clientName);
                var queryUri = UriFormatter.Query(_instanceUrl, _apiVersion, queryString, queryAll);

                using (var httpClient = new HttpClient())
                {
                    JsonClient client = new JsonClient(_accessToken, httpClient);

                    List <T> results = new List <T>();

                    bool   done           = false;
                    string nextRecordsUrl = string.Empty;

                    //larger result sets will be split into batches (sized according to system and account settings)
                    //if additional batches are indicated retrieve the rest and append to the result set.
                    do
                    {
                        if (!string.IsNullOrEmpty(nextRecordsUrl))
                        {
                            queryUri = new Uri(new Uri(_instanceUrl), nextRecordsUrl);
                        }

                        QueryResult <T> qr = await client.HttpGetAsync <QueryResult <T> >(queryUri, headers);

#if DEBUG
                        Debug.WriteLine(string.Format("Got query resuts, {0} totalSize, {1} in this batch, final batch: {2}",
                                                      qr.TotalSize, qr.Records.Count.ToString(), qr.Done.ToString()));
#endif

                        results.AddRange(qr.Records);

                        done = qr.Done;

                        nextRecordsUrl = qr.NextRecordsUrl;

                        if (!done && string.IsNullOrEmpty(nextRecordsUrl))
                        {
                            //Normally if query has remaining batches, NextRecordsUrl will have a value, and Done will be false.
                            //In case of some unforseen error, flag the result as done if we're missing the NextRecordsUrl
                            //In this situation we'll just get the previous set again and be stuck in a loop.
                            done = true;
                        }
                    } while (!done);

#if DEBUG
                    sw.Stop();
                    Debug.WriteLine(string.Format("Query results retrieved in {0}ms", sw.ElapsedMilliseconds.ToString()));
#endif

                    return(results);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error querying: " + ex.Message);
                throw ex;
            }
        }