From 6bcbfd8c6a06c3d478bae77fc3dd6e08e1c4956b Mon Sep 17 00:00:00 2001 From: Kingcean Date: Tue, 7 Jan 2025 22:22:15 +0800 Subject: [PATCH] Add Type Script definition content generation of JSON schema. --- Core/Collection/List/StringsConverter.cs | 3 - Core/CommandLine/Console/StyleConsole.cs | 2 + Core/Data/Reader/ColumnMapping.cs | 3 + Core/Data/Result/Change.cs | 24 ++ Core/Data/Result/CollectionResult.cs | 17 +- Core/IO/Stream/CharsReader.cs | 2 + Core/Net/Http/HttpClientExtensions.cs | 290 +++++++++++++++++- Core/Net/Http/JsonHttpClient.cs | 198 ++++++------ Core/Net/Http/ServerSentEvent.cs | 15 + Core/Net/Uri/QueryData.cs | 2 + Core/Reflection/Lifecycle/ExceptionHandler.cs | 2 +- Core/Reflection/Lifecycle/Initialization.cs | 2 + .../Lifecycle/ObservableProperties.cs | 76 +++++ Core/Reflection/Objects/FactorySet.cs | 2 + Core/Reflection/Objects/ObjectResolver.cs | 2 +- Core/Reflection/Objects/Singleton.cs | 2 +- Core/Reflection/Objects/VersionComparer.cs | 2 + .../Cryptography/SignatureProvider.cs | 4 + Core/Security/Token/SecretExchange.cs | 2 + Core/Security/Token/TokenRequestRoute.cs | 2 + Core/Tasks/Interceptor/Interceptor.cs | 2 + Core/Tasks/Interceptor/InterceptorPolicy.cs | 1 + Core/Tasks/Observable/Progress.cs | 6 +- Core/Tasks/Observable/StateEventArgs.cs | 21 ++ Core/Tasks/Retry/RetryExtensions.cs | 2 +- Core/Text/JsonNode/Array.cs | 1 + Core/Text/JsonNode/Boolean.cs | 2 + Core/Text/JsonNode/Number.cs | 4 + Core/Text/JsonNode/Object.cs | 1 + Core/Text/JsonNode/String.cs | 2 + Core/Text/JsonSchema/ObjectNode.cs | 150 ++++++++- Core/Text/JsonSchema/StringNode.cs | 2 +- Core/Text/Table/CsvParser.cs | 2 + Core/Text/Table/TsvParser.cs | 2 + UnitTest/Net/HttpClientVerb.cs | 10 +- UnitTest/Text/JsonUnitTest.cs | 34 ++ 36 files changed, 761 insertions(+), 133 deletions(-) diff --git a/Core/Collection/List/StringsConverter.cs b/Core/Collection/List/StringsConverter.cs index 649372d5..19c3a5d5 100644 --- a/Core/Collection/List/StringsConverter.cs +++ b/Core/Collection/List/StringsConverter.cs @@ -69,7 +69,6 @@ public static int WriteTo(this IEnumerable col, Stream stre if (item == null) continue; if (i > 0) writer.Write('\n'); writer.Write(item.ToResponseString(true)); - writer.Write('\n'); writer.Flush(); i++; } @@ -96,7 +95,6 @@ public static async Task WriteToAsync(this IEnumerable if (item == null) continue; if (i > 0) writer.Write('\n'); await writer.WriteAsync(item.ToResponseString(true)); - writer.Write('\n'); await writer.FlushAsync(); i++; } @@ -123,7 +121,6 @@ public static async Task WriteToAsync(this IAsyncEnumerable 0) writer.Write('\n'); await writer.WriteAsync(item.ToResponseString(true)); - writer.Write('\n'); await writer.FlushAsync(); i++; } diff --git a/Core/CommandLine/Console/StyleConsole.cs b/Core/CommandLine/Console/StyleConsole.cs index 22636bc3..383c82aa 100644 --- a/Core/CommandLine/Console/StyleConsole.cs +++ b/Core/CommandLine/Console/StyleConsole.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading.Tasks; @@ -17,6 +18,7 @@ namespace Trivial.CommandLine; /// /// The command line interface. /// +[Guid("86E515F9-00B4-465A-ACB5-C473B7B678D4")] public sealed partial class StyleConsole { /// diff --git a/Core/Data/Reader/ColumnMapping.cs b/Core/Data/Reader/ColumnMapping.cs index 2b7194a7..a7300a98 100644 --- a/Core/Data/Reader/ColumnMapping.cs +++ b/Core/Data/Reader/ColumnMapping.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; using Trivial.Text; @@ -9,6 +10,7 @@ namespace Trivial.Data; /// /// Column mapping. /// +[Guid("A11ACE29-8AD8-4A59-9C88-52053CDEF354")] public class ColumnMapping : List { /// @@ -73,6 +75,7 @@ public static ColumnMapping Load(Type type) /// /// Column mapping item. /// +[Guid("A6C96FD4-87BA-4076-8FC9-D37B859F9EFB")] public class ColumnMappingItem { /// diff --git a/Core/Data/Result/Change.cs b/Core/Data/Result/Change.cs index 7f2036af..0c4daf5e 100644 --- a/Core/Data/Result/Change.cs +++ b/Core/Data/Result/Change.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Text.Json; @@ -334,6 +335,29 @@ public DataEventArgs(T data, string message) : this(data) /// Gets the additional message. /// public string Message { get; } + + /// + public override string ToString() + { + try + { + return Data?.ToString() ?? Message ?? "null"; + } + catch (InvalidOperationException) + { + } + catch (NotSupportedException) + { + } + catch (NotImplementedException) + { + } + catch (ExternalException) + { + } + + return base.ToString(); + } } /// diff --git a/Core/Data/Result/CollectionResult.cs b/Core/Data/Result/CollectionResult.cs index c25dcad7..33820a2c 100644 --- a/Core/Data/Result/CollectionResult.cs +++ b/Core/Data/Result/CollectionResult.cs @@ -565,6 +565,12 @@ internal JsonObjectNode ToJson() /// The Server-Sent Events collection data result. /// /// The type of item. +/// +/// +/// var http = new JsonHttpClient<StreamingCollectionResult<JsonObjectNode>>() +/// var sse = await http.GetAsync(A-URL-TO-STREAM-MESSAGE); +/// +/// public class StreamingCollectionResult : CollectionResult { private Task task; @@ -613,12 +619,21 @@ public StreamingCollectionResult(IAsyncEnumerable collectio { } + /// + /// Initializes a new instance of the CollectionResultSource class. + /// + /// The Server-Sent Events stream. + public StreamingCollectionResult(Stream sse) + : this(ServerSentEventInfo.ParseAsync(sse), null as Action, ServerSentEventInfo>, null) + { + } + /// /// Initializes a new instance of the CollectionResultSource class. /// /// The Server-Sent Events stream. /// The JSON serializer options. - public StreamingCollectionResult(Stream sse, JsonSerializerOptions options = null) + public StreamingCollectionResult(Stream sse, JsonSerializerOptions options) : this(ServerSentEventInfo.ParseAsync(sse), null as Action, ServerSentEventInfo>, options) { } diff --git a/Core/IO/Stream/CharsReader.cs b/Core/IO/Stream/CharsReader.cs index 711a0804..3f2e74f2 100644 --- a/Core/IO/Stream/CharsReader.cs +++ b/Core/IO/Stream/CharsReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -12,6 +13,7 @@ namespace Trivial.IO; /// /// The characters reader. /// +[Guid("B8BB09CE-3ADC-4E56-AF99-D7DF82FAA419")] public class CharsReader : TextReader { private readonly IEnumerator enumerator; diff --git a/Core/Net/Http/HttpClientExtensions.cs b/Core/Net/Http/HttpClientExtensions.cs index 00188563..bdf3f8d1 100644 --- a/Core/Net/Http/HttpClientExtensions.cs +++ b/Core/Net/Http/HttpClientExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -38,6 +39,29 @@ public static class HttpClientExtensions public static IObjectResolver CreateResolver(HttpClient value) => new InstanceObjectRef(value); + /// + /// Creates an object resolver for HTTP client. + /// + /// The HTTP client created. + /// The object resolver of HTTP client. + public static IObjectResolver CreateResolver(out HttpClient client) + { + client = new(); + return new InstanceObjectRef(client); + } + + /// + /// Creates an object resolver for HTTP client. + /// + /// A handler occurred on the HTTP client instance is initialized. + /// The object resolver of HTTP client. + public static IObjectResolver CreateResolver(Action onInit) + { + var http = new HttpClient(); + onInit(http); + return new InstanceObjectRef(http); + } + /// /// Creates an object resolver for HTTP client. /// @@ -55,6 +79,47 @@ public static IObjectResolver CreateResolver(HttpClientHandler value public static IObjectResolver CreateResolver(HttpClientHandler value, bool disposeHandler) => new InstanceObjectRef(new(value, disposeHandler)); + /// + /// Creates an object resolver for HTTP client. + /// + /// The handler of HTTP client instance responsible for processing the HTTP response. + /// The HTTP client created. + /// The object resolver of HTTP client. + public static IObjectResolver CreateResolver(HttpClientHandler value, out HttpClient client) + { + HttpClient http = value is null ? new() : new(value); + client = http; + return new InstanceObjectRef(http); + } + + /// + /// Creates an object resolver for HTTP client. + /// + /// The handler of HTTP client instance responsible for processing the HTTP response. + /// true if the inner handler should be disposed when the HTTP client is disposed; otherwise, false, to reuse the inner handler. + /// The HTTP client created. + /// The object resolver of HTTP client. + public static IObjectResolver CreateResolver(HttpClientHandler value, bool disposeHandler, out HttpClient client) + { + HttpClient http = new(value, disposeHandler); + client = http; + return new InstanceObjectRef(http); + } + + /// + /// Creates an object resolver for HTTP client. + /// + /// The handler of HTTP client instance responsible for processing the HTTP response. + /// true if the inner handler should be disposed when the HTTP client is disposed; otherwise, false, to reuse the inner handler. + /// A handler occurred on the HTTP client instance is initialized. + /// The object resolver of HTTP client. + public static IObjectResolver CreateResolver(HttpClientHandler value, bool disposeHandler, Action onInit) + { + HttpClient http = new(value, disposeHandler); + onInit?.Invoke(http); + return new InstanceObjectRef(http); + } + /// /// Creates an object resolver for HTTP client. /// @@ -107,7 +172,7 @@ public static IObjectResolver CreateResolver(HttpClientHandler value /// The task object representing the asynchronous operation. /// The argument is null. public static Task CopyToAsync(this HttpContent httpContent, Stream destination, IProgress progress = null, CancellationToken cancellationToken = default) - => CopyToAsync(httpContent, destination, IO.StreamCopy.DefaultBufferSize, progress, cancellationToken); + => CopyToAsync(httpContent, destination, StreamCopy.DefaultBufferSize, progress, cancellationToken); /// /// Loads the HTTP content into a stream of bytes and copies it to the stream object provided as the stream parameter. @@ -128,7 +193,7 @@ public static async Task CopyToAsync(this HttpContent httpContent, Stream destin #else var downloadingStream = await httpContent.ReadAsStreamAsync(); #endif - await IO.StreamCopy.CopyToAsync(downloadingStream, destination, bufferSize, progress, cancellationToken); + await StreamCopy.CopyToAsync(downloadingStream, destination, bufferSize, progress, cancellationToken); } /// @@ -146,7 +211,7 @@ public static async Task CopyToAsync(this HttpContent httpContent, Stream destin /// The specified path is invalid, such as being on an unmapped drive. /// The path of the file refers to a non-file device, such as "con:", "com1:", "lpt1:". public static Task WriteFileAsync(this HttpContent httpContent, string fileName, IProgress progress = null, CancellationToken cancellationToken = default) - => WriteFileAsync(httpContent, fileName, IO.StreamCopy.DefaultBufferSize, progress, cancellationToken); + => WriteFileAsync(httpContent, fileName, StreamCopy.DefaultBufferSize, progress, cancellationToken); /// /// Loads the HTTP content into a stream of bytes and copies it to the file. @@ -220,7 +285,7 @@ public static async Task WriteFileAsync(Uri uri, string fileName, IPro /// The specified path is invalid, such as being on an unmapped drive. /// The path of the file refers to a non-file device, such as "con:", "com1:", "lpt1:". public static Task TryWriteFileAsync(HttpContent httpContent, string fileName, IProgress progress = null, CancellationToken cancellationToken = default) - => TryWriteFileAsync(httpContent, fileName, IO.StreamCopy.DefaultBufferSize, progress, cancellationToken); + => TryWriteFileAsync(httpContent, fileName, StreamCopy.DefaultBufferSize, progress, cancellationToken); /// /// Loads the HTTP content into a stream of bytes and copies it to the file. @@ -344,12 +409,38 @@ public static async Task DeserializeJsonAsync(this HttpContent httpContent if (type == typeof(JsonArrayNode)) return (T)(object)await JsonArrayNode.ParseAsync(stream, default, cancellationToken); if (type == typeof(JsonDocument)) return (T)(object)await JsonDocument.ParseAsync(stream, default, cancellationToken); if (type == typeof(System.Text.Json.Nodes.JsonNode) || type.IsSubclassOf(typeof(System.Text.Json.Nodes.JsonNode))) return (T)(object)System.Text.Json.Nodes.JsonNode.Parse(stream); - if (type == typeof(IEnumerable)) return (T)(object)ServerSentEventInfo.Parse(stream); - if (type == typeof(IAsyncEnumerable)) return (T)(object)ServerSentEventInfo.ParseAsync(stream); - if (type == typeof(IEnumerable)) return (T)(object)ToJsonObjectNodes(httpContent.Headers.ContentType?.MediaType, stream); - if (type == typeof(IAsyncEnumerable)) return (T)(object)ToJsonObjectNodesAsync(httpContent.Headers.ContentType?.MediaType, stream); + if (type.IsGenericType) + { + var generic = type.GetGenericTypeDefinition(); + if (generic == typeof(IAsyncEnumerable<>)) + { + if (type == typeof(IAsyncEnumerable)) return (T)(object)ServerSentEventInfo.ParseAsync(stream); + if (type == typeof(IAsyncEnumerable)) return (T)(object)ToJsonObjectNodesAsync(httpContent.Headers.ContentType?.MediaType, stream); + if (type == typeof(IAsyncEnumerable)) return (T)(object)CharsReader.ReadLinesAsync(stream, Encoding.UTF8); + } + else if (generic == typeof(IEnumerable<>)) + { + if (type == typeof(IEnumerable)) return (T)(object)ServerSentEventInfo.Parse(stream); + if (type == typeof(IEnumerable)) return (T)(object)ToJsonObjectNodes(httpContent.Headers.ContentType?.MediaType, stream); + if (type == typeof(IEnumerable)) return (T)(object)CharsReader.ReadLines(stream, Encoding.UTF8); + } + else if (generic == typeof(StreamingCollectionResult<>)) + { + var genericType = type.GetGenericArguments().FirstOrDefault(); + if (genericType == null) return await JsonSerializer.DeserializeAsync(stream, default(JsonSerializerOptions), cancellationToken); + if (httpContent.Headers?.ContentType?.MediaType == WebFormat.ServerSentEventsMIME) + return (T)Activator.CreateInstance(type, new object[] { stream }); + var baseType = typeof(CollectionResult<>).MakeGenericType(new[] { genericType }); + var copy = await JsonSerializer.DeserializeAsync(stream, baseType, default(JsonSerializerOptions), cancellationToken); + if (copy is null) return default; + var con = type.GetConstructor(new[] { baseType }); + return (T)con.Invoke(new object[] { copy }); + } + } + if (type == typeof(QueryData)) return (T)(object)QueryData.ParseAsync(stream); if (type == typeof(Stream)) return (T)(object)stream; + if (type == typeof(HttpContent)) return (T)(object)httpContent; return await JsonSerializer.DeserializeAsync(stream, default(JsonSerializerOptions), cancellationToken); } @@ -382,7 +473,7 @@ public static async Task DeserializeJsonAsync(this HttpContent httpContent #else var stream = await httpContent.ReadAsStreamAsync(); #endif - IO.StreamCopy.TrySeek(stream, origin); + StreamCopy.TrySeek(stream, origin); return await JsonSerializer.DeserializeAsync(stream, options, cancellationToken); } @@ -918,6 +1009,187 @@ public static void Add(this MultipartFormDataContent content, IDictionary + /// Sets the preferred color scheme to HTTP request headers. + /// + /// The headers to fill the property. + /// The preferred color scheme to add, e.g. light and dark. + public static void SetTheme(HttpRequestHeaders headers, string value) + { + if (headers == null) return; + headers.Remove("Sec-CH-Prefers-Color-Scheme"); + if (!string.IsNullOrWhiteSpace(value)) headers.Add("Sec-CH-Prefers-Color-Scheme", JsonStringNode.ToJson(value)); + } + + /// + /// Sets the preferred color scheme as light to HTTP request headers. + /// + /// The headers to fill the property. + public static void SetLightTheme(HttpRequestHeaders headers) + => SetTheme(headers, "light"); + + /// + /// Sets the preferred color scheme as dark to HTTP request headers. + /// + /// The headers to fill the property. + public static void SetDarkTheme(HttpRequestHeaders headers) + => SetTheme(headers, "dark"); + + /// + /// Sets the OS platform name to HTTP request headers. + /// + /// The headers to fill the property. + /// true if also append its version and CPU arch; otherwise, false. + /// The platform name. + public static string SetPlatform(HttpRequestHeaders headers, bool fullInfo = false) + { + var client = new HttpClient(); + var name = GetPlatformName(); + if (string.IsNullOrWhiteSpace(name) || name == "Unknown" || headers == null) return name; + headers.Add("Sec-CH-UA-Platform", string.Concat('"', name, '"')); + if (!fullInfo) return name; + try + { + headers.Remove("Sec-CH-UA-Platform-Version"); + headers.Add("Sec-CH-UA-Platform-Version", string.Concat('"', Environment.OSVersion.Version, '"')); + } + catch (InvalidOperationException) + { + } + catch (NullReferenceException) + { + } + catch (NotSupportedException) + { + } + catch (NotImplementedException) + { + } + catch (ArgumentException) + { + } + catch (ExternalException) + { + } + +#if NET48_OR_GREATER || NETCOREAPP + try + { + var bitness = Environment.Is64BitProcess ? "64" : "32"; + var arch = RuntimeInformation.ProcessArchitecture.ToString(); + headers.Remove("Sec-CH-UA-Bitness"); + headers.Add("Sec-CH-UA-Bitness", bitness); + headers.Remove("Sec-CH-UA-Arch"); + headers.Add("Sec-CH-UA-Arch", string.Concat('"', arch, '"')); + } + catch (InvalidOperationException) + { + } + catch (NullReferenceException) + { + } + catch (NotSupportedException) + { + } + catch (NotImplementedException) + { + } + catch (ArgumentException) + { + } + catch (ExternalException) + { + } +#endif + + return name; + } + + internal static HttpRequestMessage CreateRequestMessage(HttpMethod method, Uri requestUri) + { + var m = new HttpRequestMessage(method, requestUri); + m.Headers.Date = DateTimeOffset.Now; + return m; + } + + internal static HttpRequestMessage CreateRequestMessage(HttpMethod method, string requestUri) + { + var m = new HttpRequestMessage(method, requestUri); + m.Headers.Date = DateTimeOffset.Now; + return m; + } + + internal static HttpRequestMessage CreateRequestMessage(HttpMethod method, Uri requestUri, HttpContent content) + { + var m = new HttpRequestMessage(method, requestUri) + { + Content = content + }; + m.Headers.Date = DateTimeOffset.Now; + return m; + } + + internal static HttpRequestMessage CreateRequestMessage(HttpMethod method, string requestUri, HttpContent content) + { + var m = new HttpRequestMessage(method, requestUri) + { + Content = content + }; + m.Headers.Date = DateTimeOffset.Now; + return m; + } + + /// + /// Gets the operating system name of the host. + /// + /// The OS name used in HTTP request header Sec-CH-UA-Platform. + private static string GetPlatformName() + { + try + { +#if NETFRAMEWORK + return Environment.OSVersion.Platform switch + { + PlatformID.Win32NT or PlatformID.Win32Windows or PlatformID.Win32S => "Windows", + PlatformID.MacOSX => "macOS", + PlatformID.WinCE => "Windows CE", + PlatformID.Xbox => "Xbox", + _ => "Unknown" + }; +#else + if (OperatingSystem.IsWindows()) return "Windows"; + if (OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst()) return "macOS"; + if (OperatingSystem.IsIOS()) return "iOS"; + if (OperatingSystem.IsAndroid()) return "Android"; + if (OperatingSystem.IsTvOS()) return "tvOS"; + if (OperatingSystem.IsWatchOS()) return "watchOS"; + if (OperatingSystem.IsLinux()) return "Linux"; + + //if (OperatingSystem.IsBrowser()) return "Chromium OS"; // Need read its host. +#endif + } + catch (InvalidOperationException) + { + } + catch (NullReferenceException) + { + } + catch (NotSupportedException) + { + } + catch (NotImplementedException) + { + } + catch (ArgumentException) + { + } + catch (ExternalException) + { + } + + return "Unknown"; + } + private static IEnumerable ToJsonObjectNodes(string mime, Stream stream) { if (mime == JsonValues.JsonlMIME) diff --git a/Core/Net/Http/JsonHttpClient.cs b/Core/Net/Http/JsonHttpClient.cs index 2a349c4f..bdef4bda 100644 --- a/Core/Net/Http/JsonHttpClient.cs +++ b/Core/Net/Http/JsonHttpClient.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using Trivial.Reflection; +using Trivial.Tasks; using Trivial.Web; namespace Trivial.Net; @@ -42,21 +43,14 @@ public interface IJsonHttpClientMaker /// /// The event arguments on sending. /// -public class SendingEventArgs : EventArgs +/// The HTTP request message. +public class SendingEventArgs(HttpRequestMessage requestMessage) : EventArgs { - /// - /// Initializes a new instance of the SendingEventArgs class. - /// - /// The HTTP request message. - public SendingEventArgs(HttpRequestMessage requestMessage) - { - RequestMessage = requestMessage; - } /// /// Gets the HTTP request message. /// - public HttpRequestMessage RequestMessage { get; } + public HttpRequestMessage RequestMessage { get; } = requestMessage; /// /// Gets the request URI. @@ -226,9 +220,70 @@ public ReceivedEventArgs ConvertTo() /// var http3 = new JsonHttpClient<SerializableModel>() /// var model = await http3.GetAsync(A-URL-TO-GET-JSON); /// +/// +/// var http4 = new JsonHttpClient<IAsyncEnumerable<JsonObjectNode>>() +/// var sse = await http4.GetAsync(A-URL-TO-GET-JSONL); +/// /// public class JsonHttpClient { + /// + /// Initializes a new instance of the JsonHttpClient class. + /// + public JsonHttpClient() + { + var d = WebFormat.GetJsonDeserializer(true); + if (d != null) Deserializer = d; + } + + /// + /// Initializes a new instance of the JsonHttpClient class. + /// + /// The HTTP client resolver. + /// true if still need serialize the result even if it fails to send request; otherwise, false. + /// The retry policy. + public JsonHttpClient(IObjectResolver httpClientResolver, bool serializeEvenIfFailed = false, IRetryPolicy retryPolicy = null) + : this() + { + HttpClientResolver = httpClientResolver; + SerializeEvenIfFailed = serializeEvenIfFailed; + RetryPolicy = retryPolicy; + } + + /// + /// Initializes a new instance of the JsonHttpClient class. + /// + /// The HTTP client to re-use. + /// true if still need serialize the result even if it fails to send request; otherwise, false. + /// The retry policy. + public JsonHttpClient(HttpClient httpClient, bool serializeEvenIfFailed = false, IRetryPolicy retryPolicy = null) + : this(new InstanceObjectRef(httpClient ?? new()), serializeEvenIfFailed, retryPolicy) + { + } + + /// + /// Initializes a new instance of the JsonHttpClient class. + /// + /// The HTTP client handler to re-use. + /// true if the inner handler should be disposed when the HTTP client is disposed; otherwise, false, to reuse the inner handler. + /// true if still need serialize the result even if it fails to send request; otherwise, false. + /// The retry policy. + public JsonHttpClient(HttpClientHandler httpClientHandler, bool disposeHandler, bool serializeEvenIfFailed = false, IRetryPolicy retryPolicy = null) + : this(new InstanceObjectRef(httpClientHandler is null ? new() : new(httpClientHandler, disposeHandler)), serializeEvenIfFailed, retryPolicy) + { + } + + /// + /// Initializes a new instance of the JsonHttpClient class. + /// + /// true if create a new HTTP client everytime; otherwise, false. + /// true if still need serialize the result even if it fails to send request; otherwise, false. + /// The retry policy. + public JsonHttpClient(bool createHttpClientEvertime, bool serializeEvenIfFailed = false, IRetryPolicy retryPolicy = null) + : this(createHttpClientEvertime ? FactoryObjectResolver.Create() : new InstanceObjectRef(new()), serializeEvenIfFailed, retryPolicy) + { + } + /// /// Adds or removes a handler raised on sending. /// @@ -247,7 +302,7 @@ public class JsonHttpClient /// /// Gets or sets the retry policy. /// - public Tasks.IRetryPolicy RetryPolicy { get; set; } + public IRetryPolicy RetryPolicy { get; set; } /// /// Gets or sets the JSON deserializer. @@ -274,15 +329,6 @@ public class JsonHttpClient /// public IDictionary Bag { get; } = new Dictionary(); - /// - /// Initializes a new instance of the JsonHttpClient class. - /// - public JsonHttpClient() - { - var d = WebFormat.GetJsonDeserializer(true); - if (d != null) Deserializer = d; - } - /// /// Gets HTTP client instance. /// @@ -335,7 +381,7 @@ public async Task SendAsync(HttpRequestMessage request, Action + var result = await RetryExtensions.ProcessAsync(RetryPolicy, async (CancellationToken cancellation) => { resp = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); if (!SerializeEvenIfFailed && !resp.IsSuccessStatusCode) @@ -426,7 +472,7 @@ public Task SendAsync(HttpRequestMessage request, CancellationToken cancellat /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendAsync(HttpMethod method, string requestUri, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Get, requestUri), cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Get, requestUri), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -440,7 +486,7 @@ public Task SendAsync(HttpMethod method, string requestUri, CancellationToken /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendAsync(HttpMethod method, Uri requestUri, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Get, requestUri), cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Get, requestUri), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -455,10 +501,7 @@ public Task SendAsync(HttpMethod method, Uri requestUri, CancellationToken ca /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendAsync(HttpMethod method, string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = content - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, content), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -473,10 +516,7 @@ public Task SendAsync(HttpMethod method, string requestUri, HttpContent conte /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendAsync(HttpMethod method, Uri requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = content - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, content), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -491,10 +531,7 @@ public Task SendAsync(HttpMethod method, Uri requestUri, HttpContent content, /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendAsync(HttpMethod method, string requestUri, QueryData content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = new StringContent(content.ToString(), Encoding.UTF8, Web.WebFormat.FormUrlMIME) - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, new StringContent(content.ToString(), Encoding.UTF8, WebFormat.FormUrlMIME)), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -509,10 +546,7 @@ public Task SendAsync(HttpMethod method, string requestUri, QueryData content /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendAsync(HttpMethod method, Uri requestUri, QueryData content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = new StringContent(content.ToString(), Encoding.UTF8, Web.WebFormat.FormUrlMIME) - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, new StringContent(content.ToString(), Encoding.UTF8, WebFormat.FormUrlMIME)), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -530,10 +564,7 @@ public Task SendAsync(HttpMethod method, Uri requestUri, QueryData content, C public async Task SendJsonAsync(HttpMethod method, string requestUri, object content, JsonSerializerOptions options, CancellationToken cancellationToken = default) { using var body = HttpClientExtensions.CreateJsonContent(content, options); - return await SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = body - }, cancellationToken); + return await SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, body), cancellationToken); } /// @@ -552,10 +583,7 @@ public async Task SendJsonAsync(HttpMethod method, string requestUri, object public async Task SendJsonAsync(HttpMethod method, Uri requestUri, object content, JsonSerializerOptions options, CancellationToken cancellationToken = default) { using var body = HttpClientExtensions.CreateJsonContent(content, options); - return await SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = body - }, cancellationToken); + return await SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, body), cancellationToken); } /// @@ -572,10 +600,7 @@ public async Task SendJsonAsync(HttpMethod method, Uri requestUri, object con /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendJsonAsync(HttpMethod method, string requestUri, object content, DataContractJsonSerializerSettings options, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = HttpClientExtensions.CreateJsonContent(content, options) - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, HttpClientExtensions.CreateJsonContent(content, options)), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -591,10 +616,7 @@ public Task SendJsonAsync(HttpMethod method, string requestUri, object conten /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendJsonAsync(HttpMethod method, Uri requestUri, object content, DataContractJsonSerializerSettings options, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = HttpClientExtensions.CreateJsonContent(content, options) - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, HttpClientExtensions.CreateJsonContent(content, options)), cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -641,10 +663,7 @@ public Task SendJsonAsync(HttpMethod method, Uri requestUri, object content, /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendJsonAsync(HttpMethod method, string requestUri, TRequestBody content, Func deserializer, CancellationToken cancellationToken = default) - => deserializer != null ? SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = new StringContent(deserializer(content), Encoding.UTF8, Text.JsonValues.JsonMIME) - }, cancellationToken) : SendJsonAsync(method, requestUri, content, cancellationToken); + => deserializer != null ? SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, new StringContent(deserializer(content), Encoding.UTF8, Text.JsonValues.JsonMIME)), cancellationToken) : SendJsonAsync(method, requestUri, content, cancellationToken); /// /// Sends an HTTP request and gets the result serialized by JSON. @@ -661,10 +680,7 @@ public Task SendJsonAsync(HttpMethod method, string requestUri, /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task SendJsonAsync(HttpMethod method, Uri requestUri, TRequestBody content, Func deserializer, CancellationToken cancellationToken = default) - => deserializer != null ? SendAsync(new HttpRequestMessage(method ?? HttpMethod.Post, requestUri) - { - Content = new StringContent(deserializer(content), Encoding.UTF8, Text.JsonValues.JsonMIME) - }, cancellationToken) : SendJsonAsync(method, requestUri, content, cancellationToken); + => deserializer != null ? SendAsync(HttpClientExtensions.CreateRequestMessage(method ?? HttpMethod.Post, requestUri, new StringContent(deserializer(content), Encoding.UTF8, Text.JsonValues.JsonMIME)), cancellationToken) : SendJsonAsync(method, requestUri, content, cancellationToken); /// /// Sends an Internet Control Message Protocol (ICMP) echo message with the specified data buffer to the specified computer, and receive a corresponding ICMP echo reply message from that computer as an asynchronous operation. @@ -703,7 +719,7 @@ public Task SendJsonAsync(HttpMethod method, Uri requestUri, TR /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task GetAsync(string requestUri, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Get, requestUri), cancellationToken); /// /// Sends a GET request and gets the result serialized by JSON. @@ -716,7 +732,7 @@ public Task GetAsync(string requestUri, CancellationToken cancellationToken = /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task GetAsync(Uri requestUri, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Get, requestUri), cancellationToken); /// /// Sends a POST request and gets the result serialized by JSON. @@ -727,10 +743,7 @@ public Task GetAsync(Uri requestUri, CancellationToken cancellationToken = de /// A result serialized. /// The inner HTTP web client instance has been disposed. public Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(HttpMethod.Post, requestUri) - { - Content = content - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Post, requestUri, content), cancellationToken); /// /// Sends a POST request and gets the result serialized by JSON. @@ -744,10 +757,7 @@ public Task PostAsync(string requestUri, HttpContent content, CancellationTok /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(HttpMethod.Post, requestUri) - { - Content = content - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Post, requestUri, content), cancellationToken); /// /// Sends a POST request and gets the result serialized by JSON. @@ -761,13 +771,7 @@ public Task PostAsync(Uri requestUri, HttpContent content, CancellationToken /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PostAsync(string requestUri, Text.JsonObjectNode content, CancellationToken cancellationToken = default) - { - var body = HttpClientExtensions.CreateJsonContent(content); - return SendAsync(new HttpRequestMessage(HttpMethod.Post, requestUri) - { - Content = body - }, cancellationToken); - } + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Post, requestUri, HttpClientExtensions.CreateJsonContent(content)), cancellationToken); /// /// Sends a POST request and gets the result serialized by JSON. @@ -781,13 +785,7 @@ public Task PostAsync(string requestUri, Text.JsonObjectNode content, Cancell /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PostAsync(Uri requestUri, Text.JsonObjectNode content, CancellationToken cancellationToken = default) - { - var body = HttpClientExtensions.CreateJsonContent(content); - return SendAsync(new HttpRequestMessage(HttpMethod.Post, requestUri) - { - Content = body - }, cancellationToken); - } + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Post, requestUri, HttpClientExtensions.CreateJsonContent(content)), cancellationToken); /// /// Sends a PUT request and gets the result serialized by JSON. @@ -801,10 +799,7 @@ public Task PostAsync(Uri requestUri, Text.JsonObjectNode content, Cancellati /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(HttpMethod.Put, requestUri) - { - Content = content - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Put, requestUri, content), cancellationToken); /// /// Sends a PUT request and gets the result serialized by JSON. @@ -818,10 +813,7 @@ public Task PutAsync(string requestUri, HttpContent content, CancellationToke /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken = default) - => SendAsync(new HttpRequestMessage(HttpMethod.Put, requestUri) - { - Content = content - }, cancellationToken); + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Put, requestUri, content), cancellationToken); /// /// Sends a PUT request and gets the result serialized by JSON. @@ -835,13 +827,7 @@ public Task PutAsync(Uri requestUri, HttpContent content, CancellationToken c /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PutAsync(string requestUri, Text.JsonObjectNode content, CancellationToken cancellationToken = default) - { - var body = HttpClientExtensions.CreateJsonContent(content); - return SendAsync(new HttpRequestMessage(HttpMethod.Put, requestUri) - { - Content = body - }, cancellationToken); - } + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Put, requestUri, HttpClientExtensions.CreateJsonContent(content)), cancellationToken); /// /// Sends a PUT request and gets the result serialized by JSON. @@ -855,13 +841,7 @@ public Task PutAsync(string requestUri, Text.JsonObjectNode content, Cancella /// The task is cancelled. /// The inner HTTP web client instance has been disposed. public Task PutAsync(Uri requestUri, Text.JsonObjectNode content, CancellationToken cancellationToken = default) - { - var body = HttpClientExtensions.CreateJsonContent(content); - return SendAsync(new HttpRequestMessage(HttpMethod.Put, requestUri) - { - Content = body - }, cancellationToken); - } + => SendAsync(HttpClientExtensions.CreateRequestMessage(HttpMethod.Put, requestUri, HttpClientExtensions.CreateJsonContent(content)), cancellationToken); /// /// Gets the exception need throw. diff --git a/Core/Net/Http/ServerSentEvent.cs b/Core/Net/Http/ServerSentEvent.cs index 4ee708fa..bc3aea3d 100644 --- a/Core/Net/Http/ServerSentEvent.cs +++ b/Core/Net/Http/ServerSentEvent.cs @@ -18,6 +18,21 @@ namespace Trivial.Net; /// /// The record item of Server-Sent Events response. /// +/// +/// +/// // Client side to receive streaming +/// var http = new JsonHttpClient<IAsyncEnumerable<ServerSentEventInfo>>() +/// var sse = await http.GetAsync(A-URL-TO-STREAM-MESSAGE); +/// +/// +/// // Server side (ASP.NET) to push data +/// public IActionResult Streaming() +/// { +/// IAsyncEnumerable<ServerSentEventInfo> sse = GetStreamingData(); +/// return sse.ToActionResult(); +/// } +/// +/// public class ServerSentEventInfo { private readonly Dictionary dict = new(); diff --git a/Core/Net/Uri/QueryData.cs b/Core/Net/Uri/QueryData.cs index e9c1b215..f0ea91f2 100644 --- a/Core/Net/Uri/QueryData.cs +++ b/Core/Net/Uri/QueryData.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; @@ -18,6 +19,7 @@ namespace Trivial.Net; /// /// The query data in URI after question mark. /// +[Guid("E17633F1-1C54-484C-9751-C049368AE6FD")] public class QueryData : StringKeyValuePairs { #pragma warning disable IDE0056, IDE0057, CA1834 diff --git a/Core/Reflection/Lifecycle/ExceptionHandler.cs b/Core/Reflection/Lifecycle/ExceptionHandler.cs index 8412609f..7062a72b 100644 --- a/Core/Reflection/Lifecycle/ExceptionHandler.cs +++ b/Core/Reflection/Lifecycle/ExceptionHandler.cs @@ -24,7 +24,7 @@ public class ExceptionHandler /// /// The exception type. /// The catch handler. - public class Item(Type type, ExceptionHandler.ItemHandler handler) + public class Item(Type type, ItemHandler handler) { /// diff --git a/Core/Reflection/Lifecycle/Initialization.cs b/Core/Reflection/Lifecycle/Initialization.cs index 85c59327..1839e583 100644 --- a/Core/Reflection/Lifecycle/Initialization.cs +++ b/Core/Reflection/Lifecycle/Initialization.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ namespace Trivial.Reflection; /// /// The initialization in thread-safe mode. /// +[Guid("EF30759E-EF34-4C68-9994-5769A147BE4E")] public class Initialization { private SemaphoreSlim semaphoreSlim = new(1, 1); diff --git a/Core/Reflection/Lifecycle/ObservableProperties.cs b/Core/Reflection/Lifecycle/ObservableProperties.cs index 8cbaa66e..9f5c8b3d 100644 --- a/Core/Reflection/Lifecycle/ObservableProperties.cs +++ b/Core/Reflection/Lifecycle/ObservableProperties.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; using System.Xml.Linq; using Trivial.Data; using Trivial.Text; @@ -38,6 +40,23 @@ public enum PropertySettingPolicies /// /// Base model with observable properties. /// +/// +/// +/// public class TestModel : BaseObservableProperties +/// { +/// public string Name +/// { +/// get => GetCurrentProperty<string>(); +/// set => SetCurrentProperty(value); +/// } +/// } +/// +/// +/// var m = new TestModel(); +/// m.PropertyChanged += (sender, e) => Console.WriteLine($"Property {e.PropertyName} changed."); +/// m.Name = "Programming"; +/// +/// public abstract class BaseObservableProperties : INotifyPropertyChanged { /// @@ -123,6 +142,36 @@ protected T GetCurrentProperty(T defaultValue = default, [CallerMemberName] s return defaultValue; } + /// + /// Gets a property value. + /// + /// The type of the property value. + /// A casing rules of the specified culture. + /// An object that supplies culture-specific casing rules. + /// The default value. + /// The additional key. + /// A property value. + protected T GetCurrentProperty(Cases keyCase, CultureInfo culture = null, T defaultValue = default, [CallerMemberName] string key = null) + { + key = StringExtensions.ToSpecificCase(key, keyCase, culture); + if (string.IsNullOrWhiteSpace(key)) return defaultValue; + try + { + return TryGetPropertyInternal(key, out var v) ? (T)v : defaultValue; + } + catch (InvalidCastException) + { + } + catch (NullReferenceException) + { + } + catch (ArgumentException) + { + } + + return defaultValue; + } + /// /// Sets a property. /// @@ -147,6 +196,33 @@ protected bool SetCurrentProperty(object value, [CallerMemberName] string key = throw new InvalidOperationException("Forbid to set property."); } + /// + /// Sets a property. + /// + /// The value. + /// A casing rules of the specified culture. + /// An object that supplies culture-specific casing rules. + /// The additional key. + /// true if set succeeded; otherwise, false. + /// key was null. + /// key was empty or consists only of white-space characters; or s was not in correct format to parse. + protected bool SetCurrentProperty(object value, Cases keyCase, CultureInfo culture = null, [CallerMemberName] string key = null) + { + key = StringExtensions.ToSpecificCase(key, keyCase, culture); + if (!AssertPropertyKey(key)) return false; + var exist = cache.TryGetValue(key, out var v); + if (exist && v == value) return false; + if (PropertiesSettingPolicy == PropertySettingPolicies.Allow) + { + if (!SetPropertyInternal(key, value)) return false; + RaisePropertyChange(v, value, exist ? ChangeMethods.Update : ChangeMethods.Add, key); + return true; + } + + if (PropertiesSettingPolicy == PropertySettingPolicies.Skip) return false; + throw new InvalidOperationException("Forbid to set property."); + } + /// /// Sets a property. /// diff --git a/Core/Reflection/Objects/FactorySet.cs b/Core/Reflection/Objects/FactorySet.cs index 46cbc594..8d040f58 100644 --- a/Core/Reflection/Objects/FactorySet.cs +++ b/Core/Reflection/Objects/FactorySet.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ namespace Trivial.Reflection; /// /// A set of factory used to register and resolve instance. /// +[Guid("B9589B69-A5A1-423E-86BB-85B35C78A2AA")] public sealed class FactorySet { /// diff --git a/Core/Reflection/Objects/ObjectResolver.cs b/Core/Reflection/Objects/ObjectResolver.cs index d96261c6..91a310fb 100644 --- a/Core/Reflection/Objects/ObjectResolver.cs +++ b/Core/Reflection/Objects/ObjectResolver.cs @@ -33,7 +33,7 @@ public sealed class FactoryObjectResolver : IObjectResolver /// Initializes a new instance of the FactoryObjectResolver class. /// /// The handler to create instance. - public FactoryObjectResolver(Func create) + public FactoryObjectResolver(Func create = null) { handler = create ?? Activator.CreateInstance; } diff --git a/Core/Reflection/Objects/Singleton.cs b/Core/Reflection/Objects/Singleton.cs index 3cb822fa..94abb28c 100644 --- a/Core/Reflection/Objects/Singleton.cs +++ b/Core/Reflection/Objects/Singleton.cs @@ -334,7 +334,7 @@ public void Register(string key = null) /// An instance resolved. public T EnsureResolve(string key, IObjectRef reference) { - if (key == null) key = string.Empty; + key ??= string.Empty; var set = GetInstances(typeof(T)); if (!set.TryGetValue(key, out var result)) { diff --git a/Core/Reflection/Objects/VersionComparer.cs b/Core/Reflection/Objects/VersionComparer.cs index 1e21014c..74ad381b 100644 --- a/Core/Reflection/Objects/VersionComparer.cs +++ b/Core/Reflection/Objects/VersionComparer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -9,6 +10,7 @@ namespace Trivial.Reflection; /// /// The version comparer. /// +[Guid("00236369-B525-416D-AB9E-9CD81925457F")] public class VersionComparer : IComparer { /// diff --git a/Core/Security/Cryptography/SignatureProvider.cs b/Core/Security/Cryptography/SignatureProvider.cs index f6423889..d14e6345 100644 --- a/Core/Security/Cryptography/SignatureProvider.cs +++ b/Core/Security/Cryptography/SignatureProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; @@ -62,6 +63,7 @@ public interface ISignatureProvider /// /// The hash signature for string. /// +[Guid("0476149D-9482-409B-9D75-427801ABC853")] public class HashSignatureProvider : ISignatureProvider { private readonly bool needDispose; @@ -429,6 +431,7 @@ public bool Verify(Stream data, byte[] signature) /// /// The RSA hash signature for string. /// +[Guid("0A09C949-B088-461E-A886-A72C06C296EB")] public class RSASignatureProvider : ISignatureProvider { private readonly bool needDispose; @@ -679,6 +682,7 @@ public bool Verify(Stream data, byte[] signature) /// /// The ECDSA hash signature for string. /// +[Guid("5EB7E921-9CF8-41EA-96D1-9F7DA015FFEE")] public class ECDsaSignatureProvider : ISignatureProvider { private readonly bool needDispose; diff --git a/Core/Security/Token/SecretExchange.cs b/Core/Security/Token/SecretExchange.cs index c9180090..7d2c92bf 100644 --- a/Core/Security/Token/SecretExchange.cs +++ b/Core/Security/Token/SecretExchange.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Security.Cryptography; @@ -128,6 +129,7 @@ namespace Trivial.Security; /// var jwt = exchange.ToJsonWebTokenAuthenticationHeaderValue(sign); /// /// +[Guid("2E887BCE-F862-429F-B67C-D15A4DF1856B")] public class RSASecretExchange : ICloneable { /// diff --git a/Core/Security/Token/TokenRequestRoute.cs b/Core/Security/Token/TokenRequestRoute.cs index 6911e2c6..b2e2c652 100644 --- a/Core/Security/Token/TokenRequestRoute.cs +++ b/Core/Security/Token/TokenRequestRoute.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Http.Headers; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Text; @@ -40,6 +41,7 @@ namespace Trivial.Security; /// var resp = await route.SignInAsync(tokenReq); /// /// +[Guid("1E9E554C-82DB-4D5C-8C5A-A0D054D17426")] public class TokenRequestRoute { /// diff --git a/Core/Tasks/Interceptor/Interceptor.cs b/Core/Tasks/Interceptor/Interceptor.cs index acc99d92..3ea27129 100644 --- a/Core/Tasks/Interceptor/Interceptor.cs +++ b/Core/Tasks/Interceptor/Interceptor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -33,6 +34,7 @@ namespace Trivial.Tasks; /// /// /// +[Guid("61BDE098-5EFF-413B-B0DE-D53667377015")] public class Interceptor : BaseInterceptor { /// diff --git a/Core/Tasks/Interceptor/InterceptorPolicy.cs b/Core/Tasks/Interceptor/InterceptorPolicy.cs index 5cfa9daa..c1b85855 100644 --- a/Core/Tasks/Interceptor/InterceptorPolicy.cs +++ b/Core/Tasks/Interceptor/InterceptorPolicy.cs @@ -51,6 +51,7 @@ public enum InterceptorModes : byte /// The interceptor mode to determine which one invokes in the above invoking times limitation, e.g.the first one, the last one or all. /// /// +[Guid("11698602-816D-4878-9E1C-5D453AF81E86")] public sealed class InterceptorPolicy : ICloneable, IEquatable { /// diff --git a/Core/Tasks/Observable/Progress.cs b/Core/Tasks/Observable/Progress.cs index d0eb53cf..398a742d 100644 --- a/Core/Tasks/Observable/Progress.cs +++ b/Core/Tasks/Observable/Progress.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -9,6 +10,7 @@ namespace Trivial.Tasks; /// /// The progress from zero (0) to one (1) in double-floating number. /// +[Guid("3C47D491-58A0-493D-9A69-803F8A4B1FA0")] public class OneProgress : IProgress, INotifyPropertyChanged { /// @@ -102,9 +104,7 @@ public void ReportProcessing(double value) /// /// The delta value to update. public void Increase(double delta = 0.01) - { - Report(Value + delta); - } + => Report(Value + delta); /// /// Increases value in a given time inteval. diff --git a/Core/Tasks/Observable/StateEventArgs.cs b/Core/Tasks/Observable/StateEventArgs.cs index 20b9247b..4e64e9e5 100644 --- a/Core/Tasks/Observable/StateEventArgs.cs +++ b/Core/Tasks/Observable/StateEventArgs.cs @@ -86,6 +86,27 @@ public ResultEventArgs(T result, TaskStates state = TaskStates.Done, Exception e /// The state. public ResultEventArgs(Exception exception, TaskStates state = TaskStates.Faulted) : base(state) => Exception = exception; + /// + /// Initializes a new instance of the ResultEventArgs class. + /// + /// The additional message. + /// The result. + /// The state. + /// The exception. + public ResultEventArgs(string message, T result, TaskStates state = TaskStates.Done, Exception exception = null) : base(state, message) + { + Result = result; + Exception = exception; + } + + /// + /// Initializes a new instance of the ResultEventArgs class. + /// + /// The additional message. + /// The exception. + /// The state. + public ResultEventArgs(string message, Exception exception, TaskStates state = TaskStates.Faulted) : base(state, message) => Exception = exception; + /// /// Gets the result. /// diff --git a/Core/Tasks/Retry/RetryExtensions.cs b/Core/Tasks/Retry/RetryExtensions.cs index 4146067b..35635993 100644 --- a/Core/Tasks/Retry/RetryExtensions.cs +++ b/Core/Tasks/Retry/RetryExtensions.cs @@ -166,7 +166,7 @@ public static async Task> ProcessAsync(this IRetryPolicy polic { result.Fail(ex); ex = needThrow(ex); - if (ex != null) throw ex; + if (ex != null) throw; } var span = retry.Next(); diff --git a/Core/Text/JsonNode/Array.cs b/Core/Text/JsonNode/Array.cs index e531101b..e0d0ace9 100644 --- a/Core/Text/JsonNode/Array.cs +++ b/Core/Text/JsonNode/Array.cs @@ -53,6 +53,7 @@ namespace Trivial.Text; /// [Serializable] [System.Text.Json.Serialization.JsonConverter(typeof(JsonValueNodeConverter.ArrayConverter))] +[Guid("710F2703-04E9-4C13-9B6A-3403EC89A298")] public class JsonArrayNode : BaseJsonValueNode, IJsonContainerNode, IReadOnlyList, IReadOnlyList, IEquatable, ISerializable, INotifyPropertyChanged, INotifyCollectionChanged #if NET8_0_OR_GREATER , IParsable, IAdditionOperators, IAdditionOperators, JsonArrayNode>, IAdditionOperators, JsonArrayNode> diff --git a/Core/Text/JsonNode/Boolean.cs b/Core/Text/JsonNode/Boolean.cs index 6bb2ddd2..79644a3e 100644 --- a/Core/Text/JsonNode/Boolean.cs +++ b/Core/Text/JsonNode/Boolean.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -13,6 +14,7 @@ namespace Trivial.Text; /// Represents a specific JSON boolean value. /// [System.Text.Json.Serialization.JsonConverter(typeof(JsonValueNodeConverter.BooleanConverter))] +[Guid("BB8C0C59-D1D0-47B4-9C94-0DE7680FAFC8")] public sealed class JsonBooleanNode : BaseJsonValueNode, IConvertible #if NET8_0_OR_GREATER , IParsable diff --git a/Core/Text/JsonNode/Number.cs b/Core/Text/JsonNode/Number.cs index bd35583b..7b1c8a92 100644 --- a/Core/Text/JsonNode/Number.cs +++ b/Core/Text/JsonNode/Number.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; +using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -28,6 +29,7 @@ public interface IJsonNumberNode : IJsonValueNode, IEquatable, /// Represents a specific JSON integer number value. /// [System.Text.Json.Serialization.JsonConverter(typeof(JsonValueNodeConverter.IntegerConverter))] +[Guid("46D19567-7B97-42B8-9BC5-01CFB01E320B")] public sealed class JsonIntegerNode : BaseJsonValueNode, IJsonNumberNode, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IEquatable, IEquatable, IEquatable, IEquatable, IFormattable, IConvertible, IAdvancedAdditionCapable #if NET8_0_OR_GREATER , IParsable, IUnaryNegationOperators, IAdditionOperators, JsonIntegerNode>, IAdditionOperators, JsonIntegerNode>, IAdditionOperators, IAdditionOperators, ISubtractionOperators, JsonIntegerNode>, ISubtractionOperators, JsonIntegerNode>, ISubtractionOperators, ISubtractionOperators @@ -1443,6 +1445,7 @@ public static explicit operator JsonDecimalNode(JsonIntegerNode json) /// Represents a specific JSON double float number value. /// [System.Text.Json.Serialization.JsonConverter(typeof(JsonValueNodeConverter.DoubleConverter))] +[Guid("25098B53-EC11-48E1-A8FD-AFB685C38BA9")] public sealed class JsonDoubleNode : BaseJsonValueNode, IJsonNumberNode, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IEquatable, IEquatable, IEquatable, IEquatable, IEquatable, IFormattable, IConvertible, IAdvancedAdditionCapable #if NET8_0_OR_GREATER , IParsable, IUnaryNegationOperators, IAdditionOperators, JsonDoubleNode>, IAdditionOperators, JsonDoubleNode>, IAdditionOperators, ISubtractionOperators, JsonDoubleNode>, ISubtractionOperators, JsonDoubleNode>, ISubtractionOperators @@ -2968,6 +2971,7 @@ public static explicit operator JsonDecimalNode(JsonDoubleNode json) /// Represents a specific JSON decimal float number value. /// [System.Text.Json.Serialization.JsonConverter(typeof(JsonValueNodeConverter.DecimalConverter))] +[Guid("2DE9E34B-A394-4BF7-B814-2A6765420E05")] public sealed class JsonDecimalNode : BaseJsonValueNode, IJsonNumberNode, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IComparable, IEquatable, IEquatable, IEquatable, IEquatable, IEquatable, IFormattable, IConvertible, IAdvancedAdditionCapable #if NET8_0_OR_GREATER , IParsable, IUnaryNegationOperators, IAdditionOperators, JsonDecimalNode>, IAdditionOperators, ISubtractionOperators, JsonDecimalNode>, ISubtractionOperators diff --git a/Core/Text/JsonNode/Object.cs b/Core/Text/JsonNode/Object.cs index 53422203..0110c671 100644 --- a/Core/Text/JsonNode/Object.cs +++ b/Core/Text/JsonNode/Object.cs @@ -55,6 +55,7 @@ namespace Trivial.Text; /// [Serializable] [JsonConverter(typeof(JsonValueNodeConverter.ObjectConverter))] +[Guid("2D5D5CB8-9ACD-4DB6-9B0F-1D766EF64D75")] public class JsonObjectNode : BaseJsonValueNode, IJsonContainerNode, IDictionary, IDictionary, IReadOnlyDictionary, IReadOnlyDictionary, IEquatable, ISerializable, INotifyPropertyChanged #if NET8_0_OR_GREATER , IParsable diff --git a/Core/Text/JsonNode/String.cs b/Core/Text/JsonNode/String.cs index 37f221c3..17143c73 100644 --- a/Core/Text/JsonNode/String.cs +++ b/Core/Text/JsonNode/String.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.Numerics; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Text.Json; @@ -18,6 +19,7 @@ namespace Trivial.Text; /// Represents a specific JSON string value. /// [System.Text.Json.Serialization.JsonConverter(typeof(JsonValueNodeConverter.StringConverter))] +[Guid("F6732685-1CEA-42D3-A13D-42AD3FA235DE")] public sealed class JsonStringNode : BaseJsonValueNode, IComparable>, IComparable, IEquatable, IEquatable, IEquatable, IReadOnlyList #if NET8_0_OR_GREATER , IAdditionOperators, JsonStringNode>, IAdditionOperators diff --git a/Core/Text/JsonSchema/ObjectNode.cs b/Core/Text/JsonSchema/ObjectNode.cs index f9ce3b91..ee139b0f 100644 --- a/Core/Text/JsonSchema/ObjectNode.cs +++ b/Core/Text/JsonSchema/ObjectNode.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; namespace Trivial.Text; @@ -16,6 +17,7 @@ public class JsonObjectSchemaDescription : JsonNodeSchemaDescription /// public JsonObjectSchemaDescription() { + Properties = new(); } /// @@ -25,6 +27,22 @@ public JsonObjectSchemaDescription() public JsonObjectSchemaDescription(JsonObjectNode json) : this(json, false) { + Properties = new(); + } + + /// + /// Initializes a new instance of the JsonObjectSchemaDescription class. + /// + /// The description of properties. + public JsonObjectSchemaDescription(Dictionary properties, IEnumerable requiredPropertyNames = null) + { + Properties = properties; + if (requiredPropertyNames == null) return; + foreach (var prop in requiredPropertyNames) + { + if (string.IsNullOrWhiteSpace(prop) || RequiredPropertyNames.Contains(prop)) continue; + RequiredPropertyNames.Add(prop); + } } /// @@ -35,6 +53,7 @@ public JsonObjectSchemaDescription(JsonObjectNode json) protected JsonObjectSchemaDescription(JsonObjectNode json, bool skipExtendedProperties) : base(json, true) { + Properties = new(); if (json == null) return; DefaultValue = json.TryGetObjectValue("default"); JsonValues.FillObjectSchema(Properties, json, "properties"); @@ -102,7 +121,7 @@ protected JsonObjectSchemaDescription(JsonObjectNode json, bool skipExtendedProp /// Gets the description of properties. /// The dictionary keys are the property names. /// - public Dictionary Properties { get; } = new(); + public Dictionary Properties { get; } /// /// Gets the pattern description properties. @@ -231,6 +250,34 @@ public JsonBooleanSchemaDescription SetBooleanProperty(string propertyName, stri public bool RemoveProperty(string propertyName) => Properties.Remove(propertyName); + /// + /// Converts the schema to TypeScript definition string. + /// + /// The interface name. + /// A TypeScript definition string. + public string ToTypeScriptDefinitionString(string name) + { + var sb = new StringBuilder(); + sb.Append("export interface "); + sb.Append(name); + sb.Append(' '); + ToTypeScriptDefinitionString(sb, 0); + sb.AppendLine(); + return sb.ToString(); + } + + /// + /// Converts the schema to Type Script definition string. + /// + /// A Type Script definition string. + public string ToTypeScriptDefinitionString() + { + var sb = new StringBuilder(); + ToTypeScriptDefinitionString(sb, 0); + sb.AppendLine(); + return sb.ToString(); + } + /// protected override void FillProperties(JsonObjectNode node) { @@ -275,4 +322,105 @@ private T SetPropertyInternal(string propertyName, T value) where T : JsonNod Properties[propertyName] = value; return value; } + + /// + /// Converts the schema to TypeScript definition string. + /// + /// The string builder. + /// The indent level. + /// A TypeScript definition string. + private void ToTypeScriptDefinitionString(StringBuilder sb, int indent) + { + var indentStr = new string(' ', indent * 2); + sb.AppendLine("{"); + foreach (var prop in Properties) + { + var key = prop.Key; + var v = prop.Value; + if (string.IsNullOrWhiteSpace(key) || v == null) continue; + if (!string.IsNullOrWhiteSpace(v.Description)) + { + sb.AppendLine(); + sb.Append(indentStr); + sb.AppendLine(" /** "); + sb.Append(indentStr); + sb.Append(" * "); + sb.AppendLine(v.Description); + sb.Append(indentStr); + sb.AppendLine(" */"); + } + + sb.Append(indentStr); + sb.Append(" "); + sb.Append(key); + if (!RequiredPropertyNames.Contains(key)) sb.Append('?'); + sb.Append(": "); + if (v is JsonStringSchemaDescription str) + { + sb.Append(str.ConstantValue ?? "string"); + } + else if (v is JsonNumberSchemaDescription || v is JsonIntegerSchemaDescription) + { + sb.Append("number"); + } + else if (v is JsonBooleanSchemaDescription) + { + sb.Append("boolean"); + } + else if (v is JsonObjectSchemaDescription obj) + { + obj.ToTypeScriptDefinitionString(sb, indent + 1); + } + else if (v is JsonArraySchemaDescription arr) + { + var item = arr.DefaultItems; + if (item != null && (arr.FixedItems == null || arr.FixedItems.Count == 0)) + { + if (item is JsonStringSchemaDescription str2) + { + sb.Append(str2.ConstantValue ?? "string"); + } + else if (item is JsonNumberSchemaDescription || item is JsonIntegerSchemaDescription) + { + sb.Append("number"); + } + else if (item is JsonBooleanSchemaDescription) + { + sb.Append("boolean"); + } + else if (item is JsonObjectSchemaDescription obj2) + { + obj2.ToTypeScriptDefinitionString(sb, indent + 1); + } + else + { + sb.Append("any"); + } + } + + sb.Append("[]"); + } + else + { + sb.Append("any"); + } + + if (v.EnumItems != null && v.EnumItems.Count > 0) + { + sb.Append(" | "); + sb.Append(string.Join(" | ", v.EnumItems.Select(o => o.ToString()))); + } + + sb.AppendLine(";"); + } + + if (!DisableAdditionalProperties) + { + sb.Append(indentStr); + sb.AppendLine(" [key: string]: any;"); + } + + sb.Append(indentStr); + sb.Append('}'); + } } diff --git a/Core/Text/JsonSchema/StringNode.cs b/Core/Text/JsonSchema/StringNode.cs index abc751f1..43419b82 100644 --- a/Core/Text/JsonSchema/StringNode.cs +++ b/Core/Text/JsonSchema/StringNode.cs @@ -101,7 +101,7 @@ protected JsonStringSchemaDescription(JsonObjectNode json, bool skipExtendedProp protected override void FillProperties(JsonObjectNode node) { node.SetValueIfNotNull("default", DefaultValue); - node.SetValueIfNotNull("const", DefaultValue); + node.SetValueIfNotNull("const", ConstantValue); node.SetValueIfNotEmpty("contentMediaType", ContentType); node.SetValueIfNotEmpty("contentEncoding", ContentEncoding); node.SetValueIfNotNull("minLength", MinLength); diff --git a/Core/Text/Table/CsvParser.cs b/Core/Text/Table/CsvParser.cs index f23ec50f..f83bdeb0 100644 --- a/Core/Text/Table/CsvParser.cs +++ b/Core/Text/Table/CsvParser.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; namespace Trivial.Text; @@ -33,6 +34,7 @@ namespace Trivial.Text; /// } /// /// +[Guid("D58B97EF-FF39-4CD9-AD95-2CDC65705D4C")] public class CsvParser : BaseLinesStringTableParser { /// diff --git a/Core/Text/Table/TsvParser.cs b/Core/Text/Table/TsvParser.cs index 396a718f..ae89e15b 100644 --- a/Core/Text/Table/TsvParser.cs +++ b/Core/Text/Table/TsvParser.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; namespace Trivial.Text; @@ -9,6 +10,7 @@ namespace Trivial.Text; /// /// The text parser for tab-separated values file format. /// +[Guid("E90FE0E3-799C-4E0C-94DC-8D1925EA7F0E")] public class TsvParser : BaseLinesStringTableParser { /// diff --git a/UnitTest/Net/HttpClientVerb.cs b/UnitTest/Net/HttpClientVerb.cs index 94194bf3..9dc1d5d5 100644 --- a/UnitTest/Net/HttpClientVerb.cs +++ b/UnitTest/Net/HttpClientVerb.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Reflection.Metadata.Ecma335; using System.Runtime.Serialization; using System.Text; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Trivial.CommandLine; +using Trivial.Data; using Trivial.Security; using Trivial.Text; @@ -100,8 +102,10 @@ protected override async Task OnProcessAsync(CancellationToken cancellationToken { var oauth = new OAuthClient("trivial.cli", "demo-used") { - TokenResolverUri = new(url + "Login") + TokenResolverUri = new(url + "Login"), + HttpClientResolver = HttpClientExtensions.CreateResolver(out var http) }; + HttpClientExtensions.SetPlatform(http.DefaultRequestHeaders, true); const string name = "admin"; const string password = "P@ssw0rd"; DefaultConsole.Write("Signing in..."); @@ -114,7 +118,7 @@ protected override async Task OnProcessAsync(CancellationToken cancellationToken await oauth.ResolveTokenAsync(new PasswordTokenRequestBody(name, password), cancellationToken); DefaultConsole.BackspaceToBeginning(); DefaultConsole.WriteLine($"Signed in succeeded by account {name}."); - var resp = await client.GetAsync(url + "Data"); + var resp = await client.GetAsync(url + "Data", cancellationToken); DefaultConsole.WriteLine(); DefaultConsole.WriteLine(resp); DefaultConsole.WriteLine(); @@ -129,5 +133,7 @@ protected override async Task OnProcessAsync(CancellationToken cancellationToken } DefaultConsole.WriteLine(ConsoleColor.Green, "Done!"); + + //var col = await oauth.Create>().GetAsync(url + "Items"); } } diff --git a/UnitTest/Text/JsonUnitTest.cs b/UnitTest/Text/JsonUnitTest.cs index e75aaaa0..8b515144 100644 --- a/UnitTest/Text/JsonUnitTest.cs +++ b/UnitTest/Text/JsonUnitTest.cs @@ -664,6 +664,40 @@ public void TestJsonSwitch() Assert.IsFalse(json.GetValue("arr").Switch().Case(TestObject, (s, i) => Assert.Fail()).IsPassed); } + /// + /// Tests JSON schema. + /// + [TestMethod] + public void TestJsonSchema() + { + var schema = new JsonObjectSchemaDescription(new Dictionary + { + { + "str", new JsonStringSchemaDescription + { + DefaultValue = "Hey!", + } + }, + { + "num", new JsonNumberSchemaDescription() + }, + { + "b", new JsonBooleanSchemaDescription() + }, + { + "arr", new JsonArraySchemaDescription + { + DefaultItems = new JsonObjectSchemaDescription() + } + }, + }) + { + DisableAdditionalProperties = true + }; + Assert.IsNotNull(schema.ToTypeScriptDefinitionString()); + Assert.IsNotNull(schema.ToJson()); + } + private static bool TestObject(IJsonValueNode node, out string a, out int b) { if (node is not JsonObjectNode json)