From 847f1804a1fd6ee28970e721a5b649c7f41c8104 Mon Sep 17 00:00:00 2001 From: George Ndungu Date: Tue, 19 May 2020 01:27:26 +0300 Subject: [PATCH 1/5] Hook into Signal infrastructure to capture HTTP Request and Responses for logging purposes when Debug is specified. On Module init, hook EventHandler into EventListener delegate. --- tools/Custom/EventHelper.cs | 93 +++++++ tools/Custom/HttpMessageFormatter.cs | 356 +++++++++++++++++++++++++++ tools/Custom/Module.cs | 40 +++ 3 files changed, 489 insertions(+) create mode 100644 tools/Custom/EventHelper.cs create mode 100644 tools/Custom/HttpMessageFormatter.cs diff --git a/tools/Custom/EventHelper.cs b/tools/Custom/EventHelper.cs new file mode 100644 index 00000000000..15d311bc482 --- /dev/null +++ b/tools/Custom/EventHelper.cs @@ -0,0 +1,93 @@ +using System; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Graph.PowerShell.Runtime; + +namespace Microsoft.Graph.PowerShell +{ + public static class EventHelper + { + /// + /// Create a tracing event containing a string message + /// + /// The string message to include in event data + /// Valid EventData containign the message + public static EventData CreateLogEvent(Task message) + { + return new EventData + { + Id = Guid.NewGuid().ToString(), + Message = message.Result + }; + } + + /// + /// Create a new debug message event + /// + /// The message + /// An event cotnainng the debug message + public static EventData CreateDebugEvent(string message) + { + return new EventData + { + Id = Events.Debug, + Message = message + }; + } + + /// + /// Create a new debug message event + /// + /// The message + /// An event cotnainng the debug message + public static EventData CreateWarningEvent(string message) + { + return new EventData + { + Id = Events.Warning, + Message = message + }; + } + + /// + /// Print event details to the provided stream + /// + /// The event data to print + /// The delegate for signaling events to the runtime + /// The cancellation token for the request + /// The name of the stream to print data to + /// The name of the event to be printed + public static async void Print(this Func getEventData, Func, Task> signal, CancellationToken token, string streamName, string eventName) + { + var eventDisplayName = SplitPascalCase(eventName).ToUpperInvariant(); + var data = EventDataConverter.ConvertFrom(getEventData()); // also, we manually use our TypeConverter to return an appropriate type + if (data.Id != "Verbose" && data.Id != "Warning" && data.Id != "Debug" && data.Id != "Information" && data.Id != "Error") + { + await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} The contents are '{data?.Id}' and '{data?.Message}'")); + if (data != null) + { + await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} Parameter: '{data.Parameter}'\n{eventDisplayName} RequestMessage '{data.RequestMessage}'\n{eventDisplayName} Response: '{data.ResponseMessage}'\n{eventDisplayName} Value: '{data.Value}'")); + await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} ExtendedData Type: '{data.ExtendedData?.GetType()}'\n{eventDisplayName} ExtendedData '{data.ExtendedData}'")); + } + } + } + + static string SplitPascalCase(string word) + { + var regex = new Regex("([a-z]+)([A-Z])"); + var output = regex.Replace(word, "$1 $2"); + regex = new Regex("([A-Z])([A-Z][a-z])"); + return regex.Replace(output, "$1 $2"); + } + + public static EventArgs CreateLogEvent(string message) + { + return new EventData + { + Id = Guid.NewGuid().ToString(), + Message = message + }; + } + } +} \ No newline at end of file diff --git a/tools/Custom/HttpMessageFormatter.cs b/tools/Custom/HttpMessageFormatter.cs new file mode 100644 index 00000000000..3330f49733e --- /dev/null +++ b/tools/Custom/HttpMessageFormatter.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Graph.PowerShell +{ + /// + /// Derived class which can encapsulate an + /// or an as an entity with media type "application/http". + /// + internal class HttpMessageFormatter : HttpContent + { + private const string SP = " "; + private const string ColonSP = ": "; + private const string CRLF = "\r\n"; + private const string CommaSeparator = ", "; + + private const int DefaultHeaderAllocation = 2 * 1024; + + private const string DefaultMediaType = "application/http"; + + private const string MsgTypeParameter = "msgtype"; + private const string DefaultRequestMsgType = "request"; + private const string DefaultResponseMsgType = "response"; + + // Set of header fields that only support single values such as Set-Cookie. + private static readonly HashSet _singleValueHeaderFields = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Cookie", + "Set-Cookie", + "X-Powered-By", + }; + + // Set of header fields that should get serialized as space-separated values such as User-Agent. + private static readonly HashSet _spaceSeparatedValueHeaderFields = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "User-Agent", + }; + + // Set of header fields that should not get serialized + private static readonly HashSet _neverSerializedHeaderFields = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "SdkVersion", + "FeatureFlag", + "Authorization", + "Cache-Control", + "Transfer-Encoding", + "Duration", + "Strict-Transport-Security", + "Date" + }; + + private bool _contentConsumed; + private Lazy> _streamTask; + + /// + /// Initializes a new instance of the class encapsulating an + /// . + /// + /// The instance to encapsulate. + public HttpMessageFormatter(HttpRequestMessage httpRequest) + { + HttpRequestMessage = httpRequest ?? throw new ArgumentNullException("httpRequest"); + Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType); + Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultRequestMsgType)); + + InitializeStreamTask(); + } + + /// + /// Initializes a new instance of the class encapsulating an + /// . + /// + /// The instance to encapsulate. + public HttpMessageFormatter(HttpResponseMessage httpResponse) + { + HttpResponseMessage = httpResponse ?? throw new ArgumentNullException("httpResponse"); + Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType); + Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultResponseMsgType)); + + InitializeStreamTask(); + } + + private HttpContent Content + { + get { return HttpRequestMessage != null ? HttpRequestMessage.Content : HttpResponseMessage.Content; } + } + + /// + /// Gets the HTTP request message. + /// + public HttpRequestMessage HttpRequestMessage { get; private set; } + + /// + /// Gets the HTTP response message. + /// + public HttpResponseMessage HttpResponseMessage { get; private set; } + + private void InitializeStreamTask() + { + _streamTask = new Lazy>(() => Content?.ReadAsStreamAsync()); + } + + /// + /// Validates whether the content contains an HTTP Request or an HTTP Response. + /// + /// The content to validate. + /// if set to true if the content is either an HTTP Request or an HTTP Response. + /// Indicates whether validation failure should result in an or not. + /// true if content is either an HTTP Request or an HTTP Response + internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequest, bool throwOnError) + { + if (content == null) + { + throw new ArgumentNullException("content"); + } + + MediaTypeHeaderValue contentType = content.Headers.ContentType; + if (contentType != null) + { + if (!contentType.MediaType.Equals(DefaultMediaType, StringComparison.OrdinalIgnoreCase)) + { + if (throwOnError) + { + throw new ArgumentException("HttpMessageInvalidMediaType", "content"); + } + else + { + return false; + } + } + + foreach (NameValueHeaderValue parameter in contentType.Parameters) + { + if (parameter.Name.Equals(MsgTypeParameter, StringComparison.OrdinalIgnoreCase)) + { + string msgType = UnquoteToken(parameter.Value); + if (!msgType.Equals(isRequest ? DefaultRequestMsgType : DefaultResponseMsgType, StringComparison.OrdinalIgnoreCase)) + { + if (throwOnError) + { + throw new ArgumentException("HttpMessageInvalidMediaType", "content"); + } + else + { + return false; + } + } + + return true; + } + } + } + + if (throwOnError) + { + throw new ArgumentException("HttpMessageInvalidMediaType", "content"); + } + else + { + return false; + } + } + + + public static string UnquoteToken(string token) + { + if (String.IsNullOrWhiteSpace(token)) + { + return token; + } + + if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) + { + return token.Substring(1, token.Length - 2); + } + + return token; + } + + + /// + /// Asynchronously serializes the object's content to the given . + /// + /// The to which to write. + /// The associated . + /// A instance that is asynchronously serializing the object's content. + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + byte[] header = SerializeHeader(); + await stream.WriteAsync(header, 0, header.Length); + + if (Content != null) + { + Stream readStream = await _streamTask.Value; + ValidateStreamForReading(readStream); + await Content.CopyToAsync(stream); + } + } + + /// + /// Computes the length of the stream if possible. + /// + /// The computed length of the stream. + /// true if the length has been computed; otherwise false. + protected override bool TryComputeLength(out long length) + { + // We have four states we could be in: + // 1. We have content, but the task is still running or finished without success + // 2. We have content, the task has finished successfully, and the stream came back as a null or non-seekable + // 3. We have content, the task has finished successfully, and the stream is seekable, so we know its length + // 4. We don't have content (streamTask.Value == null) + // + // For #1 and #2, we return false. + // For #3, we return true & the size of our headers + the content length + // For #4, we return true & the size of our headers + + bool hasContent = _streamTask.Value != null; + length = 0; + + // Cases #1, #2, #3 + // We serialize header to a StringBuilder so that we can determine the length + // following the pattern for HttpContent to try and determine the message length. + // The perf overhead is no larger than for the other HttpContent implementations. + byte[] header = SerializeHeader(); + length += header.Length; + return true; + } + + /// + /// Serializes the HTTP request line. + /// + /// Where to write the request line. + /// The HTTP request. + private static void SerializeRequestLine(StringBuilder message, HttpRequestMessage httpRequest) + { + Contract.Assert(message != null, "message cannot be null"); + message.Append(httpRequest.Method + SP); + message.Append(httpRequest.RequestUri.PathAndQuery + SP); + message.Append($"HTTP/{(httpRequest.Version != null ? httpRequest.Version.ToString(2) : "1.1")}{CRLF}"); + + // Only insert host header if not already present. + if (httpRequest.Headers.Host == null) + { + message.Append($"HTTP{ColonSP}{httpRequest.RequestUri.Authority}{CRLF}"); + } + } + + /// + /// Serializes the HTTP status line. + /// + /// Where to write the status line. + /// The HTTP response. + private static void SerializeStatusLine(StringBuilder message, HttpResponseMessage httpResponse) + { + Contract.Assert(message != null, "message cannot be null"); + message.Append($"HTTP/{(httpResponse.Version != null ? httpResponse.Version.ToString(2) : "1.1")}{SP}"); + message.Append((int)httpResponse.StatusCode + SP); + message.Append(httpResponse.ReasonPhrase + CRLF); + } + + /// + /// Serializes the header fields. + /// + /// Where to write the status line. + /// The headers to write. + private static void SerializeHeaderFields(StringBuilder message, HttpHeaders headers) + { + Contract.Assert(message != null, "message cannot be null"); + if (headers != null) + { + foreach (KeyValuePair> header in headers) + { + if (_neverSerializedHeaderFields.Contains(header.Key)) + { + continue; + } + if (_singleValueHeaderFields.Contains(header.Key)) + { + foreach (string value in header.Value) + { + message.Append(header.Key + ColonSP + value + CRLF); + } + } + else if (_spaceSeparatedValueHeaderFields.Contains(header.Key)) + { + message.Append(header.Key + ColonSP + String.Join(SP, header.Value) + CRLF); + } + else + { + message.Append(header.Key + ColonSP + String.Join(CommaSeparator, header.Value) + CRLF); + } + } + } + } + + private byte[] SerializeHeader() + { + StringBuilder message = new StringBuilder(DefaultHeaderAllocation); + HttpHeaders headers; + HttpContent content; + if (HttpRequestMessage != null) + { + SerializeRequestLine(message, HttpRequestMessage); + headers = HttpRequestMessage.Headers; + content = HttpRequestMessage.Content; + } + else + { + SerializeStatusLine(message, HttpResponseMessage); + headers = HttpResponseMessage.Headers; + content = HttpResponseMessage.Content; + } + + SerializeHeaderFields(message, headers); + if (content != null) + { + SerializeHeaderFields(message, content.Headers); + } + + message.Append(CRLF); + return Encoding.UTF8.GetBytes(message.ToString()); + } + + private void ValidateStreamForReading(Stream stream) + { + // If the content needs to be written to a target stream a 2nd time, then the stream must support + // seeking (e.g. a FileStream), otherwise the stream can't be copied a second time to a target + // stream (e.g. a NetworkStream). + if (_contentConsumed) + { + if (stream != null && stream.CanRead) + { + stream.Position = 0; + } + else + { + throw new InvalidOperationException("HttpMessageContentAlreadyRead"); + } + } + + _contentConsumed = true; + } + } +} \ No newline at end of file diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs index 4a0e0f6ebcc..d8289557c54 100644 --- a/tools/Custom/Module.cs +++ b/tools/Custom/Module.cs @@ -1,11 +1,19 @@ // ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Graph.PowerShell.Runtime; + namespace Microsoft.Graph.PowerShell { using Microsoft.Graph.PowerShell.Authentication; using Microsoft.Graph.PowerShell.Authentication.Helpers; using Microsoft.Graph.PowerShell.Authentication.Models; + using System; using System.Linq; using System.Management.Automation; @@ -17,5 +25,37 @@ partial void BeforeCreatePipeline(System.Management.Automation.InvocationInfo in { pipeline = new Runtime.HttpPipeline(new Runtime.HttpClientFactory(HttpHelpers.GetGraphHttpClient())); } + partial void CustomInit() + { + this.EventListener = EventHandler; + } + public async Task EventHandler(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal, InvocationInfo invocationInfo, string parameterSetName, System.Exception exception) + { + switch (id) + { + case Events.Finally: + await Finally(id, cancellationToken, getEventData, signal); + break; + default: + getEventData.Print(signal, cancellationToken, Events.Information, id); + break; + } + } + + private async Task Finally(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal) + { + using (Extensions.NoSynchronizationContext) + { + var eventData = EventDataConverter.ConvertFrom(getEventData()); + using (var requestFormatter = new HttpMessageFormatter(eventData.RequestMessage as HttpRequestMessage)) + using (var responseFormatter = new HttpMessageFormatter(eventData.ResponseMessage as HttpResponseMessage)) + { + var requestString = await requestFormatter.ReadAsStringAsync(); + var responseString = await responseFormatter.ReadAsStringAsync(); + await signal(Events.Debug, cancellationToken, () => EventHelper.CreateLogEvent(requestString)); + await signal(Events.Debug, cancellationToken, () => EventHelper.CreateLogEvent(responseString)); + } + } + } } } From 49cbde9cf768cb6662e6a2129a1ff8067d61dbf1 Mon Sep 17 00:00:00 2001 From: George Ndungu Date: Wed, 24 Jun 2020 21:51:58 +0300 Subject: [PATCH 2/5] Explicitly call Init before pipeline creation. --- tools/Custom/Module.cs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs index d8289557c54..d13f9726546 100644 --- a/tools/Custom/Module.cs +++ b/tools/Custom/Module.cs @@ -23,12 +23,33 @@ public partial class Module { partial void BeforeCreatePipeline(System.Management.Automation.InvocationInfo invocationInfo, ref Runtime.HttpPipeline pipeline) { + // Call Init to trigger any custom initialization needed after + // module load and before pipeline is setup and used. + Init(); pipeline = new Runtime.HttpPipeline(new Runtime.HttpClientFactory(HttpHelpers.GetGraphHttpClient())); } + + /// + /// Any needed Custom Initialization. + /// partial void CustomInit() { this.EventListener = EventHandler; } + + /// + /// Common Module Event Listener, allows to handle emitted by CmdLets + /// + /// The ID of the event + /// The cancellation token for the event + /// A delegate to get the detailed event data + /// The callback for the event dispatcher + /// The from the cmdlet + /// The cmdlet's parameterset name + /// the exception that is being thrown (if available) + /// + /// A that will be complete when handling of the event is completed. + /// public async Task EventHandler(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal, InvocationInfo invocationInfo, string parameterSetName, System.Exception exception) { switch (id) @@ -42,6 +63,16 @@ public async Task EventHandler(string id, CancellationToken cancellationToken, F } } + /// + /// Handles the Finally event, which is called just before Request and Response are disposed. + /// + /// The ID of the event + /// The cancellation token for the event + /// A delegate to get the detailed event data + /// The callback for the event dispatcher + /// + /// A that will be complete when handling of the event is completed. + /// private async Task Finally(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal) { using (Extensions.NoSynchronizationContext) From 83b4ec87e8fa31e88c11ca68e4b2f9d7335ed2b9 Mon Sep 17 00:00:00 2001 From: George Date: Sun, 12 Jul 2020 02:59:23 +0300 Subject: [PATCH 3/5] Add copyright header --- tools/Custom/EventHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/Custom/EventHelper.cs b/tools/Custom/EventHelper.cs index 15d311bc482..41d326ffabc 100644 --- a/tools/Custom/EventHelper.cs +++ b/tools/Custom/EventHelper.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + using System; using System.Text.RegularExpressions; using System.Threading; From ec4e2cb606e0035ba4c6187e4d2e25c4b1a611ce Mon Sep 17 00:00:00 2001 From: George Date: Sun, 12 Jul 2020 03:48:14 +0300 Subject: [PATCH 4/5] Rename EventHelper to EventFactory. Move print method to EventExtentions. --- tools/Custom/EventExtensions.cs | 37 +++++++++++++++++++ .../{EventHelper.cs => EventFactory.cs} | 26 +------------ 2 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 tools/Custom/EventExtensions.cs rename tools/Custom/{EventHelper.cs => EventFactory.cs} (55%) diff --git a/tools/Custom/EventExtensions.cs b/tools/Custom/EventExtensions.cs new file mode 100644 index 00000000000..f1c7e503774 --- /dev/null +++ b/tools/Custom/EventExtensions.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ +namespace Microsoft.Graph.PowerShell +{ + using System; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Graph.PowerShell.Runtime; + + public static class EventExtensions + { + /// + /// Print event details to the provided stream + /// + /// The event data to print + /// The delegate for signaling events to the runtime + /// The cancellation token for the request + /// The name of the stream to print data to + /// The name of the event to be printed + public static async void Print(this Func getEventData, Func, Task> signal, CancellationToken token, string streamName, string eventName) + { + var eventDisplayName = SplitPascalCase(eventName).ToUpperInvariant(); + var data = EventDataConverter.ConvertFrom(getEventData()); // also, we manually use our TypeConverter to return an appropriate type + if (data.Id != "Verbose" && data.Id != "Warning" && data.Id != "Debug" && data.Id != "Information" && data.Id != "Error") + { + await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} The contents are '{data?.Id}' and '{data?.Message}'")); + if (data != null) + { + await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} Parameter: '{data.Parameter}'\n{eventDisplayName} RequestMessage '{data.RequestMessage}'\n{eventDisplayName} Response: '{data.ResponseMessage}'\n{eventDisplayName} Value: '{data.Value}'")); + await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} ExtendedData Type: '{data.ExtendedData?.GetType()}'\n{eventDisplayName} ExtendedData '{data.ExtendedData}'")); + } + } + } + } +} \ No newline at end of file diff --git a/tools/Custom/EventHelper.cs b/tools/Custom/EventFactory.cs similarity index 55% rename from tools/Custom/EventHelper.cs rename to tools/Custom/EventFactory.cs index 41d326ffabc..6002d397b1b 100644 --- a/tools/Custom/EventHelper.cs +++ b/tools/Custom/EventFactory.cs @@ -10,7 +10,7 @@ namespace Microsoft.Graph.PowerShell { - public static class EventHelper + public static class EventFactory { /// /// Create a tracing event containing a string message @@ -53,30 +53,6 @@ public static EventData CreateWarningEvent(string message) Message = message }; } - - /// - /// Print event details to the provided stream - /// - /// The event data to print - /// The delegate for signaling events to the runtime - /// The cancellation token for the request - /// The name of the stream to print data to - /// The name of the event to be printed - public static async void Print(this Func getEventData, Func, Task> signal, CancellationToken token, string streamName, string eventName) - { - var eventDisplayName = SplitPascalCase(eventName).ToUpperInvariant(); - var data = EventDataConverter.ConvertFrom(getEventData()); // also, we manually use our TypeConverter to return an appropriate type - if (data.Id != "Verbose" && data.Id != "Warning" && data.Id != "Debug" && data.Id != "Information" && data.Id != "Error") - { - await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} The contents are '{data?.Id}' and '{data?.Message}'")); - if (data != null) - { - await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} Parameter: '{data.Parameter}'\n{eventDisplayName} RequestMessage '{data.RequestMessage}'\n{eventDisplayName} Response: '{data.ResponseMessage}'\n{eventDisplayName} Value: '{data.Value}'")); - await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} ExtendedData Type: '{data.ExtendedData?.GetType()}'\n{eventDisplayName} ExtendedData '{data.ExtendedData}'")); - } - } - } - static string SplitPascalCase(string word) { var regex = new Regex("([a-z]+)([A-Z])"); From 9afdcb7b16dc4475d9bb77df360634bf0c2e8f77 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 14 Jul 2020 05:37:50 +0300 Subject: [PATCH 5/5] correct typos in comments. use nameof in exceptions. add appropriate license headers. --- tools/Custom/EventExtensions.cs | 11 ++++++----- tools/Custom/EventFactory.cs | 8 ++++---- tools/Custom/HttpMessageFormatter.cs | 18 +++++++++++------- tools/Custom/Module.cs | 4 ++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/tools/Custom/EventExtensions.cs b/tools/Custom/EventExtensions.cs index f1c7e503774..df05433e553 100644 --- a/tools/Custom/EventExtensions.cs +++ b/tools/Custom/EventExtensions.cs @@ -1,6 +1,7 @@ // ------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ + namespace Microsoft.Graph.PowerShell { using System; @@ -21,15 +22,15 @@ public static class EventExtensions /// The name of the event to be printed public static async void Print(this Func getEventData, Func, Task> signal, CancellationToken token, string streamName, string eventName) { - var eventDisplayName = SplitPascalCase(eventName).ToUpperInvariant(); + var eventDisplayName = EventFactory.SplitPascalCase(eventName).ToUpperInvariant(); var data = EventDataConverter.ConvertFrom(getEventData()); // also, we manually use our TypeConverter to return an appropriate type - if (data.Id != "Verbose" && data.Id != "Warning" && data.Id != "Debug" && data.Id != "Information" && data.Id != "Error") + if (data.Id != Events.Verbose && data.Id != Events.Warning && data.Id != Events.Debug && data.Id != Events.Information && data.Id != Events.Error) { - await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} The contents are '{data?.Id}' and '{data?.Message}'")); + await signal(streamName, token, () => EventFactory.CreateLogEvent($"{eventDisplayName} The contents are '{data?.Id}' and '{data?.Message}'")); if (data != null) { - await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} Parameter: '{data.Parameter}'\n{eventDisplayName} RequestMessage '{data.RequestMessage}'\n{eventDisplayName} Response: '{data.ResponseMessage}'\n{eventDisplayName} Value: '{data.Value}'")); - await signal(streamName, token, () => EventHelper.CreateLogEvent($"{eventDisplayName} ExtendedData Type: '{data.ExtendedData?.GetType()}'\n{eventDisplayName} ExtendedData '{data.ExtendedData}'")); + await signal(streamName, token, () => EventFactory.CreateLogEvent($"{eventDisplayName} Parameter: '{data.Parameter}'\n{eventDisplayName} RequestMessage '{data.RequestMessage}'\n{eventDisplayName} Response: '{data.ResponseMessage}'\n{eventDisplayName} Value: '{data.Value}'")); + await signal(streamName, token, () => EventFactory.CreateLogEvent($"{eventDisplayName} ExtendedData Type: '{data.ExtendedData?.GetType()}'\n{eventDisplayName} ExtendedData '{data.ExtendedData}'")); } } } diff --git a/tools/Custom/EventFactory.cs b/tools/Custom/EventFactory.cs index 6002d397b1b..2e2966de6e8 100644 --- a/tools/Custom/EventFactory.cs +++ b/tools/Custom/EventFactory.cs @@ -16,7 +16,7 @@ public static class EventFactory /// Create a tracing event containing a string message /// /// The string message to include in event data - /// Valid EventData containign the message + /// Valid EventData containing the message public static EventData CreateLogEvent(Task message) { return new EventData @@ -30,7 +30,7 @@ public static EventData CreateLogEvent(Task message) /// Create a new debug message event /// /// The message - /// An event cotnainng the debug message + /// An event containing the debug message public static EventData CreateDebugEvent(string message) { return new EventData @@ -44,7 +44,7 @@ public static EventData CreateDebugEvent(string message) /// Create a new debug message event /// /// The message - /// An event cotnainng the debug message + /// An event containing the debug message public static EventData CreateWarningEvent(string message) { return new EventData @@ -53,7 +53,7 @@ public static EventData CreateWarningEvent(string message) Message = message }; } - static string SplitPascalCase(string word) + public static string SplitPascalCase(string word) { var regex = new Regex("([a-z]+)([A-Z])"); var output = regex.Replace(word, "$1 $2"); diff --git a/tools/Custom/HttpMessageFormatter.cs b/tools/Custom/HttpMessageFormatter.cs index 3330f49733e..8bd25ea08a8 100644 --- a/tools/Custom/HttpMessageFormatter.cs +++ b/tools/Custom/HttpMessageFormatter.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + using System; using System.Collections.Generic; using System.Diagnostics.Contracts; @@ -66,7 +70,7 @@ internal class HttpMessageFormatter : HttpContent /// The instance to encapsulate. public HttpMessageFormatter(HttpRequestMessage httpRequest) { - HttpRequestMessage = httpRequest ?? throw new ArgumentNullException("httpRequest"); + HttpRequestMessage = httpRequest ?? throw new ArgumentNullException(nameof(httpRequest)); Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType); Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultRequestMsgType)); @@ -80,7 +84,7 @@ public HttpMessageFormatter(HttpRequestMessage httpRequest) /// The instance to encapsulate. public HttpMessageFormatter(HttpResponseMessage httpResponse) { - HttpResponseMessage = httpResponse ?? throw new ArgumentNullException("httpResponse"); + HttpResponseMessage = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse)); Headers.ContentType = new MediaTypeHeaderValue(DefaultMediaType); Headers.ContentType.Parameters.Add(new NameValueHeaderValue(MsgTypeParameter, DefaultResponseMsgType)); @@ -118,7 +122,7 @@ internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequ { if (content == null) { - throw new ArgumentNullException("content"); + throw new ArgumentNullException(nameof(content)); } MediaTypeHeaderValue contentType = content.Headers.ContentType; @@ -128,7 +132,7 @@ internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequ { if (throwOnError) { - throw new ArgumentException("HttpMessageInvalidMediaType", "content"); + throw new ArgumentException("HttpMessageInvalidMediaType", nameof(content)); } else { @@ -145,7 +149,7 @@ internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequ { if (throwOnError) { - throw new ArgumentException("HttpMessageInvalidMediaType", "content"); + throw new ArgumentException("HttpMessageInvalidMediaType", nameof(content)); } else { @@ -160,7 +164,7 @@ internal static bool ValidateHttpMessageContent(HttpContent content, bool isRequ if (throwOnError) { - throw new ArgumentException("HttpMessageInvalidMediaType", "content"); + throw new ArgumentException("HttpMessageInvalidMediaType", nameof(content)); } else { @@ -195,7 +199,7 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon { if (stream == null) { - throw new ArgumentNullException("stream"); + throw new ArgumentNullException(nameof(stream)); } byte[] header = SerializeHeader(); diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs index 853f2dc9822..4766b09682c 100644 --- a/tools/Custom/Module.cs +++ b/tools/Custom/Module.cs @@ -87,8 +87,8 @@ private async Task Finally(string id, CancellationToken cancellationToken, Func< { var requestString = await requestFormatter.ReadAsStringAsync(); var responseString = await responseFormatter.ReadAsStringAsync(); - await signal(Events.Debug, cancellationToken, () => EventHelper.CreateLogEvent(requestString)); - await signal(Events.Debug, cancellationToken, () => EventHelper.CreateLogEvent(responseString)); + await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(requestString)); + await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(responseString)); } } }