private/runtime/EventListener.cs
namespace Microsoft.Rest.ClientRuntime { using System; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using GetEventData = System.Func<EventData>; using static Microsoft.Rest.ClientRuntime.Extensions; public interface IValidates { Task Validate(Microsoft.Rest.ClientRuntime.IEventListener listener); } /// <summary> /// The IEventListener Interface defines the communication mechanism for Signaling events during a remote call. /// </summary> /// <remarks> /// The interface is designed to be as minimal as possible, allow for quick peeking of the event type (<c>id</c>) /// and the cancellation status and provides a delegate for retrieving the event details themselves. /// </remarks> public interface IEventListener { Task Signal(string id, CancellationToken token, GetEventData createMessage); CancellationToken Token { get; } System.Action Cancel { get; } } public static partial class Extensions { public static Task Signal(this IEventListener instance, string id, CancellationToken token, Func<EventData> createMessage) => instance.Signal(id, token, createMessage); public static Task Signal(this IEventListener instance, string id, CancellationToken token) => instance.Signal(id, token, () => new EventData { Id = id, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, string messageText) => instance.Signal(id, token, () => new EventData { Id = id, Message = messageText, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, string messageText, HttpRequestMessage request) => instance.Signal(id, token, () => new EventData { Id = id, Message = messageText, RequestMessage = request, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, string messageText, HttpResponseMessage response) => instance.Signal(id, token, () => new EventData { Id = id, Message = messageText, RequestMessage = response.RequestMessage, ResponseMessage = response, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, string messageText, double magnitude) => instance.Signal(id, token, () => new EventData { Id = id, Message = messageText, Value = magnitude, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, string messageText, double magnitude, HttpRequestMessage request) => instance.Signal(id, token, () => new EventData { Id = id, Message = messageText, RequestMessage = request, Value = magnitude, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, string messageText, double magnitude, HttpResponseMessage response) => instance.Signal(id, token, () => new EventData { Id = id, Message = messageText, RequestMessage = response.RequestMessage, ResponseMessage = response, Value = magnitude, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, HttpRequestMessage request) => instance.Signal(id, token, () => new EventData { Id = id, RequestMessage = request, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, HttpRequestMessage request, HttpResponseMessage response) => instance.Signal(id, token, () => new EventData { Id = id, RequestMessage = request, ResponseMessage = response, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, HttpResponseMessage response) => instance.Signal(id, token, () => new EventData { Id = id, RequestMessage = response.RequestMessage, ResponseMessage = response, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, CancellationToken token, EventData message) => instance.Signal(id, token, () => { message.Id = id; message.Cancel = instance.Cancel; return message; }); public static Task Signal(this IEventListener instance, string id, Func<EventData> createMessage) => instance.Signal(id, instance.Token, createMessage); public static Task Signal(this IEventListener instance, string id) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, string messageText) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = messageText, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, string messageText, HttpRequestMessage request) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = messageText, RequestMessage = request, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, string messageText, HttpResponseMessage response) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = messageText, RequestMessage = response.RequestMessage, ResponseMessage = response, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, string messageText, double magnitude) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = messageText, Value = magnitude, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, string messageText, double magnitude, HttpRequestMessage request) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = messageText, RequestMessage = request, Value = magnitude, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, string messageText, double magnitude, HttpResponseMessage response) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = messageText, RequestMessage = response.RequestMessage, ResponseMessage = response, Value = magnitude, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, HttpRequestMessage request) => instance.Signal(id, instance.Token, () => new EventData { Id = id, RequestMessage = request, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, HttpRequestMessage request, HttpResponseMessage response) => instance.Signal(id, instance.Token, () => new EventData { Id = id, RequestMessage = request, ResponseMessage = response, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, HttpResponseMessage response) => instance.Signal(id, instance.Token, () => new EventData { Id = id, RequestMessage = response.RequestMessage, ResponseMessage = response, Cancel = instance.Cancel }); public static Task Signal(this IEventListener instance, string id, EventData message) => instance.Signal(id, instance.Token, () => { message.Id = id; message.Cancel = instance.Cancel; return message; }); public static Task Signal(this IEventListener instance, string id, System.Uri uri) => instance.Signal(id, instance.Token, () => new EventData { Id = id, Message = uri.ToString(), Cancel = instance.Cancel }); public static async Task AssertNotNull(this IEventListener instance, string parameterName, object value) { if (value == null) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"'{parameterName}' should not be null", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertMinimumLength(this IEventListener instance, string parameterName, string value, int length) { if (value != null && value.Length < length) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Length of '{parameterName}' is less than {length}", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertMaximumLength(this IEventListener instance, string parameterName, string value, int length) { if (value != null && value.Length > length) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Length of '{parameterName}' is greater than {length}", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertRegEx(this IEventListener instance, string parameterName, string value, string regularExpression) { if (value != null && !System.Text.RegularExpressions.Regex.Match(value, regularExpression).Success) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"'{parameterName}' does not validate against pattern /{regularExpression}/", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertEnum(this IEventListener instance, string parameterName, string value, params string[] values) { if (!values.Any(each => each.Equals(value))) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"'{parameterName}' is not one of ({values.Aggregate((c, e) => $"'{e}',{c}")}", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertObjectIsValid(this IEventListener instance, string parameterName, object inst) { await (inst as Microsoft.Rest.ClientRuntime.IValidates)?.Validate(instance); } public static async Task AssertIsLessThan<T>(this IEventListener instance, string parameterName, T? value, T max) where T : struct, System.IComparable<T> { if (null != value && ((T)value).CompareTo(max) >= 0) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be less than {max} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertIsGreaterThan<T>(this IEventListener instance, string parameterName, T? value, T max) where T : struct, System.IComparable<T> { if (null != value && ((T)value).CompareTo(max) <= 0) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be greater than {max} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertIsLessThanOrEqual<T>(this IEventListener instance, string parameterName, T? value, T max) where T : struct, System.IComparable<T> { if (null != value && ((T)value).CompareTo(max) > 0) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be less than or equal to {max} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertIsGreaterThanOrEqual<T>(this IEventListener instance, string parameterName, T? value, T max) where T : struct, System.IComparable<T> { if (null != value && ((T)value).CompareTo(max) < 0) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be greater than or equal to {max} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertIsMultipleOf(this IEventListener instance, string parameterName, Int64? value, Int64 multiple) { if (null != value && value % multiple != 0) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be multiple of {multiple} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } public static async Task AssertIsMultipleOf(this IEventListener instance, string parameterName, double? value, double multiple) { if (null != value) { var i = (Int64)(value / multiple); if (i != value / multiple) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be multiple of {multiple} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } } public static async Task AssertIsMultipleOf(this IEventListener instance, string parameterName, decimal? value, decimal multiple) { if (null != value) { var i = (Int64)(value / multiple); if (i != value / multiple) { await instance.Signal(Microsoft.Rest.ClientRuntime.Events.ValidationWarning, instance.Token, () => new EventData { Id = Microsoft.Rest.ClientRuntime.Events.ValidationWarning, Message = $"Value of '{parameterName}' must be multiple of {multiple} (value is {value})", Parameter = parameterName, Cancel = instance.Cancel }); } } } } /// <summary> /// An Implementation of the IEventListener that supports subscribing to events and dispatching them /// (used for manually using the lowlevel interface) /// </summary> public class EventListener : CancellationTokenSource, IEnumerable<KeyValuePair<string, Event>>, IEventListener { private Dictionary<string, Event> calls = new Dictionary<string, Event>(); public IEnumerator<KeyValuePair<string, Event>> GetEnumerator() => calls.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => calls.GetEnumerator(); public EventListener() { } public new Action Cancel => base.Cancel; private Event tracer; public EventListener(params (string name, Event callback)[] initializer) { foreach (var each in initializer) { Add(each.name, each.callback); } } public void Add(string name, SynchEvent callback) { Add(name, (message) => { callback(message); return Task.CompletedTask; }); } public void Add(string name, Event callback) { if (callback != null) { if (string.IsNullOrEmpty(name)) { if (calls.ContainsKey(name)) { tracer += callback; } else { tracer = callback; } } else { if (calls.ContainsKey(name)) { calls[name ?? System.String.Empty] += callback; } else { calls[name ?? System.String.Empty] = callback; } } } } public async Task Signal(string id, CancellationToken token, GetEventData createMessage) { using(NoSynchronizationContext) { if (!string.IsNullOrEmpty(id) && (calls.TryGetValue(id, out Event listener) || tracer != null)) { var message = createMessage(); message.Id = id; await listener?.Invoke(message); await tracer?.Invoke(message); if (token.IsCancellationRequested) { throw new OperationCanceledException($"Canceled by event {id} ", this.Token); } } } } } } |