public async Task <CompositeResult> PostAsync(ICompositeRequest request)
        {
            if (request == null || request.CompositeRequests.Count <= 0)
            {
                throw new ArgumentNullException(nameof(request));
            }

            try
            {
                var resourceName = $"{request.Prefix}composite";

                if (request.AllOrNone)
                {
                    var requests = request.CompositeRequests;

                    if (requests.Count > Dnf.CompositeLimit)
                    {
                        throw new ArgumentOutOfRangeException(nameof(request));
                    }
                    if (requests.Count(c => IsQuery(c.ResponseType)) > Dnf.CompositeQueryLimit)
                    {
                        throw new ArgumentOutOfRangeException(nameof(request));
                    }

                    var inputObject = new JObject
                    {
                        ["allOrNone"]        = true,
                        ["compositeRequest"] = JToken.FromObject(requests.Select(req => Dnf.Assign(JObject.FromObject(req), new JObject
                        {
                            ["url"] = DecodeReference($"/services/data/{ApiVersion}/{request.Prefix}{req.Url?.TrimStart('/')}")
                        })))
                    };

                    var result = await JsonHttp.HttpPostAsync <CompositeResultBody>(inputObject, resourceName)
                                 .ConfigureAwait(false);

                    var results = new CompositeResult(request.CompositeRequests, result?.CompositeResponse ?? new List <CompositeSubRequestResult>());
                    return(results);
                }
                else
                {
                    var throttler = new SemaphoreSlim(Dnf.DefaultConcurrentLimit, Dnf.DefaultConcurrentLimit);
                    var results   = new CompositeResult();

                    var chunks = new List <IList <CompositeSubRequest> >();
                    IList <CompositeSubRequest>?chunk = null;

                    foreach (var req in request.CompositeRequests)
                    {
                        var added = false;

                        if (IsQuery(req.ResponseType))
                        {
                            if (chunk != null && chunk.Count(c => IsQuery(c.ResponseType)) < Dnf.CompositeQueryLimit)
                            {
                                chunk.Add(req);
                                added = true;
                            }
                        }
                        else if (chunk?.Count < Dnf.CompositeLimit)
                        {
                            chunk.Add(req);
                            added = true;
                        }

                        if (added)
                        {
                            continue;
                        }
                        chunk = new List <CompositeSubRequest> {
                            req
                        };
                        chunks.Add(chunk);
                    }

                    var tasks = new List <Task>();

                    foreach (var requests in chunks)
                    {
                        await throttler.WaitAsync()
                        .ConfigureAwait(false);

                        tasks.Add(Task.Run(async() =>
                        {
                            try
                            {
                                var inputObject = new JObject
                                {
                                    ["compositeRequest"] = JToken.FromObject(requests.Select(req => Dnf.Assign(JObject.FromObject(req), new JObject
                                    {
                                        ["url"] = DecodeReference($"/services/data/{ApiVersion}/{request.Prefix}{req.Url?.TrimStart('/')}")
                                    })))
                                };

                                var result = await JsonHttp.HttpPostAsync <CompositeResultBody>(inputObject, resourceName)
                                             .ConfigureAwait(false);
                                results.Add(requests, result?.CompositeResponse ?? new List <CompositeSubRequestResult>());
                            }
                            catch (Exception ex)
                            {
                                var body = new JArray {
                                    ex.Message
                                };
                                var responses = requests.Select(req => new CompositeSubRequestResult
                                {
                                    Body           = body,
                                    ReferenceId    = req.ReferenceId,
                                    HttpStatusCode = 500
                                }).ToList();
                                results.Add(requests, responses);
                            }
                            finally
                            {
                                throttler.Release();
                            }
                        }));
                    }
                    await Task.WhenAll(tasks)
                    .ConfigureAwait(false);

                    return(results);
                }
            }
            catch (Exception ex)
            {
                var body = new JArray {
                    ex.Message
                };
                var responses = request.CompositeRequests.Select(req => new CompositeSubRequestResult
                {
                    Body           = body,
                    ReferenceId    = req.ReferenceId,
                    HttpStatusCode = 500
                }).ToList();
                var results = new CompositeResult(request.CompositeRequests, responses);
                return(results);
            }

            bool IsQuery(string responseType)
            {
                return(responseType == "query" || responseType == "collections");
            }
        }
        public async Task <BatchResult> BatchAsync(IBatchRequest request)
        {
            if (request == null || request.BatchRequests.Count <= 0)
            {
                throw new ArgumentNullException(nameof(request));
            }

            try
            {
                var resourceName = $"{request.Prefix}composite/batch";

                if (request.HaltOnError)
                {
                    if (request.BatchRequests.Count > Dnf.BatchLimit)
                    {
                        throw new ArgumentOutOfRangeException(nameof(request));
                    }

                    var inputObject = new JObject
                    {
                        ["batchRequests"] = JToken.FromObject(request.BatchRequests.Select(req => Dnf.Assign(JObject.FromObject(req), new JObject
                        {
                            ["url"] = DecodeReference($"/services/data/{ApiVersion}/{request.Prefix}{req.Url?.TrimStart('/')}")
                        }))),
                        ["haltOnError"] = true
                    };

                    var result = await JsonHttp.HttpPostAsync <BatchResultBody>(inputObject, resourceName)
                                 .ConfigureAwait(false);

                    var results = new BatchResult(request.BatchRequests, result?.Results ?? new List <BatchSubRequestResult>());
                    return(results);
                }
                else
                {
                    var throttler = new SemaphoreSlim(Dnf.DefaultConcurrentLimit, Dnf.DefaultConcurrentLimit);
                    var results   = new BatchResult();

                    var chunks = new List <IList <BatchSubRequest> >();
                    IList <BatchSubRequest>?chunk = null;

                    foreach (var req in request.BatchRequests)
                    {
                        var added = false;

                        if (chunk?.Count < Dnf.BatchLimit)
                        {
                            chunk.Add(req);
                            added = true;
                        }

                        if (added)
                        {
                            continue;
                        }
                        chunk = new List <BatchSubRequest> {
                            req
                        };
                        chunks.Add(chunk);
                    }

                    var tasks = new List <Task>();

                    foreach (var requests in chunks)
                    {
                        await throttler.WaitAsync()
                        .ConfigureAwait(false);

                        tasks.Add(Task.Run(async() =>
                        {
                            try
                            {
                                var inputObject = new JObject
                                {
                                    ["batchRequests"] = JToken.FromObject(requests.Select(req => Dnf.Assign(JObject.FromObject(req), new JObject
                                    {
                                        ["url"] = DecodeReference($"/services/data/{ApiVersion}/{request.Prefix}{req.Url?.TrimStart('/')}")
                                    })))
                                };


                                var result = await JsonHttp.HttpPostAsync <BatchResultBody>(inputObject, resourceName)
                                             .ConfigureAwait(false);
                                results.Add(requests, result?.Results ?? new List <BatchSubRequestResult>());
                            }
                            catch (Exception ex)
                            {
                                var body = new JArray {
                                    ex.Message
                                };
                                var responses = requests.Select(req => new BatchSubRequestResult
                                {
                                    Result     = body,
                                    StatusCode = 500
                                }).ToList();
                                results.Add(requests, responses);
                            }
                            finally
                            {
                                throttler.Release();
                            }
                        }));
                    }
                    await Task.WhenAll(tasks)
                    .ConfigureAwait(false);

                    return(results);
                }
            }
            catch (Exception ex)
            {
                var body = new JArray {
                    ex.Message
                };
                var responses = request.BatchRequests.Select(req => new BatchSubRequestResult
                {
                    Result     = body,
                    StatusCode = 500
                }).ToList();
                var results = new BatchResult(request.BatchRequests, responses);
                return(results);
            }
        }