From 5cb482f8f80455e2c713bd9a77c54406bef6022a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 7 Jun 2024 14:52:06 -0400 Subject: [PATCH] com.utilities.rest 3.1.0 (#79) - refactor Server Sent Event Implementation - added Response.ctr with reference to UnityWebRequest - added Response.ServerSentEvents list - sealed all public classes --- README.md | 21 +- .../Documentation~/README.md | 21 +- .../EmptyToNullStringContractResolver.cs | 2 +- .../EmptyToNullStringValueProvider.cs | 2 +- .../com.utilities.rest/Runtime/Response.cs | 89 +++++-- .../com.utilities.rest/Runtime/Rest.cs | 219 +++++++++++------- .../Runtime/RestException.cs | 2 +- .../Runtime/RestParameters.cs | 5 +- .../Runtime/ServerSentEvent.cs | 51 ++++ .../Runtime/ServerSentEvent.cs.meta | 11 + .../Runtime/ServerSentEventKind.cs | 13 ++ .../Runtime/ServerSentEventKind.cs.meta | 11 + .../Packages/com.utilities.rest/package.json | 2 +- 13 files changed, 303 insertions(+), 146 deletions(-) create mode 100644 Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs create mode 100644 Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs.meta create mode 100644 Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs create mode 100644 Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs.meta diff --git a/README.md b/README.md index 9643ce2..46ccd37 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Advanced features includes progress notifications, authentication and native mul - [Rest Parameters](#rest-parameters) - [Get](#get) - [Post](#post) - - [Server Sent Events](#server-sent-events) + - [Server Sent Events](#server-sent-events) :new: - [Data Received Callbacks](#data-received-callbacks) - [Put](#put) - [Patch](#patch) @@ -115,25 +115,10 @@ response.Validate(debug: true); #### Server Sent Events -> [!WARNING] -> Breaking change. `eventData` payloads are now json objects where the type is the key and field data is value. -> For existing data callbacks, they are now nested: `{"data":"{}"}` - -Handles [server sent event](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) messages. - -`eventData` json Schema: - -```json -{ - "type":"value", - "data":"{}" // nullable -} -``` - ```csharp var jsonData = "{\"data\":\"content\"}"; -var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, eventData => { - Debug.Log(eventData); +var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, (sseResponse, ssEvent) => { + Debug.Log(ssEvent); }); // Validates the response for you and will throw a RestException if the response is unsuccessful. response.Validate(debug: true); diff --git a/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md b/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md index 5a9e264..f00fa26 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md +++ b/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md @@ -46,7 +46,7 @@ Advanced features includes progress notifications, authentication and native mul - [Rest Parameters](#rest-parameters) - [Get](#get) - [Post](#post) - - [Server Sent Events](#server-sent-events) + - [Server Sent Events](#server-sent-events) :new: - [Data Received Callbacks](#data-received-callbacks) - [Put](#put) - [Patch](#patch) @@ -115,25 +115,10 @@ response.Validate(debug: true); #### Server Sent Events -> [!WARNING] -> Breaking change. `eventData` payloads are now json objects where the type is the key and field data is value. -> For existing data callbacks, they are now nested: `{"data":"{}"}` - -Handles [server sent event](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) messages. - -`eventData` json Schema: - -```json -{ - "type":"value", - "data":"{}" // nullable -} -``` - ```csharp var jsonData = "{\"data\":\"content\"}"; -var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, eventData => { - Debug.Log(eventData); +var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, (sseResponse, ssEvent) => { + Debug.Log(ssEvent); }); // Validates the response for you and will throw a RestException if the response is unsuccessful. response.Validate(debug: true); diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringContractResolver.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringContractResolver.cs index 35f5481..664d2e9 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringContractResolver.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringContractResolver.cs @@ -12,7 +12,7 @@ namespace Utilities.Rest.Extensions /// Converts empty strings to null values so they are properly ignored by the serializer. /// https://stackoverflow.com/questions/39855694/convert-empty-strings-to-null-with-json-net /// - public class EmptyToNullStringContractResolver : DefaultContractResolver + public sealed class EmptyToNullStringContractResolver : DefaultContractResolver { protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) => type.GetProperties() diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringValueProvider.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringValueProvider.cs index c6c28c9..b2934c5 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringValueProvider.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Extensions/EmptyToNullStringValueProvider.cs @@ -8,7 +8,7 @@ namespace Utilities.Rest.Extensions /// /// https://stackoverflow.com/questions/39855694/convert-empty-strings-to-null-with-json-net /// - public class EmptyToNullStringValueProvider : IValueProvider + public sealed class EmptyToNullStringValueProvider : IValueProvider { private readonly PropertyInfo memberInfo; diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Response.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Response.cs index b6703a7..3920257 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Response.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Response.cs @@ -5,14 +5,17 @@ using System; using System.Collections.Generic; using System.Text; +using UnityEngine.Networking; namespace Utilities.WebRequestRest { /// /// Response to a REST Call. /// - public class Response + public sealed class Response { + private static readonly Dictionary invalidHeaders = new() { { "Invalid Headers", "Invalid Headers" } }; + /// /// The original request that prompted the response. /// @@ -63,6 +66,66 @@ public class Response /// public RestParameters Parameters { get; } + /// + /// Full list of server sent events. + /// + public IReadOnlyList ServerSentEvents => Parameters?.ServerSentEvents; + + /// + /// Constructor. + /// + /// The request that prompted the response. + /// The request body that prompted the response. + /// Was the request successful? + /// The parameters of the request. + /// Optional, response body override. + public Response(UnityWebRequest webRequest, string requestBody, bool successful, RestParameters parameters, string responseBody = null) + { + Request = webRequest.url; + RequestBody = requestBody; + Method = webRequest.method; + Successful = successful; + + if (string.IsNullOrWhiteSpace(responseBody)) + { + switch (webRequest.downloadHandler) + { + case DownloadHandlerFile: + case DownloadHandlerTexture: + case DownloadHandlerAudioClip: + case DownloadHandlerAssetBundle: + Body = null; + Data = null; + break; + case DownloadHandlerBuffer downloadHandlerBuffer: + Body = downloadHandlerBuffer.text; + Data = downloadHandlerBuffer.data; + break; + case DownloadHandlerScript downloadHandlerScript: + Body = downloadHandlerScript.text; + Data = downloadHandlerScript.data; + break; + default: + Body = webRequest.responseCode == 401 ? "Invalid Credentials" : webRequest.downloadHandler?.text; + Data = webRequest.downloadHandler?.data; + break; + } + } + else + { + Body = responseBody; + } + + Code = webRequest.responseCode; + Headers = webRequest.GetResponseHeaders() ?? invalidHeaders; + Parameters = parameters; + + if (!successful) + { + Error = $"{webRequest.error}\n{webRequest.downloadHandler?.error}"; + } + } + /// /// Constructor. /// @@ -158,29 +221,27 @@ public string ToString(string methodName) { if (Parameters.ServerSentEvents.Count > 0) { - debugMessageObject["response"]["body"] = new JArray(); + var array = new JArray(); - foreach (var (type, value, data) in Parameters.ServerSentEvents) + foreach (var @event in Parameters.ServerSentEvents) { var eventObject = new JObject { - [type] = value + [@event.Event.ToString().ToLower()] = @event.Value }; - if (!string.IsNullOrWhiteSpace(data)) + if (@event.Data != null) { - try - { - eventObject[nameof(data)] = JToken.Parse(data); - } - catch - { - eventObject[nameof(data)] = data; - } + eventObject["data"] = @event.Data; } - ((JArray)debugMessageObject["response"]["body"]).Add(eventObject); + array.Add(eventObject); } + + debugMessageObject["response"]["body"] = new JObject + { + ["events"] = array + }; } else { diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs index d6f45c1..05250a0 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs @@ -78,10 +78,22 @@ public static async Task GetAsync( /// Rest GET. /// /// Finalized Endpoint Query with parameters. - /// server sent event callback. + /// server sent event callback handler. /// Optional, . /// Optional, . /// The response data. + public static async Task GetAsync( + string query, + Action serverSentEventHandler, + RestParameters parameters = null, + CancellationToken cancellationToken = default) + { + await Awaiters.UnityMainThread; + using var webRequest = UnityWebRequest.Get(query); + return await webRequest.SendAsync(parameters, serverSentEventHandler, cancellationToken); + } + + [Obsolete("use new overload with serverSentEventHandler")] public static async Task GetAsync( string query, Action serverSentEventCallback, @@ -196,10 +208,29 @@ public static async Task PostAsync( /// /// Finalized Endpoint Query with parameters. /// JSON data for the request. - /// server sent event callback. + /// server sent event callback handler. /// Optional, . /// Optional, . /// The response data. + public static async Task PostAsync( + string query, + string jsonData, + Action serverSentEventHandler, + RestParameters parameters = null, + CancellationToken cancellationToken = default) + { + await Awaiters.UnityMainThread; + using var webRequest = new UnityWebRequest(query, UnityWebRequest.kHttpVerbPOST); + var data = new UTF8Encoding().GetBytes(jsonData); + using var uploadHandler = new UploadHandlerRaw(data); + webRequest.uploadHandler = uploadHandler; + using var downloadHandler = new DownloadHandlerBuffer(); + webRequest.downloadHandler = downloadHandler; + webRequest.SetRequestHeader(content_type, application_json); + return await webRequest.SendAsync(parameters, serverSentEventHandler, cancellationToken); + } + + [Obsolete("use new overload with serverSentEventHandler")] public static async Task PostAsync( string query, string jsonData, @@ -250,7 +281,7 @@ public static async Task PostAsync( try { - return await webRequest.SendAsync(parameters, null, cancellationToken); + return await webRequest.SendAsync(parameters, serverSentEventHandler: null, cancellationToken); } finally { @@ -1026,20 +1057,43 @@ public static async Task SendAsync( this UnityWebRequest webRequest, RestParameters parameters = null, CancellationToken cancellationToken = default) - => await SendAsync(webRequest, parameters, null, cancellationToken); + => await SendAsync(webRequest, parameters, serverSentEventHandler: null, cancellationToken); + + [Obsolete("Use new overload with serverSentEventHandler")] + public static async Task SendAsync( + this UnityWebRequest webRequest, + RestParameters parameters = null, + Action serverSentEventCallback = null, + CancellationToken cancellationToken = default) + { + Action serverSentEventHandler = null; + + if (serverSentEventCallback != null) + { + serverSentEventHandler = (_, @event) => + { + if (@event.Value != null) + { + serverSentEventCallback.Invoke(@event.Value.ToString(Formatting.None)); + } + }; + } + + return await SendAsync(webRequest, parameters, serverSentEventHandler, cancellationToken); + } /// /// Process a asynchronously. /// /// The . /// Optional, . - /// Optional, server sent event callback. + /// Optional, server sent event callback handler. /// Optional . /// public static async Task SendAsync( this UnityWebRequest webRequest, RestParameters parameters = null, - Action serverSentEventCallback = null, + Action serverSentEventHandler = null, CancellationToken cancellationToken = default) { await Awaiters.UnityMainThread; @@ -1079,8 +1133,48 @@ UnityWebRequest.kHttpVerbPUT or webRequest.disposeDownloadHandlerOnDispose = parameters?.DisposeDownloadHandler ?? true; webRequest.disposeUploadHandlerOnDispose = parameters?.DisposeUploadHandler ?? true; + var requestBody = string.Empty; + + if (hasUpload && webRequest.uploadHandler != null) + { + var contentType = webRequest.GetRequestHeader(content_type); + if (webRequest.uploadHandler.data is { Length: > 0 } && + contentType.Contains("multipart/form-data")) + { + var boundary = contentType.Split(';')[1].Split('=')[1]; + var encodedData = Encoding.UTF8.GetString(webRequest.uploadHandler.data); + var formData = encodedData.Split(new[] { $"\r\n--{boundary}\r\n", $"\r\n--{boundary}--\r\n" }, StringSplitOptions.RemoveEmptyEntries); + var formParts = new Dictionary(); + + foreach (var form in formData) + { + var formFields = form.Split(new[] { "\r\n\r\n" }, StringSplitOptions.RemoveEmptyEntries); + var fieldHeader = formFields[0]; + var key = fieldHeader.Split(new[] { "name=\"" }, StringSplitOptions.RemoveEmptyEntries)[1].Split("\"")[0]; + + if (fieldHeader.Contains("application/octet-stream")) + { + var fileName = fieldHeader.Split(new[] { "filename=\"" }, StringSplitOptions.RemoveEmptyEntries)[1].Split("\"")[0]; + formParts.Add(key, fileName); + } + else + { + var value = formFields[1]; + formParts.Add(key, value); + } + } + + requestBody = JsonConvert.SerializeObject(new { contentType, formParts }); + } + else + { + requestBody = string.Empty; + } + } + + if (parameters is { Progress: not null } || - serverSentEventCallback != null) + serverSentEventHandler != null) { async void CallbackThread() { @@ -1098,9 +1192,9 @@ async void CallbackThread() while (!webRequest.isDone) { - if (serverSentEventCallback != null) + if (serverSentEventHandler != null) { - SendServerEventCallback(false); + SendServerEventCallback(false, requestBody); } if (parameters is { Progress: not null }) @@ -1160,91 +1254,34 @@ async void CallbackThread() #pragma warning restore CS4014 } - var requestBody = string.Empty; - - if (hasUpload && webRequest.uploadHandler != null) - { - var contentType = webRequest.GetRequestHeader(content_type); - if (webRequest.uploadHandler.data is { Length: > 0 } && - contentType.Contains("multipart/form-data")) - { - var boundary = contentType.Split(';')[1].Split('=')[1]; - var encodedData = Encoding.UTF8.GetString(webRequest.uploadHandler.data); - var formData = encodedData.Split(new[] { $"\r\n--{boundary}\r\n", $"\r\n--{boundary}--\r\n" }, StringSplitOptions.RemoveEmptyEntries); - var formParts = new Dictionary(); - - foreach (var form in formData) - { - var formFields = form.Split(new[] { "\r\n\r\n" }, StringSplitOptions.RemoveEmptyEntries); - var fieldHeader = formFields[0]; - var key = fieldHeader.Split(new[] { "name=\"" }, StringSplitOptions.RemoveEmptyEntries)[1].Split("\"")[0]; - - if (fieldHeader.Contains("application/octet-stream")) - { - var fileName = fieldHeader.Split(new[] { "filename=\"" }, StringSplitOptions.RemoveEmptyEntries)[1].Split("\"")[0]; - formParts.Add(key, fileName); - } - else - { - var value = formFields[1]; - formParts.Add(key, value); - } - } - - requestBody = JsonConvert.SerializeObject(new { contentType, formParts }); - } - else - { - requestBody = string.Empty; - } - } - try { await webRequest.SendWebRequest(); } + catch (Exception e) { return new Response(webRequest.url, webRequest.method, requestBody, false, $"{nameof(Rest)}.{nameof(SendAsync)}::{nameof(UnityWebRequest.SendWebRequest)} Failed!", null, -1, null, parameters, e.ToString()); } parameters?.Progress?.Report(new Progress(webRequest.downloadedBytes, webRequest.downloadedBytes, 100f, 0, Progress.DataUnit.b)); - var responseHeaders = webRequest.GetResponseHeaders() ?? new Dictionary { { "Invalid Headers", "Invalid Headers" } }; if (webRequest.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError && webRequest.responseCode is 0 or >= 400) { - return webRequest.downloadHandler switch - { - DownloadHandlerFile => new Response(webRequest.url, webRequest.method, requestBody, false, null, null, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}"), - DownloadHandlerTexture => new Response(webRequest.url, webRequest.method, requestBody, false, null, null, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}"), - DownloadHandlerAudioClip => new Response(webRequest.url, webRequest.method, requestBody, false, null, null, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}"), - DownloadHandlerAssetBundle => new Response(webRequest.url, webRequest.method, requestBody, false, null, null, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}"), - DownloadHandlerBuffer bufferDownloadHandler => new Response(webRequest.url, webRequest.method, requestBody, false, bufferDownloadHandler.text, bufferDownloadHandler.data, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}"), - DownloadHandlerScript scriptDownloadHandler => new Response(webRequest.url, webRequest.method, requestBody, false, scriptDownloadHandler.text, scriptDownloadHandler.data, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}"), - _ => new Response(webRequest.url, webRequest.method, requestBody, false, webRequest.responseCode == 401 ? "Invalid Credentials" : webRequest.downloadHandler?.text, webRequest.downloadHandler?.data, webRequest.responseCode, responseHeaders, parameters, $"{webRequest.error}\n{webRequest.downloadHandler?.error}") - }; + return new Response(webRequest, requestBody, false, parameters); } - if (serverSentEventCallback != null) + if (serverSentEventHandler != null) { - SendServerEventCallback(true); + SendServerEventCallback(true, requestBody); } - return webRequest.downloadHandler switch - { - DownloadHandlerFile => new Response(webRequest.url, webRequest.method, requestBody, true, null, null, webRequest.responseCode, responseHeaders, parameters), - DownloadHandlerTexture => new Response(webRequest.url, webRequest.method, requestBody, true, null, null, webRequest.responseCode, responseHeaders, parameters), - DownloadHandlerAudioClip => new Response(webRequest.url, webRequest.method, requestBody, true, null, null, webRequest.responseCode, responseHeaders, parameters), - DownloadHandlerAssetBundle => new Response(webRequest.url, webRequest.method, requestBody, true, null, null, webRequest.responseCode, responseHeaders, parameters), - DownloadHandlerBuffer bufferDownloadHandler => new Response(webRequest.url, webRequest.method, requestBody, true, bufferDownloadHandler.text, bufferDownloadHandler.data, webRequest.responseCode, responseHeaders, parameters), - DownloadHandlerScript scriptDownloadHandler => new Response(webRequest.url, webRequest.method, requestBody, true, scriptDownloadHandler.text, scriptDownloadHandler.data, webRequest.responseCode, responseHeaders, parameters), - _ => new Response(webRequest.url, webRequest.method, requestBody, true, webRequest.downloadHandler?.text, webRequest.downloadHandler?.data, webRequest.responseCode, responseHeaders, parameters) - }; + return new Response(webRequest, requestBody, true, parameters); - void SendServerEventCallback(bool isEnd) + void SendServerEventCallback(bool isEnd, string body) { var allEventMessages = webRequest.downloadHandler?.text; if (string.IsNullOrWhiteSpace(allEventMessages)) { return; } @@ -1255,51 +1292,53 @@ void SendServerEventCallback(bool isEnd) for (var i = parameters.ServerSentEventCount; i < matches.Count - stride; i++) { - string type; + ServerSentEventKind type; string value; string data; var match = matches[i]; - const string comment = nameof(comment); - type = match.Groups[nameof(type)].Value.Trim(); // If the field type is not provided, treat it as a comment - type = string.IsNullOrEmpty(type) ? comment : type; - value = match.Groups[nameof(value)].Value.Trim(); - data = match.Groups[nameof(data)].Value.Trim(); + type = ServerSentEvent.EventMap.GetValueOrDefault(match.Groups[nameof(type)].Value.Trim(), ServerSentEventKind.Comment); + // The UTF-8 decode algorithm strips one leading UTF-8 Byte Order Mark (BOM), if any. + value = match.Groups[nameof(value)].Value.TrimStart(' '); + data = match.Groups[nameof(data)].Value; - if ((type.Equals("event") && value.Equals("done") && data.Equals("[DONE]")) || - (type.Equals("data") && value.Equals("[DONE]"))) - { - return; - } + const string doneTag = "[DONE]"; + // if either value or data equals doneTag then stop processing events. + if (value.Equals(doneTag) || data.Equals(doneTag)) { return; } - var eventObject = new Dictionary(); + var @event = new ServerSentEvent(type); try { - eventObject[type] = JToken.Parse(value); + @event.Value = JToken.Parse(value); } catch { - eventObject[type] = value; + @event.Value = new JValue(value); } if (!string.IsNullOrWhiteSpace(data)) { try { - eventObject[nameof(data)] = JToken.Parse(data); + @event.Data = JToken.Parse(data); } catch { - eventObject[nameof(data)] = data; + @event.Data = string.IsNullOrWhiteSpace(data) ? null : new JValue(value); } } + else + { + @event.Data = null; + } - serverSentEventCallback.Invoke(JsonConvert.SerializeObject(eventObject)); + var sseResponse = new Response(webRequest, body, true, parameters, (@event.Data ?? @event.Value).ToString(Formatting.None)); + serverSentEventHandler.Invoke(sseResponse, @event); parameters.ServerSentEventCount++; - parameters.ServerSentEvents.Add(new Tuple(type, value, data)); + parameters.ServerSentEvents.Add(@event); } } } diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestException.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestException.cs index a41188d..7de7e71 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestException.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestException.cs @@ -4,7 +4,7 @@ namespace Utilities.WebRequestRest { - public class RestException : Exception + public sealed class RestException : Exception { public RestException(Response response, string message = null, Exception innerException = null) : base(string.IsNullOrWhiteSpace(message) ? response.ToString() : message, innerException) diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestParameters.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestParameters.cs index 18cb116..0d82053 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestParameters.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/RestParameters.cs @@ -9,7 +9,7 @@ namespace Utilities.WebRequestRest /// /// A common class for restful parameters /// - public class RestParameters + public sealed class RestParameters { /// /// Constructor. @@ -85,7 +85,8 @@ public RestParameters( internal int ServerSentEventCount { get; set; } - internal readonly List> ServerSentEvents = new(); + // ReSharper disable once InconsistentNaming + internal readonly List ServerSentEvents = new(); /// /// Cache downloaded content.
diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs new file mode 100644 index 0000000..be4955e --- /dev/null +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs @@ -0,0 +1,51 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Text; +using UnityEngine.Scripting; + +namespace Utilities.WebRequestRest +{ + [Preserve] + public sealed class ServerSentEvent + { + [Preserve] + internal static readonly Dictionary EventMap = new() + { + { "comment", ServerSentEventKind.Comment }, + { "event", ServerSentEventKind.Event }, + { "data", ServerSentEventKind.Data }, + { "id", ServerSentEventKind.Id }, + { "retry", ServerSentEventKind.Retry }, + }; + + [Preserve] + internal ServerSentEvent(ServerSentEventKind @event) => Event = @event; + + [Preserve] + public ServerSentEventKind Event { get; } + + [Preserve] + public JToken Value { get; internal set; } + + [Preserve] + public JToken Data { get; internal set; } + + [Preserve] + public override string ToString() + { + var stringBuilder = new StringBuilder(); + stringBuilder.Append($"{{\"{Event.ToString().ToLower()}\": {Value.ToString(Formatting.None)}"); + + if (Data != null) + { + stringBuilder.Append($", \"data\": {Data.ToString(Formatting.None)}"); + } + + stringBuilder.Append('}'); + return stringBuilder.ToString(); + } + } +} diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs.meta b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs.meta new file mode 100644 index 0000000..252914d --- /dev/null +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9915a3a426d3e6940a70d8fb198953c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs new file mode 100644 index 0000000..4d6a669 --- /dev/null +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs @@ -0,0 +1,13 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Utilities.WebRequestRest +{ + public enum ServerSentEventKind + { + Comment, + Event, + Data, + Id, + Retry, + } +} diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs.meta b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs.meta new file mode 100644 index 0000000..89aa70f --- /dev/null +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/ServerSentEventKind.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3e780bd9ee5d8d479e2fbaccab69e0a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Utilities.Rest/Packages/com.utilities.rest/package.json b/Utilities.Rest/Packages/com.utilities.rest/package.json index 868b4b7..2ecaa51 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/package.json +++ b/Utilities.Rest/Packages/com.utilities.rest/package.json @@ -3,7 +3,7 @@ "displayName": "Utilities.Rest", "description": "This package contains useful RESTful utilities for the Unity Game Engine.", "keywords": [], - "version": "3.0.1", + "version": "3.1.0", "unity": "2021.3", "documentationUrl": "https://github.com/RageAgainstThePixel/com.utilities.rest#documentation", "changelogUrl": "https://github.com/RageAgainstThePixel/com.utilities.rest/releases",