/* * Copyright (c) 2024 Vaughn Nugent * * Package: CMNext.Cli * File: Program.cs * * CMNext.Cli is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 2 of the License, * or (at your option) any later version. * * CMNext.Cli is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CMNext.Cli. If not, see http://www.gnu.org/licenses/. */ using RestSharp; using System; using System.Net; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Text.Json; using System.Text.Json.Serialization; using VNLib.Net.Rest.Client; using VNLib.Net.Rest.Client.Construction; using FluentValidation; using FluentValidation.Results; using CMNext.Cli.Exceptions; using CMNext.Cli.Settings; namespace CMNext.Cli.Site { public sealed class CMNextSiteAdapter : RestSiteAdapterBase { protected override RestClientPool Pool { get; } public CMNextSiteAdapter(AppConfig config) { Uri baseUri = new(config.BaseAddress); RestClientOptions options = new(baseUri) { RemoteCertificateValidationCallback = (_, _, _, err) => true, AutomaticDecompression = DecompressionMethods.All, Encoding = System.Text.Encoding.UTF8, ThrowOnAnyError = false, ThrowOnDeserializationError = true, FollowRedirects = false, UserAgent = "vnuge/cmnext-cli", }; Pool = new RestClientPool(2, options); } /// public override Task WaitAsync(CancellationToken cancellation = default) => Task.CompletedTask; /// public override void OnResponse(RestResponse response) { //always see if a json web-message error was returned ParseErrorAndThrow(response); switch (response.StatusCode) { case HttpStatusCode.InternalServerError: throw new CMNextApiException("The server encountered an internal error"); case HttpStatusCode.NotFound: throw new EntityNotFoundException("The requested entity was not found"); case HttpStatusCode.Forbidden: throw new CMNextPermissionException("You do not have the required permissions to perform this action. Access Denied"); case HttpStatusCode.Unauthorized: throw new CMNextPermissionException("Your credentials are invalid or expired. Access Denied"); case HttpStatusCode.Conflict: throw new CMNextApiException("The requested action could not be completed due to a conflict"); default: response.ThrowIfError(); break; } } private static void ParseErrorAndThrow(RestResponse response) { if (response.RawBytes == null || response.ContentType != "application/json") { return; } using JsonDocument doc = JsonDocument.Parse(response.RawBytes); //Webmessage must be an object if(doc.RootElement.ValueKind != JsonValueKind.Object) { return; } //Check for validation errors and raise them if(doc.RootElement.TryGetProperty("errors", out JsonElement errors)) { //Desserilize the errors into a validation failure ValidationFailure[] err = errors.EnumerateArray() .Select(e => e.Deserialize()!) .Select(e => new ValidationFailure(e.PropertyName, e.ErrorMessage)) .ToArray(); //Raise a fluent validation exception from the server results throw new ValidationException(err); } //Get result now, we don't know it's type yet _ = doc.RootElement.TryGetProperty("result", out JsonElement result); if (doc.RootElement.TryGetProperty("success", out JsonElement success)) { //If the request was not successful, throw an exception, a result will be a string if (!success.GetBoolean()) { throw new CMNextException(result.GetString()!); } } } internal record ServerValidationJson( [property: JsonPropertyName("property")] string? PropertyName, [property: JsonPropertyName("message")] string? ErrorMessage ); } }