public async Task <Validation> SubmitAsync(ValidationRequest request, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var restClient = _restClientFactory.Build();

            // Serialize the validation request to JSON

            var content = restClient
                          .Serialize(new
            {
                quality       = request.Quality?.NameOrGuid,
                deduplication = request.Deduplication?.NameOrGuid,
                priority      = request.Priority?.Value,
                name          = request.Name,
                // Strips the milliseconds portion from the specified retention period, if any
                retention = request.Retention == null
                        ? null
                        : new TimeSpan(request.Retention.Value.Days,
                                       request.Retention.Value.Hours,
                                       request.Retention.Value.Minutes,
                                       request.Retention.Value.Seconds)
                            .ToString(),
                callback = request.CompletionCallback == null
                        ? null
                        : new
                {
                    url = request.CompletionCallback.ToString()
                },

                // Non-file specific

                entries = request.Entries,
            });

            // Send the request to the Verifalia servers

            return(await SubmitAsync(restClient,
                                     contentFactory : _ => Task.FromResult <HttpContent>(new StringContent(content, Encoding.UTF8, WellKnownMimeContentTypes.ApplicationJson)),
                                     waitingStrategy,
                                     cancellationToken)
                   .ConfigureAwait(false));
        }
        private async Task <TResult> WaitForCompletionAsync <TResult>(ValidationOverview validationOverview, WaitingStrategy waitingStrategy, CancellationToken cancellationToken) where TResult : class
        {
            if (validationOverview == null)
            {
                throw new ArgumentNullException(nameof(validationOverview));
            }
            if (waitingStrategy == null)
            {
                throw new ArgumentNullException(nameof(waitingStrategy));
            }

            var resultOverview = validationOverview;

            do
            {
                // Fires a progress, since we are not yet completed

                waitingStrategy.Progress?.Report(resultOverview);

                // Wait for the next polling schedule

                await waitingStrategy
                .WaitForNextPoll(resultOverview, cancellationToken)
                .ConfigureAwait(false);

                // Fetch the job from the API

                TResult result;

                if (typeof(TResult) == typeof(Validation))
                {
                    var validationResult = await GetAsync(validationOverview.Id,
                                                          cancellationToken : cancellationToken)
                                           .ConfigureAwait(false);

                    result = (TResult)(object)validationResult;

                    if (result == null)
                    {
                        // A null result means the validation has been deleted (or is expired) between a poll and the next one

                        return(null);
                    }

                    resultOverview = validationResult.Overview;
                }
                else if (typeof(TResult) == typeof(ValidationOverview))
                {
                    resultOverview = await GetOverviewAsync(validationOverview.Id,
                                                            cancellationToken : cancellationToken)
                                     .ConfigureAwait(false);

                    // A null result means the validation has been deleted (or is expired) between a poll and the next one

                    if (resultOverview == null)
                    {
                        return(null);
                    }

                    result = (TResult)(object)resultOverview;
                }
                else
                {
                    throw new NotSupportedException("TResult must be either of type Validation or ValidationOverview.");
                }

                // Returns immediately if the validation has been completed

                if (resultOverview.Status == ValidationStatus.Completed)
                {
                    return(result);
                }
            } while (true);
        }
        public async Task <Validation> GetAsync(Guid id, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            // Sends the request to the Verifalia servers

            var restClient = _restClientFactory.Build();

            using (var response = await restClient
                                  .InvokeAsync(HttpMethod.Get,
                                               $"email-validations/{id:D}",
                                               headers: new Dictionary <string, object> {
                { "Accept", WellKnownMimeContentTypes.ApplicationJson }
            },
                                               cancellationToken: cancellationToken)
                                  .ConfigureAwait(false))
            {
                switch (response.StatusCode)
                {
                case HttpStatusCode.OK:
                case HttpStatusCode.Accepted:
                {
                    var partialValidation = await response
                                            .Content
                                            .DeserializeAsync <PartialValidation>(restClient)
                                            .ConfigureAwait(false);

                    // Returns immediately if the validation has been completed or if we should not wait for it

                    if (waitingStrategy == null || !waitingStrategy.WaitForCompletion || partialValidation.Overview.Status == ValidationStatus.Completed)
                    {
                        return(await RetrieveValidationFromPartialValidationAsync(partialValidation, cancellationToken)
                               .ConfigureAwait(false));
                    }

                    return(await WaitForCompletionAsync <Validation>(partialValidation.Overview, waitingStrategy,
                                                                     cancellationToken)
                           .ConfigureAwait(false));
                }

                case HttpStatusCode.Gone:
                case HttpStatusCode.NotFound:
                {
                    return(null);
                }

                default:
                {
                    // An unexpected HTTP status code has been received at this point

                    var responseBody = await response
                                       .Content
#if NET5_0_OR_GREATER
                                       .ReadAsStringAsync(cancellationToken)
#else
                                       .ReadAsStringAsync()
#endif
                                       .ConfigureAwait(false);

                    throw new VerifaliaException(
                              $"Unexpected HTTP response: {(int) response.StatusCode} {responseBody}");
                }
                }
            }
        }
 public Task <Validation> SubmitAsync(IEnumerable <ValidationRequestEntry> entries, QualityLevelName quality = default, DeduplicationMode deduplication = default, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
 {
     return(SubmitAsync(new ValidationRequest(entries, quality, deduplication),
                        waitingStrategy: waitingStrategy,
                        cancellationToken: cancellationToken));
 }
        public Task <Validation> SubmitAsync(IEnumerable <string> emailAddresses, QualityLevelName quality = default, DeduplicationMode deduplication = default, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            if (emailAddresses == null)
            {
                throw new ArgumentNullException(nameof(emailAddresses));
            }

            return(SubmitAsync(emailAddresses.Select(emailAddress => new ValidationRequestEntry(emailAddress)),
                               quality: quality,
                               deduplication: deduplication,
                               waitingStrategy: waitingStrategy,
                               cancellationToken: cancellationToken));
        }
 public Task <Validation> SubmitAsync(ValidationRequestEntry entry, QualityLevelName quality = default, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
 {
     return(SubmitAsync(new[] { entry },
                        quality: quality,
                        waitingStrategy: waitingStrategy,
                        cancellationToken: cancellationToken));
 }
        public Task <Validation> SubmitAsync(string emailAddress, QualityLevelName quality = default, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            if (emailAddress == null)
            {
                throw new ArgumentNullException(nameof(emailAddress));
            }

            return(SubmitAsync(new[] { emailAddress },
                               quality: quality,
                               waitingStrategy: waitingStrategy,
                               cancellationToken: cancellationToken));
        }
        private async Task <Validation> SubmitAsync(IRestClient restClient, Func <CancellationToken, Task <HttpContent> > contentFactory, WaitingStrategy waitingStrategy, CancellationToken cancellationToken)
        {
            using (var response = await restClient.InvokeAsync(HttpMethod.Post,
                                                               "email-validations",
                                                               queryParams: null,
                                                               headers: new Dictionary <string, object> {
                { "Accept", WellKnownMimeContentTypes.ApplicationJson }
            },
                                                               contentFactory: contentFactory,
                                                               cancellationToken: cancellationToken)
                                  .ConfigureAwait(false))
            {
                switch (response.StatusCode)
                {
                case HttpStatusCode.OK:
                case HttpStatusCode.Accepted:
                {
                    var partialValidation = await response
                                            .Content
                                            .DeserializeAsync <PartialValidation>(restClient)
                                            .ConfigureAwait(false);

                    // Returns immediately if the validation has been completed or if we should not wait for it

                    if (waitingStrategy == null || !waitingStrategy.WaitForCompletion || partialValidation.Overview.Status == ValidationStatus.Completed)
                    {
                        return(await RetrieveValidationFromPartialValidationAsync(partialValidation, cancellationToken)
                               .ConfigureAwait(false));
                    }

                    return(await WaitForCompletionAsync <Validation>(partialValidation.Overview, waitingStrategy,
                                                                     cancellationToken)
                           .ConfigureAwait(false));
                }

                default:
                {
                    // An unexpected HTTP status code has been received at this point

                    var responseBody = await response
                                       .Content
#if NET5_0_OR_GREATER
                                       .ReadAsStringAsync(cancellationToken)
#else
                                       .ReadAsStringAsync()
#endif
                                       .ConfigureAwait(false);

                    throw new VerifaliaException(
                              $"Unexpected HTTP response: {(int) response.StatusCode} {responseBody}");
                }
                }
            }
        }
        public async Task <Validation> SubmitAsync(FileValidationRequest request, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var restClient = _restClientFactory.Build();

            // Serialize the validation request (settings only) to JSON

            var settingsContent = restClient
                                  .Serialize(new
            {
                quality       = request.Quality?.NameOrGuid,
                deduplication = request.Deduplication?.NameOrGuid,
                priority      = request.Priority?.Value,
                name          = request.Name,
                // Strips the milliseconds portion from the specified retention period, if any
                retention = request.Retention == null
                        ? null
                        : new TimeSpan(request.Retention.Value.Days,
                                       request.Retention.Value.Hours,
                                       request.Retention.Value.Minutes,
                                       request.Retention.Value.Seconds)
                            .ToString(),
                callback = request.CompletionCallback == null
                        ? null
                        : new
                {
                    url = request.CompletionCallback.ToString()
                },

                // File-specific

                startingRow = request.StartingRow,
                endingRow   = request.EndingRow,
                column      = request.Column,
                sheet       = request.Sheet,
                lineEnding  = request.LineEnding,
                delimiter   = request.Delimiter,
            });

            // Send the request to the Verifalia servers

            using (var postedFileContent = new StreamContent(request.File))
                using (var postedSettingsContent = new StringContent(settingsContent, Encoding.UTF8, WellKnownMimeContentTypes.ApplicationJson))
                {
                    postedFileContent.Headers.ContentType     = request.ContentType;
                    postedSettingsContent.Headers.ContentType = new MediaTypeHeaderValue(WellKnownMimeContentTypes.ApplicationJson);

                    return(await SubmitAsync(restClient,
                                             contentFactory : _ =>
                    {
                        var postedContent = new MultipartFormDataContent();

                        postedContent.Add(postedFileContent, "inputFile",
                                          // HACK: Must send a filename, as the backend expects one
                                          // see https://github.com/dotnet/aspnetcore/blob/425c196cba530b161b120a57af8f1dd513b96f67/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs#L27
                                          "dummy");
                        postedContent.Add(postedSettingsContent, "settings");

                        return Task.FromResult <HttpContent>(postedContent);
                    },
                                             waitingStrategy,
                                             cancellationToken)
                           .ConfigureAwait(false));
                }
        }
        public async Task <Validation> SubmitAsync(Stream file, MediaTypeHeaderValue contentType, QualityLevelName quality = default, DeduplicationMode deduplication = default, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (contentType == null)
            {
                throw new ArgumentNullException(nameof(contentType));
            }

            using var request = new FileValidationRequest(file, contentType, quality, deduplication, leaveOpen: true);

            return(await SubmitAsync(request,
                                     waitingStrategy,
                                     cancellationToken)
                   .ConfigureAwait(false));
        }
        public async Task <Validation> SubmitAsync(byte[] file, MediaTypeHeaderValue contentType, QualityLevelName quality = default, DeduplicationMode deduplication = default, WaitingStrategy waitingStrategy = default, CancellationToken cancellationToken = default)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }

            using var stream = new MemoryStream(file);
            return(await SubmitAsync(stream,
                                     contentType,
                                     quality,
                                     deduplication,
                                     waitingStrategy,
                                     cancellationToken)
                   .ConfigureAwait(false));
        }