From 3cc32ad11504b7f77c0a000f31ccf3d4e69cd03c Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Fri, 19 Apr 2019 12:07:30 +1000 Subject: [PATCH 1/4] Initial support for multiple query parameters. --- HTTPlease.sln | 19 +- .../Core/Utilities/UriHelper.cs | 77 +++--- .../Core/ValueProviders/IValueProvider.cs | 17 +- .../Core/ValueProviders/MultiValueProvider.cs | 237 ++++++++++++++++++ .../Core/ValueProviders/ValueProvider.cs | 75 ++++-- .../ValueProviders/ValueProviderCombiner.cs | 58 +++++ .../ValueProviders/ValueProviderExtensions.cs | 23 ++ src/HTTPlease.Core/HttpRequest.cs | 22 +- src/HTTPlease.Core/HttpRequestOfTContext.cs | 17 +- src/HTTPlease.Core/IHttpRequestProperties.cs | 19 +- .../RequestExtensions.QueryParameters.cs | 7 +- src/HTTPlease.Core/RequestHeaderExtensions.cs | 2 +- .../HTTPlease.Testability.Xunit.csproj | 2 +- .../BuildMessage/UntypedRequest.cs | 101 ++++++-- .../HTTPlease.Core.Tests.csproj | 12 +- .../HTTPlease.Diagnostics.Tests.csproj | 7 +- ...TTPlease.Formatters.FunctionalTests.csproj | 7 +- .../HTTPlease.Formatters.Tests.csproj | 7 +- .../HTTPlease.TestHarness.csproj | 18 ++ test/HTTPlease.TestHarness/Program.cs | 53 ++++ 20 files changed, 675 insertions(+), 105 deletions(-) create mode 100644 src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs create mode 100644 src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs create mode 100644 test/HTTPlease.TestHarness/HTTPlease.TestHarness.csproj create mode 100644 test/HTTPlease.TestHarness/Program.cs diff --git a/HTTPlease.sln b/HTTPlease.sln index 2099d31..99d9eeb 100644 --- a/HTTPlease.sln +++ b/HTTPlease.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2024 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28729.10 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E084508E-FBEB-4A72-9C38-C34E270E8C2B}" ProjectSection(SolutionItems) = preProject @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTTPlease.Diagnostics", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTTPlease.Diagnostics.Tests", "test\HTTPlease.Diagnostics.Tests\HTTPlease.Diagnostics.Tests.csproj", "{7252EB2F-2373-40CD-A7B6-B24919B974AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HTTPlease.TestHarness", "test\HTTPlease.TestHarness\HTTPlease.TestHarness.csproj", "{A0756A83-C331-4901-B13B-31010B14784E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -203,6 +205,18 @@ Global {7252EB2F-2373-40CD-A7B6-B24919B974AF}.Release|x64.Build.0 = Release|Any CPU {7252EB2F-2373-40CD-A7B6-B24919B974AF}.Release|x86.ActiveCfg = Release|Any CPU {7252EB2F-2373-40CD-A7B6-B24919B974AF}.Release|x86.Build.0 = Release|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Debug|x64.Build.0 = Debug|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Debug|x86.Build.0 = Debug|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Release|Any CPU.Build.0 = Release|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Release|x64.ActiveCfg = Release|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Release|x64.Build.0 = Release|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Release|x86.ActiveCfg = Release|Any CPU + {A0756A83-C331-4901-B13B-31010B14784E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -221,6 +235,7 @@ Global {B7DE2327-D1E3-4F30-8603-C7277A304A73} = {244D5A84-F66C-4628-A33A-5FBA3E9895B4} {5355EEF6-2E9D-452F-A3C8-4C9F38D39EF1} = {244D5A84-F66C-4628-A33A-5FBA3E9895B4} {7252EB2F-2373-40CD-A7B6-B24919B974AF} = {D91C25AB-6BDD-485E-8375-B638972DC8EF} + {A0756A83-C331-4901-B13B-31010B14784E} = {D91C25AB-6BDD-485E-8375-B638972DC8EF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9A554FA4-21BC-4666-8041-2FB39C103BAB} diff --git a/src/HTTPlease.Core/Core/Utilities/UriHelper.cs b/src/HTTPlease.Core/Core/Utilities/UriHelper.cs index ad25d8e..9f6a617 100644 --- a/src/HTTPlease.Core/Core/Utilities/UriHelper.cs +++ b/src/HTTPlease.Core/Core/Utilities/UriHelper.cs @@ -1,10 +1,13 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Text; namespace HTTPlease.Core.Utilities { + using QueryParameterDictionary = Dictionary>; + /// /// Helper methods for working with s. /// @@ -17,14 +20,14 @@ public static class UriHelper /// The URI. /// /// - /// A containing key / value pairs representing the query parameters. + /// A containing key / value pairs representing the query parameters. /// - public static NameValueCollection ParseQueryParameters(this Uri uri) + public static QueryParameterDictionary ParseQueryParameters(this Uri uri) { if (uri == null) throw new ArgumentNullException(nameof(uri)); - NameValueCollection queryParameters = new NameValueCollection(); + QueryParameterDictionary queryParameters = new QueryParameterDictionary(); if (String.IsNullOrWhiteSpace(uri.Query)) return queryParameters; @@ -42,10 +45,16 @@ public static NameValueCollection ParseQueryParameters(this Uri uri) count: 2 ); - string key = keyAndValue[0]; - string value = keyAndValue.Length == 2 ? keyAndValue[1] : null; + string parameterName = keyAndValue[0]; + string parameterValue = keyAndValue.Length == 2 ? keyAndValue[1] : null; - queryParameters[key] = value; + List parameterValues; + if (!queryParameters.TryGetValue(parameterName, out parameterValues)) + { + parameterValues = new List(capacity: 1); // Optimise for most common case. + queryParameters.Add(parameterName, parameterValues); + } + parameterValues.Add(parameterValue); } return queryParameters; @@ -58,12 +67,12 @@ public static NameValueCollection ParseQueryParameters(this Uri uri) /// The used to construct the URI. /// /// - /// A representing the query parameters. + /// A representing the query parameters. /// /// /// A new URI with the specified query. /// - public static Uri WithQueryParameters(this Uri uri, NameValueCollection parameters) + public static Uri WithQueryParameters(this Uri uri, QueryParameterDictionary parameters) { if (uri == null) throw new ArgumentNullException(nameof(uri)); @@ -84,12 +93,12 @@ public static Uri WithQueryParameters(this Uri uri, NameValueCollection paramete /// The used to construct the URI /// /// - /// A representing the query parameters. + /// A representing the query parameters. /// /// /// The URI builder (enables inline use). /// - public static UriBuilder WithQueryParameters(this UriBuilder uriBuilder, NameValueCollection parameters) + public static UriBuilder WithQueryParameters(this UriBuilder uriBuilder, QueryParameterDictionary parameters) { if (uriBuilder == null) throw new ArgumentNullException(nameof(uriBuilder)); @@ -102,33 +111,35 @@ public static UriBuilder WithQueryParameters(this UriBuilder uriBuilder, NameVal // Yes, you could do this using String.Join, but it seems a bit wasteful to allocate all those "key=value" strings only to throw them away again. - Action addQueryParameter = (builder, parameterIndex) => + bool isFirstItem = true; + StringBuilder queryBuilder = new StringBuilder(); + foreach (KeyValuePair> parameter in parameters) { - string parameterName = parameters.GetKey(parameterIndex); - string parameterValue = parameters.Get(parameterIndex); - - builder.Append(parameterName); + string parameterName = parameter.Key; + List parameterValues = parameter.Value; - // Support for /foo/bar?x=1&y&z=2 - if (parameterValue != null) + // For multiple values, render them as separate query parameters (e.g. "?foo=value1&foo=value2"). + foreach (string parameterValue in parameterValues) { - builder.Append('='); - builder.Append( - Uri.EscapeUriString(parameterValue) - ); - } - }; - - StringBuilder queryBuilder = new StringBuilder(); - - // First parameter has no prefix. - addQueryParameter(queryBuilder, 0); + if (parameterValue == null) + continue; - // Subsequent parameters are separated with an '&' - for (int parameterIndex = 1; parameterIndex < parameters.Count; parameterIndex++) - { - queryBuilder.Append('&'); - addQueryParameter(queryBuilder, parameterIndex); + if (!isFirstItem) + queryBuilder.Append('&'); + else + isFirstItem = false; + + queryBuilder.Append(parameterName); + + // Support for /foo/bar?x=1&y&z=2 + if (parameterValue != String.Empty) + { + queryBuilder.Append('='); + queryBuilder.Append( + Uri.EscapeUriString(parameterValue) + ); + } + } } uriBuilder.Query = queryBuilder.ToString(); diff --git a/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs b/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs index d58c8d8..d5e902f 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs @@ -1,4 +1,6 @@ -namespace HTTPlease.Core.ValueProviders +using System.Collections.Generic; + +namespace HTTPlease.Core.ValueProviders { /// /// Represents the provider for a value from an instance of . @@ -12,7 +14,7 @@ public interface IValueProvider { /// - /// Extract the value from the specified context. + /// Extract a value from the specified context. /// /// /// The instance from which the value is to be extracted. @@ -21,5 +23,16 @@ public interface IValueProvider /// The value. /// TValue Get(TContext source); + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + IEnumerable GetMultiple(TContext source); } } diff --git a/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs b/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs new file mode 100644 index 0000000..eabc807 --- /dev/null +++ b/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace HTTPlease.Core.ValueProviders +{ + /// + /// Factory methods for creating multi-value providers. + /// + /// + /// The type used as a context for each request. + /// + public static class MultiValueProvider + { + /// + /// Create a value provider from the specified selector function. + /// + /// + /// The type of values returned by the selector. + /// + /// + /// A selector function that, when given an instance of , returns values of type derived from the context. + /// + /// + /// The value provider. + /// + public static IValueProvider FromSelector(Func> selector) + { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return new SelectorMultiValueProvider(selector); + } + + /// + /// Create a value provider from the specified function. + /// + /// + /// The type of values returned by the function. + /// + /// + /// A function that returns values of type . + /// + /// + /// The value provider. + /// + public static IValueProvider FromFunction(Func> getValues) + { + if (getValues == null) + throw new ArgumentNullException(nameof(getValues)); + + return new FunctionMultiValueProvider(getValues); + } + + /// + /// Create a value provider from the specified constant values. + /// + /// + /// The type of value returned by the provider. + /// + /// + /// The constant values that are returned by the provider. + /// + /// + /// The value provider. + /// + public static IValueProvider FromConstantValues(IEnumerable values) + { + return new StaticMultiValueProvider(values); + } + + /// + /// A multi-value provider that invokes a selector function on the context to extract its values. + /// + /// + /// The type of value returned by the provider. + /// + class SelectorMultiValueProvider + : IValueProvider + { + /// + /// The selector function that extracts values from the context. + /// + readonly Func> _selector; + + /// + /// Create a new selector-based value provider. + /// + /// + /// The selector function that extracts a value from the context. + /// + public SelectorMultiValueProvider(Func> selector) + { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + _selector = selector; + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + if (source == null) + throw new InvalidOperationException("The current request template has one more more deferred parameters that refer to its context; the context parameter must therefore be supplied."); + + return _selector(source); + } + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); + } + + /// + /// A multi-value provider that invokes a function to extract its value. + /// + /// + /// The type of value returned by the provider. + /// + class FunctionMultiValueProvider + : IValueProvider + { + /// + /// The function that is invoked to provide a value. + /// + readonly Func> _getValues; + + /// + /// Create a new function-based value provider. + /// + /// + /// The function that is invoked to provide values. + /// + public FunctionMultiValueProvider(Func> getValues) + { + if (getValues == null) + throw new ArgumentNullException(nameof(getValues)); + + _getValues = getValues; + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + if (source == null) + return Enumerable.Empty(); + + return _getValues(); + } + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); + } + + /// + /// A multi-value provider that returns a constant value. + /// + /// + /// The type of value returned by the provider. + /// + class StaticMultiValueProvider + : IValueProvider + { + /// + /// The values returned by the provider. + /// + readonly IEnumerable _values; + + /// + /// Create a new constant value provider. + /// + /// + /// The values returned by the provider. + /// + public StaticMultiValueProvider(IEnumerable values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + + _values = values; + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) => _values; + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); + } + } +} diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs index 44ebf5c..76bf963 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace HTTPlease.Core.ValueProviders { @@ -17,7 +18,7 @@ public static class ValueProvider /// The type of value returned by the selector. /// /// - /// A selector function that, when given an instance of , and returns a well-known value of type derived from the context. + /// A selector function that, when given an instance of , returns a value of type derived from the context. /// /// /// The value provider. @@ -37,7 +38,7 @@ public static IValueProvider FromSelector(Func /// - /// A function that returns a well-known value of type . + /// A function that returns a value of type . /// /// /// The value provider. @@ -64,7 +65,7 @@ public static IValueProvider FromFunction(Func /// public static IValueProvider FromConstantValue(TValue value) { - return new ConstantValueProvider(value); + return new StaticValueProvider(value); } /// @@ -89,6 +90,9 @@ class SelectorValueProvider /// public SelectorValueProvider(Func selector) { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + _selector = selector; } @@ -108,6 +112,20 @@ public TValue Get(TContext source) return _selector(source); } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + yield return Get(source); + } } /// @@ -132,11 +150,14 @@ class FunctionValueProvider /// public FunctionValueProvider(Func getValue) { + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + _getValue = getValue; } /// - /// Extract the value from the specified context. + /// Extract a value from the specified context. /// /// /// The TContext instance from which the value is to be extracted. @@ -144,26 +165,34 @@ public FunctionValueProvider(Func getValue) /// /// The value. /// - public TValue Get(TContext source) - { - if (source == null) - return default(TValue); // AF: Is this correct? + public TValue Get(TContext source) => _getValue(); - return _getValue(); + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + yield return Get(source); } } /// - /// Value provider that returns a constant value. + /// Value provider that returns a static set of values. /// /// /// The type of value returned by the provider. /// - class ConstantValueProvider + class StaticValueProvider : IValueProvider { /// - /// The constant value returned by the provider. + /// The value returned by the provider. /// readonly TValue _value; @@ -171,9 +200,9 @@ class ConstantValueProvider /// Create a new constant value provider. /// /// - /// The constant value returned by the provider. + /// The value returned by the provider. /// - public ConstantValueProvider(TValue value) + public StaticValueProvider(TValue value) { _value = value; } @@ -187,12 +216,20 @@ public ConstantValueProvider(TValue value) /// /// The value. /// - public TValue Get(TContext source) - { - if (source == null) - return default(TValue); // AF: Is this correct? + public TValue Get(TContext source) => _value; - return _value; + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + yield return Get(source); } } } diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs new file mode 100644 index 0000000..c6162a1 --- /dev/null +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace HTTPlease.Core.ValueProviders +{ + /// + /// Combination operations for a value provider. + /// + /// + /// The type used as a context for each request. + /// + /// + /// The type of value returned by the value provider. + /// + public struct ValueProviderCombiner + { + /// + /// Create a new value-provider combiner. + /// + /// + /// The value provider being combined. + /// + public ValueProviderCombiner(IValueProvider valueProvider) + : this() + { + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + ValueProvider = valueProvider; + } + + /// + /// The value provider being combined. + /// + public IValueProvider ValueProvider { get; } + + /// + /// Wrap the value provider in a value provider that appends its values to the values supplied by another specified value provider. + /// + /// + /// The value provider whose values come first. + /// + public IValueProvider ByAppendingTo(IValueProvider valueProvider ) + { + // Can't close over members of structs. + IValueProvider existingValueProvider = ValueProvider; + + return MultiValueProvider.FromSelector(context => + { + IEnumerable existingValues = valueProvider.GetMultiple(context); + IEnumerable appendValues = existingValueProvider.GetMultiple(context); + + return existingValues.Concat(appendValues); + }); + } + } +} \ No newline at end of file diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs index 5855947..15bb9b9 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs @@ -29,5 +29,28 @@ public static ValueProviderConversion Convert(valueProvider); } + + /// + /// Perform a conversion on the value provider. + /// + /// + /// The source type from which the value is extracted. + /// + /// + /// The type of value extracted by the provider. + /// + /// + /// The value provider. + /// + /// + /// A whose methods can be used to select the conversion to perform on the value mergeer. + /// + public static ValueProviderCombiner Combine(this IValueProvider valueProvider) + { + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + return new ValueProviderCombiner(valueProvider); + } } } \ No newline at end of file diff --git a/src/HTTPlease.Core/HttpRequest.cs b/src/HTTPlease.Core/HttpRequest.cs index 1ef3a88..5c7df16 100644 --- a/src/HTTPlease.Core/HttpRequest.cs +++ b/src/HTTPlease.Core/HttpRequest.cs @@ -12,6 +12,7 @@ namespace HTTPlease using Core.Utilities; using Core.ValueProviders; + using QueryParameterDictionary = Dictionary>; using RequestProperties = ImmutableDictionary; /// @@ -329,13 +330,24 @@ Uri MergeQueryParameters(Uri requestUri) if (QueryParameters.Count == 0) return requestUri; - NameValueCollection queryParameters = requestUri.ParseQueryParameters(); + // Unlike other types of template parameters, query parameters are a special case - each parameter can have multiple values (which are rendered as multiple parameters of the form "param=value1¶m=value2". + QueryParameterDictionary queryParameters = requestUri.ParseQueryParameters(); foreach (KeyValuePair> queryParameter in QueryParameters) { - string queryParameterValue = queryParameter.Value.Get(DefaultContext); - if (queryParameterValue != null) - queryParameters[queryParameter.Key] = queryParameterValue; - else + string parameterName = queryParameter.Key; + IValueProvider parameterValueProvider = queryParameter.Value; + + List existingValues; + if (!queryParameters.TryGetValue(queryParameter.Key, out existingValues)) + { + existingValues = new List(); + queryParameters.Add(queryParameter.Key, existingValues); + } + + existingValues.AddRange( + parameterValueProvider.GetMultiple(DefaultContext) + ); + if (existingValues.Count == 0) queryParameters.Remove(queryParameter.Key); } diff --git a/src/HTTPlease.Core/HttpRequestOfTContext.cs b/src/HTTPlease.Core/HttpRequestOfTContext.cs index 57675f8..7649411 100644 --- a/src/HTTPlease.Core/HttpRequestOfTContext.cs +++ b/src/HTTPlease.Core/HttpRequestOfTContext.cs @@ -12,6 +12,7 @@ namespace HTTPlease using Core.Utilities; using Core.ValueProviders; + using QueryParameterDictionary = Dictionary>; using RequestProperties = ImmutableDictionary; /// @@ -310,13 +311,23 @@ Uri MergeQueryParameters(Uri requestUri, TContext context) if (QueryParameters.Count == 0) return requestUri; - NameValueCollection queryParameters = requestUri.ParseQueryParameters(); + // Unlike other types of template parameters, query parameters are a special case - each parameter can have multiple values (which are rendered as multiple parameters of the form "param=value1¶m=value2". + QueryParameterDictionary queryParameters = requestUri.ParseQueryParameters(); foreach (KeyValuePair> queryParameter in QueryParameters) { + string parameterName = queryParameter.Key; + + List existingValues; + if (!queryParameters.TryGetValue(queryParameter.Key, out existingValues)) + { + existingValues = new List(); + queryParameters.Add(queryParameter.Key, existingValues); + } + string queryParameterValue = queryParameter.Value.Get(context); if (queryParameterValue != null) - queryParameters[queryParameter.Key] = queryParameterValue; - else + existingValues.Add(queryParameterValue); + else if (existingValues.Count == 0) queryParameters.Remove(queryParameter.Key); } diff --git a/src/HTTPlease.Core/IHttpRequestProperties.cs b/src/HTTPlease.Core/IHttpRequestProperties.cs index e5c1fdd..740cfe7 100644 --- a/src/HTTPlease.Core/IHttpRequestProperties.cs +++ b/src/HTTPlease.Core/IHttpRequestProperties.cs @@ -11,31 +11,22 @@ namespace HTTPlease /// Represents common properties of templates for building HTTP requests. /// public interface IHttpRequestProperties - { + { /// /// The request URI. /// - Uri Uri - { - get; - } + Uri Uri { get; } /// /// Is the request URI a template? /// - bool IsUriTemplate - { - get; - } + bool IsUriTemplate { get; } /// /// Additional properties for the request. /// - ImmutableDictionary Properties - { - get; - } - } + ImmutableDictionary Properties { get; } + } /// /// Represents common properties of templates for building HTTP requests. diff --git a/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs b/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs index 37e61d3..04c5c01 100644 --- a/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs +++ b/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs @@ -108,9 +108,14 @@ public static HttpRequest WithQueryParameterFromProvider(this HttpRe return request.Clone(properties => { + IValueProvider stringValueProvider = valueProvider.Convert().ValueToString(); + + if (request.QueryParameters.TryGetValue(name, out IValueProvider existingStringValueProvider)) + stringValueProvider = stringValueProvider.Combine().ByAppendingTo(existingStringValueProvider); + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.SetItem( key: name, - value: valueProvider.Convert().ValueToString() + value: stringValueProvider ); }); } diff --git a/src/HTTPlease.Core/RequestHeaderExtensions.cs b/src/HTTPlease.Core/RequestHeaderExtensions.cs index 3739ba5..4c401b0 100644 --- a/src/HTTPlease.Core/RequestHeaderExtensions.cs +++ b/src/HTTPlease.Core/RequestHeaderExtensions.cs @@ -20,7 +20,7 @@ public static class RequestHeaderExtensions /// The name of the target header. /// /// - /// The header value, or null if the header is not present (or an string if the header is present but has no value). + /// The header value, or null if the header is not present (or an string if the header is present but has no value). /// public static string GetOptionalHeaderValue(this HttpRequestHeaders requestHeaders, string headerName) { diff --git a/src/HTTPlease.Testability.Xunit/HTTPlease.Testability.Xunit.csproj b/src/HTTPlease.Testability.Xunit/HTTPlease.Testability.Xunit.csproj index a992aab..da4840c 100755 --- a/src/HTTPlease.Testability.Xunit/HTTPlease.Testability.Xunit.csproj +++ b/src/HTTPlease.Testability.Xunit/HTTPlease.Testability.Xunit.csproj @@ -20,7 +20,7 @@ - + diff --git a/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs b/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs index 74c3c04..baf196b 100644 --- a/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs +++ b/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs @@ -5,6 +5,7 @@ namespace HTTPlease.Tests.BuildMessage { using Testability; + using Xunit.Abstractions; /// /// Message-building tests for (). @@ -21,6 +22,16 @@ public class UntypedRequest /// static readonly HttpRequest RelativeRequest = HttpRequest.Create("foo/bar"); + public UntypedRequest(ITestOutputHelper testOutput) + { + if (testOutput == null) + throw new ArgumentNullException(nameof(testOutput)); + + TestOutput = testOutput; + } + + ITestOutputHelper TestOutput { get; } + /// /// An throws . /// @@ -133,13 +144,13 @@ public void AbsoluteUri_Template_DeferredValues() public void AbsoluteUri_Template_Query() { HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}?flag={flag}") + HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}?value={value}") .WithTemplateParameter("action", "foo") .WithTemplateParameter("id", "bar") - .WithTemplateParameter("flag", true); + .WithTemplateParameter("value", true); RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?flag=True" + expectedUri: "http://localhost:1234/foo/bar?value=True" ); } @@ -151,22 +162,22 @@ public void AbsoluteUri_Template_Query_DeferredValues() { string action = "foo"; string id = "bar"; - bool? flag = true; + bool? value = true; HttpRequest request = HttpRequest.Factory.Create("http://localhost:1234/") - .WithRelativeUri("{action}/{id}?flag={flag?}") + .WithRelativeUri("{action}/{id}?value={value?}") .WithTemplateParameter("action", () => action) .WithTemplateParameter("id", () => id) - .WithTemplateParameter("flag", () => flag); + .WithTemplateParameter("value", () => value); RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?flag=True" + expectedUri: "http://localhost:1234/foo/bar?value=True" ); action = "diddly"; id = "dee"; - flag = null; + value = null; RequestAssert.MessageHasUri(request, expectedUri: "http://localhost:1234/diddly/dee" @@ -185,10 +196,10 @@ public void AbsoluteUri_Query() { HttpRequest request = HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("flag", true); + .WithQueryParameter("value", true); RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?flag=True" + expectedUri: "http://localhost:1234/foo/bar?value=True" ); } @@ -198,17 +209,79 @@ public void AbsoluteUri_Query() [Fact] public void AbsoluteUri_AddQuery_DeferredValues() { - bool? flag = true; + bool? value = true; HttpRequest request = HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("flag", () => flag); + .WithQueryParameter("value", () => value); RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?flag=True" + expectedUri: "http://localhost:1234/foo/bar?value=True" ); - flag = null; + value = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + } + + /// + /// An with an absolute URI that adds a multiple (duplicate) parameter query component, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_AddQuery_Multiple_DeferredValues() + { + bool? value1 = true; + bool? value2 = false; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("value", () => value1) + .WithQueryParameter("value", () => value2); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=True&value=False" + ); + + value1 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=False" + ); + + value2 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + } + + /// + /// An with an absolute URI that adds a multiple (duplicate) flag parameter query component, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_AddQuery_Flag_Multiple_DeferredValues() + { + string value1 = String.Empty; + string value2 = String.Empty; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("flag", () => value1) + .WithQueryParameter("flag", () => value2); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?flag&flag" + ); + + value1 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?flag" + ); + + value2 = null; RequestAssert.MessageHasUri(request, expectedUri: "http://localhost:1234/foo/bar" diff --git a/test/HTTPlease.Core.Tests/HTTPlease.Core.Tests.csproj b/test/HTTPlease.Core.Tests/HTTPlease.Core.Tests.csproj index cb42281..b7d62c4 100755 --- a/test/HTTPlease.Core.Tests/HTTPlease.Core.Tests.csproj +++ b/test/HTTPlease.Core.Tests/HTTPlease.Core.Tests.csproj @@ -2,22 +2,26 @@ netcoreapp2.0 + exe true true HTTPlease.Core.Tests ../../HTTPlease.snk true HTTPlease.Core.Tests - - true + + true - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/HTTPlease.Diagnostics.Tests/HTTPlease.Diagnostics.Tests.csproj b/test/HTTPlease.Diagnostics.Tests/HTTPlease.Diagnostics.Tests.csproj index fac2b24..fffb4c9 100644 --- a/test/HTTPlease.Diagnostics.Tests/HTTPlease.Diagnostics.Tests.csproj +++ b/test/HTTPlease.Diagnostics.Tests/HTTPlease.Diagnostics.Tests.csproj @@ -16,8 +16,11 @@ - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/HTTPlease.Formatters.FunctionalTests/HTTPlease.Formatters.FunctionalTests.csproj b/test/HTTPlease.Formatters.FunctionalTests/HTTPlease.Formatters.FunctionalTests.csproj index 745c73c..e47809d 100755 --- a/test/HTTPlease.Formatters.FunctionalTests/HTTPlease.Formatters.FunctionalTests.csproj +++ b/test/HTTPlease.Formatters.FunctionalTests/HTTPlease.Formatters.FunctionalTests.csproj @@ -22,8 +22,11 @@ - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/HTTPlease.Formatters.Tests/HTTPlease.Formatters.Tests.csproj b/test/HTTPlease.Formatters.Tests/HTTPlease.Formatters.Tests.csproj index 8b5b43a..c3f55d3 100755 --- a/test/HTTPlease.Formatters.Tests/HTTPlease.Formatters.Tests.csproj +++ b/test/HTTPlease.Formatters.Tests/HTTPlease.Formatters.Tests.csproj @@ -16,8 +16,11 @@ - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/HTTPlease.TestHarness/HTTPlease.TestHarness.csproj b/test/HTTPlease.TestHarness/HTTPlease.TestHarness.csproj new file mode 100644 index 0000000..48ad7c2 --- /dev/null +++ b/test/HTTPlease.TestHarness/HTTPlease.TestHarness.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + diff --git a/test/HTTPlease.TestHarness/Program.cs b/test/HTTPlease.TestHarness/Program.cs new file mode 100644 index 0000000..a7f4039 --- /dev/null +++ b/test/HTTPlease.TestHarness/Program.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; +using System.Net.Http; + +namespace HTTPlease.TestHarness +{ + /// + /// Quick-and-dirty test harness for use when Visual Studio refuses to debug unit tests. + /// + static class Program + { + static void Main() + { + Trace.Listeners.Add( + new TextWriterTraceListener(Console.Out) + ); + + try + { + string value1 = String.Empty; + string value2 = String.Empty; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("flag", () => value1) + .WithQueryParameter("flag", () => value2); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) + { + Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); + } + + value1 = null; + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) + { + Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); + } + + value2 = null; + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) + { + Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); + } + } + catch (Exception unexpectedError) + { + Console.WriteLine(unexpectedError); + } + } + } +} From e6c5016209d6bc7a27d9c0e2dddadc3140163bbb Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Fri, 19 Apr 2019 12:17:50 +1000 Subject: [PATCH 2/4] Tabs to spaces (cackle!). --- .editorconfig | 2 +- .../source/development/contributing/index.rst | 2 +- src/HTTPlease.Core/ClientBuilder.cs | 802 +++++----- src/HTTPlease.Core/ClientBuilderExtensions.cs | 524 +++---- .../ClientExtensions.Streamed.cs | 20 +- src/HTTPlease.Core/ClientExtensions.cs | 458 +++--- src/HTTPlease.Core/Core/HttpRequestBase.cs | 508 +++---- src/HTTPlease.Core/Core/RequestActions.cs | 42 +- src/HTTPlease.Core/Core/ResponseActions.cs | 42 +- .../Templates/ITemplateEvaluationContext.cs | 80 +- .../Core/Templates/LiteralQuerySegment.cs | 104 +- .../Core/Templates/LiteralUriSegment.cs | 104 +- .../Templates/ParameterizedQuerySegment.cs | 160 +- .../Core/Templates/ParameterizedUriSegment.cs | 164 +- .../Core/Templates/QuerySegment.cs | 66 +- .../Core/Templates/RootUriSegment.cs | 66 +- .../Templates/TemplateEvaluationContext.cs | 190 +-- .../Core/Templates/TemplateSegment.cs | 382 ++--- .../Core/Templates/UriSegment.cs | 62 +- .../Core/Utilities/DisposableObject.cs | 146 +- .../Core/Utilities/DisposalHelpers.cs | 138 +- .../Core/Utilities/ReflectionHelper.cs | 56 +- .../Core/Utilities/UriHelper.cs | 592 ++++---- .../Core/ValueProviders/IValueProvider.cs | 64 +- .../Core/ValueProviders/MultiValueProvider.cs | 460 +++--- .../Core/ValueProviders/ValueProvider.cs | 460 +++--- .../ValueProviders/ValueProviderCombiner.cs | 92 +- .../ValueProviders/ValueProviderConversion.cs | 142 +- .../ValueProviders/ValueProviderExtensions.cs | 96 +- src/HTTPlease.Core/FactoryExtensions.cs | 54 +- src/HTTPlease.Core/HttpRequest.cs | 780 +++++----- src/HTTPlease.Core/HttpRequestException.cs | 138 +- src/HTTPlease.Core/HttpRequestFactory.cs | 150 +- src/HTTPlease.Core/HttpRequestOfTContext.cs | 746 ++++----- src/HTTPlease.Core/HttpResponse.cs | 172 +-- src/HTTPlease.Core/IHttpRequest.cs | 102 +- src/HTTPlease.Core/IHttpRequestProperties.cs | 106 +- src/HTTPlease.Core/MessageExtensions.cs | 86 +- src/HTTPlease.Core/MessageProperties.cs | 42 +- src/HTTPlease.Core/OtherHttpMethods.cs | 20 +- .../RequestExtensions.Headers.cs | 532 +++---- .../RequestExtensions.Helpers.cs | 178 +-- .../RequestExtensions.Messages.cs | 12 +- .../RequestExtensions.QueryParameters.cs | 484 +++--- .../RequestExtensions.RequestActions.cs | 228 +-- .../RequestExtensions.RequestUri.cs | 318 ++-- .../RequestExtensions.ResponseActions.cs | 228 +-- .../RequestExtensions.TemplateParameters.cs | 478 +++--- src/HTTPlease.Core/RequestHeaderExtensions.cs | 66 +- src/HTTPlease.Core/TypedClientExtensions.cs | 504 +++---- src/HTTPlease.Core/TypedFactoryExtensions.cs | 60 +- .../TypedRequestExtensions.Headers.cs | 784 +++++----- .../TypedRequestExtensions.Helpers.cs | 176 +-- .../TypedRequestExtensions.Messages.cs | 12 +- .../TypedRequestExtensions.QueryParameters.cs | 448 +++--- .../TypedRequestExtensions.RequestActions.cs | 272 ++-- .../TypedRequestExtensions.RequestUri.cs | 244 +-- .../TypedRequestExtensions.ResponseActions.cs | 272 ++-- ...pedRequestExtensions.TemplateParameters.cs | 576 +++---- src/HTTPlease.Core/UriTemplate.cs | 334 ++--- src/HTTPlease.Core/UriTemplateException.cs | 72 +- .../ClientBuilderExtensions.cs | 128 +- src/HTTPlease.Diagnostics/LogEventIds.cs | 58 +- .../LogMessageComponents.cs | 54 +- src/HTTPlease.Diagnostics/LoggerExtensions.cs | 358 ++--- .../MessageHandlers/LoggingMessageHandler.cs | 200 +-- .../JsonFormatter.cs | 236 +-- .../JsonFormatterExtensions.cs | 56 +- .../JsonFormatterFactoryExtensions.cs | 190 +-- .../JsonFormatterRequestExtensions.cs | 108 +- .../JsonFormatterTypedFactoryExtensions.cs | 114 +- .../TypedFactoryExtensions.cs | 114 +- src/HTTPlease.Formatters.Xml/XmlFormatter.cs | 204 +-- .../XmlFormatterExtensions.cs | 44 +- .../XmlFormatterFactoryExtensions.cs | 104 +- .../XmlFormatterRequestExtensions.cs | 84 +- .../XmlSerializerFormatter.cs | 198 +-- .../XmlSerializerFormatterExtensions.cs | 42 +- ...XmlSerializerFormatterFactoryExtensions.cs | 104 +- ...XmlSerializerFormatterRequestExtensions.cs | 84 +- ...rializerFormatterTypedFactoryExtensions.cs | 114 +- src/HTTPlease.Formatters/ContentExtensions.cs | 172 +-- .../EncodingWithoutPreamble.cs | 34 +- .../FormattedObjectContent.cs | 300 ++-- .../FormatterClientExtensions.cs | 478 +++--- .../FormatterCollection.cs | 550 +++---- .../FormatterCollectionExtensions.cs | 44 +- .../FormatterRequestExtensions.cs | 472 +++--- .../FormatterResponseExtensions.cs | 1204 +++++++-------- .../FormatterTypedRequestExtensions.cs | 424 +++--- src/HTTPlease.Formatters/IFormatter.cs | 16 +- .../IFormatterCollection.cs | 104 +- src/HTTPlease.Formatters/IInputFormatter.cs | 58 +- .../IInputOutputFormatter.cs | 8 +- src/HTTPlease.Formatters/IOutputFormatter.cs | 54 +- .../InputFormatterContext.cs | 108 +- src/HTTPlease.Formatters/MessageExtensions.cs | 122 +- .../OutputFormatterContext.cs | 126 +- src/HTTPlease.Formatters/StreamHelper.cs | 142 +- .../WellKnownMediaTypes.cs | 36 +- .../HttpRequestAuthenticationProvider.cs | 52 +- ...HttpRequestHeaderAuthenticationProvider.cs | 98 +- .../IHttpRequestAuthenticationProvider.cs | 38 +- .../ClientBuilderExtensions.cs | 100 +- .../AuthenticationMessageHandler.cs | 120 +- .../MessageAssert.cs | 168 +-- .../MessageExtensions.cs | 222 +-- .../Mocks/MockMessageHandler.cs | 144 +- .../RequestAssert.cs | 1334 ++++++++--------- .../TestClients.cs | 712 ++++----- .../TestHandlers.cs | 696 ++++----- .../BuildMessage/TypedRequest.cs | 196 +-- .../BuildMessage/UntypedRequest.cs | 632 ++++---- .../HTTPlease.Core.Tests/TypedRequestTests.cs | 378 ++--- test/HTTPlease.Core.Tests/UnitTestBase.cs | 24 +- test/HTTPlease.Core.Tests/UriTemplateTests.cs | 254 ++-- test/HTTPlease.Diagnostics.Tests/LogEntry.cs | 128 +- .../LoggingTests.cs | 256 ++-- .../HTTPlease.Diagnostics.Tests/TestLogger.cs | 138 +- .../FormattedRequestTests.cs | 240 +-- .../JsonFormattedRequestTests.cs | 70 +- .../JsonTestClients.cs | 534 +++---- .../MessageExtensions.cs | 112 +- .../ReadResponseTests.cs | 460 +++--- .../XmlFormattedRequestTests.cs | 70 +- .../XmlTestClients.cs | 534 +++---- test/HTTPlease.TestHarness/Program.cs | 92 +- 127 files changed, 14773 insertions(+), 14773 deletions(-) diff --git a/.editorconfig b/.editorconfig index 3856786..5b17dc0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,7 @@ root = true [*] -indent_style = tab +indent_style = space # Code files [*.{cs,csx,vb,vbx}] diff --git a/docs/source/development/contributing/index.rst b/docs/source/development/contributing/index.rst index d2d5872..e6eabf7 100644 --- a/docs/source/development/contributing/index.rst +++ b/docs/source/development/contributing/index.rst @@ -28,7 +28,7 @@ Some basic guidelines: * Please don't use auto-generated comments - all this does is make it harder to know what members actually have meaningful comments. * If you really object to adding doc comments, make a note in the pull request and we'll try to add them for you -* Tabs, not spaces +* Spaces, not tabs * Please don't use the ``var`` keyword unless: * There is a type on the right-hand side of the expression (e.g. ``new Foo()``) diff --git a/src/HTTPlease.Core/ClientBuilder.cs b/src/HTTPlease.Core/ClientBuilder.cs index 55cb51f..5f59b32 100644 --- a/src/HTTPlease.Core/ClientBuilder.cs +++ b/src/HTTPlease.Core/ClientBuilder.cs @@ -12,417 +12,417 @@ namespace HTTPlease using Core.Utilities; /// - /// Builds s with pipelines of HTTP message handlers. + /// Builds s with pipelines of HTTP message handlers. /// /// - /// Be aware that, if you return singleton instances of message handlers from factory delegates, those handlers will be disposed if the factory encounters any exception while creating a client. + /// Be aware that, if you return singleton instances of message handlers from factory delegates, those handlers will be disposed if the factory encounters any exception while creating a client. /// public sealed class ClientBuilder - { - /// - /// The default factory for message-pipeline terminus handlers. - /// - static readonly Func DefaultMessagePipelineTerminus = existingHandler => new HttpClientHandler(); - - /// - /// Factory delegates used to produce the HTTP message handlers that comprise client pipelines. - /// - ImmutableList> _handlerFactories = ImmutableList>.Empty; - - /// - /// Delegates to create or modify the that will form the message pipeline terminus. + { + /// + /// The default factory for message-pipeline terminus handlers. /// - /// - /// Each delegate is passed the result of the previous delegate (if any). - /// - /// Can be overridden by the value passed to CreateClient. + static readonly Func DefaultMessagePipelineTerminus = existingHandler => new HttpClientHandler(); + + /// + /// Factory delegates used to produce the HTTP message handlers that comprise client pipelines. + /// + ImmutableList> _handlerFactories = ImmutableList>.Empty; + + /// + /// Delegates to create or modify the that will form the message pipeline terminus. + /// + /// + /// Each delegate is passed the result of the previous delegate (if any). + /// + /// Can be overridden by the value passed to CreateClient. /// - ImmutableList> _pipelineTerminusConfigurators = ImmutableList.Create(DefaultMessagePipelineTerminus); - - /// - /// Create a new HTTP client builder. - /// - public ClientBuilder() - { - } - - /// - /// Create a new HTTP client builder. - /// - /// - /// The HTTP client buider to copy configuration from. - /// - ClientBuilder(ClientBuilder copyFrom) - { - if (copyFrom == null) - throw new ArgumentNullException(nameof(copyFrom)); - - _handlerFactories = copyFrom._handlerFactories; - _pipelineTerminusConfigurators = copyFrom._pipelineTerminusConfigurators; - } - - /// - /// Create an using the configured message-handler pipeline. - /// - /// - /// An optional base URI for the . - /// - /// - /// An optional that will form the message pipeline terminus. - /// - /// If not specified, the pre-configured message pipeline terminus is used. - /// - /// - /// The new . - /// - public HttpClient CreateClient(Uri baseUri = null, HttpMessageHandler messagePipelineTerminus = null) - { - HttpMessageHandler pipelineTerminus = null; - List pipelineHandlers = new List(); - HttpMessageHandler pipeline = null; - HttpClient client = null; - - try - { - pipelineTerminus = messagePipelineTerminus ?? BuildMessagePipelineTerminus(); - - foreach (Func handlerFactory in _handlerFactories) - { - DelegatingHandler currentHandler = null; - try - { - currentHandler = handlerFactory(); - } - catch - { - using (currentHandler) - throw; - } - pipelineHandlers.Add(currentHandler); - } - - pipeline = CreatePipeline(pipelineTerminus, pipelineHandlers); - - client = new HttpClient(pipeline); - if (baseUri != null) - client.BaseAddress = baseUri; - } - catch - { - using (pipelineTerminus) - using (pipelineHandlers.ToAggregateDisposable()) - using (pipeline) - using (client) - { - throw; - } - } - - return client; - } - - /// - /// Create a copy of the , but with the specified configuration for its message pipeline terminus. + ImmutableList> _pipelineTerminusConfigurators = ImmutableList.Create(DefaultMessagePipelineTerminus); + + /// + /// Create a new HTTP client builder. + /// + public ClientBuilder() + { + } + + /// + /// Create a new HTTP client builder. + /// + /// + /// The HTTP client buider to copy configuration from. + /// + ClientBuilder(ClientBuilder copyFrom) + { + if (copyFrom == null) + throw new ArgumentNullException(nameof(copyFrom)); + + _handlerFactories = copyFrom._handlerFactories; + _pipelineTerminusConfigurators = copyFrom._pipelineTerminusConfigurators; + } + + /// + /// Create an using the configured message-handler pipeline. + /// + /// + /// An optional base URI for the . + /// + /// + /// An optional that will form the message pipeline terminus. + /// + /// If not specified, the pre-configured message pipeline terminus is used. + /// + /// + /// The new . + /// + public HttpClient CreateClient(Uri baseUri = null, HttpMessageHandler messagePipelineTerminus = null) + { + HttpMessageHandler pipelineTerminus = null; + List pipelineHandlers = new List(); + HttpMessageHandler pipeline = null; + HttpClient client = null; + + try + { + pipelineTerminus = messagePipelineTerminus ?? BuildMessagePipelineTerminus(); + + foreach (Func handlerFactory in _handlerFactories) + { + DelegatingHandler currentHandler = null; + try + { + currentHandler = handlerFactory(); + } + catch + { + using (currentHandler) + throw; + } + pipelineHandlers.Add(currentHandler); + } + + pipeline = CreatePipeline(pipelineTerminus, pipelineHandlers); + + client = new HttpClient(pipeline); + if (baseUri != null) + client.BaseAddress = baseUri; + } + catch + { + using (pipelineTerminus) + using (pipelineHandlers.ToAggregateDisposable()) + using (pipeline) + using (client) + { + throw; + } + } + + return client; + } + + /// + /// Create a copy of the , but with the specified configuration for its message pipeline terminus. /// /// - /// A delegate that creates the for each that will form its message pipeline terminus. - /// - /// If null, the default message handler pipeline terminus will be used. - /// + /// A delegate that creates the for each that will form its message pipeline terminus. + /// + /// If null, the default message handler pipeline terminus will be used. + /// /// - /// The configured . - /// - public ClientBuilder WithMessagePipelineTerminus(Func pipelineTerminusConfigurator) - { - return new ClientBuilder(this) - { - _pipelineTerminusConfigurators = _pipelineTerminusConfigurators.Add( - pipelineTerminusConfigurator ?? DefaultMessagePipelineTerminus - ) - }; - } - - /// - /// Create a copy of the , but with the specified message pipeline terminus. + /// The configured . + /// + public ClientBuilder WithMessagePipelineTerminus(Func pipelineTerminusConfigurator) + { + return new ClientBuilder(this) + { + _pipelineTerminusConfigurators = _pipelineTerminusConfigurators.Add( + pipelineTerminusConfigurator ?? DefaultMessagePipelineTerminus + ) + }; + } + + /// + /// Create a copy of the , but with the specified message pipeline terminus. /// /// - /// A delegate that creates the for each that will form its message pipeline terminus. - /// - /// If null, the default message handler pipeline terminus will be used. - /// + /// A delegate that creates the for each that will form its message pipeline terminus. + /// + /// If null, the default message handler pipeline terminus will be used. + /// + /// + /// The configured . + /// + public ClientBuilder WithMessagePipelineTerminus(Func pipelineTerminusFactory) + { + Func configurator = DefaultMessagePipelineTerminus; + if (pipelineTerminusFactory != null) + configurator = _ => pipelineTerminusFactory(); + + return new ClientBuilder(this) + { + _pipelineTerminusConfigurators = ImmutableList.Create(configurator) // Replaces any existing configurators. + }; + } + + /// + /// Create a copy of the , adding an HTTP message-handler factory to the end of the pipeline. + /// + /// + /// The handler type. + /// + /// + /// The message-handler factory. + /// + /// + /// The (enables method-chaining). + /// + /// + /// cannot be the base class. + /// + public ClientBuilder AddHandler(Func handlerFactory) + where THandler : DelegatingHandler + { + if (handlerFactory == null) + throw new ArgumentNullException(nameof(handlerFactory)); + + if (typeof(THandler) == typeof(DelegatingHandler)) + throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); + + if (_handlerFactories.OfType>().Any()) + { + throw new InvalidOperationException( + String.Format( + "The configured handler pipeline already contains a factory for message-handlers of type '{0}'.", + typeof(THandler).AssemblyQualifiedName + ) + ); + } + + return new ClientBuilder(this) + { + _handlerFactories = _handlerFactories.Add(handlerFactory) + }; + } + + /// + /// Create a copy of the , inserting an HTTP message-handler factory to the pipeline before the factory that produces handlers of the specified type. + /// + /// + /// The handler type. + /// + /// + /// The type of handler before whose factory the new handler factory should be inserted. + /// + /// + /// The message-handler factory. + /// + /// + /// Throw an if no factory for is present? + /// + /// Default is false. + /// + /// + /// The (enables method-chaining). + /// + /// + /// and cannot be the base class. + /// + public ClientBuilder AddHandlerBefore(Func handlerFactory, bool throwIfNotPresent = false) + where THandler : DelegatingHandler + where TBeforeHandler : DelegatingHandler + { + if (handlerFactory == null) + throw new ArgumentNullException(nameof(handlerFactory)); + + if (typeof(THandler) == typeof(DelegatingHandler)) + throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); + + if (typeof(THandler) == typeof(DelegatingHandler)) + throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); + + if (_handlerFactories.OfType>().Any()) + { + throw new InvalidOperationException( + String.Format( + "The configured handler pipeline already contains a factory for message-handlers of type '{0}'.", + typeof(THandler).AssemblyQualifiedName + ) + ); + } + + Type beforeHandlerFactoryType = typeof(Func); + for (int handlerIndex = 0; handlerIndex < _handlerFactories.Count; handlerIndex++) + { + if (_handlerFactories[handlerIndex].GetType() == beforeHandlerFactoryType) + { + return new ClientBuilder(this) + { + _handlerFactories = _handlerFactories.Insert(handlerIndex, handlerFactory) + }; + } + } + + if (throwIfNotPresent) + { + throw new InvalidOperationException( + String.Format( + "Cannot insert factory for message-handlers of type '{0}' before the factory for message-handlers of type '{1}' (the pipeline does not contain a factory for message-handlers of this type.", + typeof(THandler).AssemblyQualifiedName, + typeof(TBeforeHandler).AssemblyQualifiedName + ) + ); + } + + // TBefore is not present, so just append to the end of the pipeline. + return new ClientBuilder(this) + { + _handlerFactories = _handlerFactories.Add(handlerFactory) + }; + } + + /// + /// Create a copy of the , inserting an HTTP message-handler factory to the pipeline after the factory that produces handlers of the specified type. + /// + /// + /// The handler type. + /// + /// + /// The type of handler after whose factory the new handler factory should be inserted. + /// + /// + /// The message-handler factory. + /// + /// + /// Throw an if no factory for is present? + /// + /// Default is false. + /// + /// + /// The (enables method-chaining). + /// + /// + /// and cannot be the base class. + /// + public ClientBuilder AddHandlerAfter(Func handlerFactory, bool throwIfNotPresent = false) + where THandler : DelegatingHandler + where TAfterHandler : DelegatingHandler + { + if (handlerFactory == null) + throw new ArgumentNullException(nameof(handlerFactory)); + + if (typeof(THandler) == typeof(DelegatingHandler)) + throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); + + if (typeof(THandler) == typeof(DelegatingHandler)) + throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); + + if (_handlerFactories.OfType>().Any()) + { + throw new InvalidOperationException( + String.Format( + "The configured handler pipeline already contains a factory for message-handlers of type '{0}'.", + typeof(THandler).AssemblyQualifiedName + ) + ); + } + + Type afterHandlerFactoryType = typeof(Func); + for (int handlerIndex = 0; handlerIndex < _handlerFactories.Count; handlerIndex++) + { + if (_handlerFactories[handlerIndex].GetType() == afterHandlerFactoryType) + { + return new ClientBuilder(this) + { + _handlerFactories = _handlerFactories.Insert(handlerIndex + 1, handlerFactory) + }; + } + } + + if (throwIfNotPresent) + { + throw new InvalidOperationException( + String.Format( + "Cannot insert factory for message-handlers of type '{0}' after the factory for message-handlers of type '{1}' (the pipeline does not contain a factory for message-handlers of this type.", + typeof(THandler).AssemblyQualifiedName, + typeof(TAfterHandler).AssemblyQualifiedName + ) + ); + } + + // TAfter is not present, so just append to the end of the pipeline. + return new ClientBuilder(this) + { + _handlerFactories = _handlerFactories.Add(handlerFactory) + }; + } + + /// + /// Enumerate the types of handlers configured in the factory's pipeline. + /// + /// + /// A sequence of 0 or more types. + /// + /// + /// This operation uses Reflection, so it can be relatively expensive; use sparingly. + /// + public IEnumerable EnumerateHandlerTypes() + { + for (int handlerIndex = 0; handlerIndex < _handlerFactories.Count; handlerIndex++) + { + Func factory = _handlerFactories[handlerIndex]; + Type factoryDelegateType = factory.GetType(); + + yield return factoryDelegateType.GenericTypeArguments[0]; + } + } + + /// + /// Create an HTTP message-handler pipeline. + /// + /// + /// An representing the terminus of the pipeline. + /// + /// + /// A sequence of s representing additional steps in the pipeline. + /// + /// + /// An representing the head of the pipeline. + /// + static HttpMessageHandler CreatePipeline(HttpMessageHandler pipelineTerminus, IEnumerable pipelineHandlers) + { + if (pipelineTerminus == null) + throw new ArgumentNullException(nameof(pipelineTerminus)); + + if (pipelineHandlers == null) + throw new ArgumentNullException(nameof(pipelineHandlers)); + + HttpMessageHandler pipeline = pipelineTerminus; + foreach (DelegatingHandler pipelineHandler in pipelineHandlers.Reverse()) + { + pipelineHandler.InnerHandler = pipeline; + pipeline = pipelineHandler; + } + + return pipeline; + } + + /// + /// Build / configure an HTTP message handler to act as the message pipeline terminus. + /// /// - /// The configured . - /// - public ClientBuilder WithMessagePipelineTerminus(Func pipelineTerminusFactory) - { - Func configurator = DefaultMessagePipelineTerminus; - if (pipelineTerminusFactory != null) - configurator = _ => pipelineTerminusFactory(); - - return new ClientBuilder(this) - { - _pipelineTerminusConfigurators = ImmutableList.Create(configurator) // Replaces any existing configurators. - }; - } - - /// - /// Create a copy of the , adding an HTTP message-handler factory to the end of the pipeline. - /// - /// - /// The handler type. - /// - /// - /// The message-handler factory. - /// - /// - /// The (enables method-chaining). - /// - /// - /// cannot be the base class. - /// - public ClientBuilder AddHandler(Func handlerFactory) - where THandler : DelegatingHandler - { - if (handlerFactory == null) - throw new ArgumentNullException(nameof(handlerFactory)); - - if (typeof(THandler) == typeof(DelegatingHandler)) - throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); - - if (_handlerFactories.OfType>().Any()) - { - throw new InvalidOperationException( - String.Format( - "The configured handler pipeline already contains a factory for message-handlers of type '{0}'.", - typeof(THandler).AssemblyQualifiedName - ) - ); - } - - return new ClientBuilder(this) - { - _handlerFactories = _handlerFactories.Add(handlerFactory) - }; - } - - /// - /// Create a copy of the , inserting an HTTP message-handler factory to the pipeline before the factory that produces handlers of the specified type. - /// - /// - /// The handler type. - /// - /// - /// The type of handler before whose factory the new handler factory should be inserted. - /// - /// - /// The message-handler factory. - /// - /// - /// Throw an if no factory for is present? - /// - /// Default is false. - /// - /// - /// The (enables method-chaining). - /// - /// - /// and cannot be the base class. - /// - public ClientBuilder AddHandlerBefore(Func handlerFactory, bool throwIfNotPresent = false) - where THandler : DelegatingHandler - where TBeforeHandler : DelegatingHandler - { - if (handlerFactory == null) - throw new ArgumentNullException(nameof(handlerFactory)); - - if (typeof(THandler) == typeof(DelegatingHandler)) - throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); - - if (typeof(THandler) == typeof(DelegatingHandler)) - throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); - - if (_handlerFactories.OfType>().Any()) - { - throw new InvalidOperationException( - String.Format( - "The configured handler pipeline already contains a factory for message-handlers of type '{0}'.", - typeof(THandler).AssemblyQualifiedName - ) - ); - } - - Type beforeHandlerFactoryType = typeof(Func); - for (int handlerIndex = 0; handlerIndex < _handlerFactories.Count; handlerIndex++) - { - if (_handlerFactories[handlerIndex].GetType() == beforeHandlerFactoryType) - { - return new ClientBuilder(this) - { - _handlerFactories = _handlerFactories.Insert(handlerIndex, handlerFactory) - }; - } - } - - if (throwIfNotPresent) - { - throw new InvalidOperationException( - String.Format( - "Cannot insert factory for message-handlers of type '{0}' before the factory for message-handlers of type '{1}' (the pipeline does not contain a factory for message-handlers of this type.", - typeof(THandler).AssemblyQualifiedName, - typeof(TBeforeHandler).AssemblyQualifiedName - ) - ); - } - - // TBefore is not present, so just append to the end of the pipeline. - return new ClientBuilder(this) - { - _handlerFactories = _handlerFactories.Add(handlerFactory) - }; - } - - /// - /// Create a copy of the , inserting an HTTP message-handler factory to the pipeline after the factory that produces handlers of the specified type. - /// - /// - /// The handler type. - /// - /// - /// The type of handler after whose factory the new handler factory should be inserted. - /// - /// - /// The message-handler factory. - /// - /// - /// Throw an if no factory for is present? - /// - /// Default is false. - /// - /// - /// The (enables method-chaining). - /// - /// - /// and cannot be the base class. - /// - public ClientBuilder AddHandlerAfter(Func handlerFactory, bool throwIfNotPresent = false) - where THandler : DelegatingHandler - where TAfterHandler : DelegatingHandler - { - if (handlerFactory == null) - throw new ArgumentNullException(nameof(handlerFactory)); - - if (typeof(THandler) == typeof(DelegatingHandler)) - throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); - - if (typeof(THandler) == typeof(DelegatingHandler)) - throw new InvalidOperationException("Handler type cannot be the DelegatingHandler base class."); - - if (_handlerFactories.OfType>().Any()) - { - throw new InvalidOperationException( - String.Format( - "The configured handler pipeline already contains a factory for message-handlers of type '{0}'.", - typeof(THandler).AssemblyQualifiedName - ) - ); - } - - Type afterHandlerFactoryType = typeof(Func); - for (int handlerIndex = 0; handlerIndex < _handlerFactories.Count; handlerIndex++) - { - if (_handlerFactories[handlerIndex].GetType() == afterHandlerFactoryType) - { - return new ClientBuilder(this) - { - _handlerFactories = _handlerFactories.Insert(handlerIndex + 1, handlerFactory) - }; - } - } - - if (throwIfNotPresent) - { - throw new InvalidOperationException( - String.Format( - "Cannot insert factory for message-handlers of type '{0}' after the factory for message-handlers of type '{1}' (the pipeline does not contain a factory for message-handlers of this type.", - typeof(THandler).AssemblyQualifiedName, - typeof(TAfterHandler).AssemblyQualifiedName - ) - ); - } - - // TAfter is not present, so just append to the end of the pipeline. - return new ClientBuilder(this) - { - _handlerFactories = _handlerFactories.Add(handlerFactory) - }; - } - - /// - /// Enumerate the types of handlers configured in the factory's pipeline. - /// - /// - /// A sequence of 0 or more types. - /// - /// - /// This operation uses Reflection, so it can be relatively expensive; use sparingly. - /// - public IEnumerable EnumerateHandlerTypes() - { - for (int handlerIndex = 0; handlerIndex < _handlerFactories.Count; handlerIndex++) - { - Func factory = _handlerFactories[handlerIndex]; - Type factoryDelegateType = factory.GetType(); - - yield return factoryDelegateType.GenericTypeArguments[0]; - } - } - - /// - /// Create an HTTP message-handler pipeline. - /// - /// - /// An representing the terminus of the pipeline. - /// - /// - /// A sequence of s representing additional steps in the pipeline. - /// - /// - /// An representing the head of the pipeline. - /// - static HttpMessageHandler CreatePipeline(HttpMessageHandler pipelineTerminus, IEnumerable pipelineHandlers) - { - if (pipelineTerminus == null) - throw new ArgumentNullException(nameof(pipelineTerminus)); - - if (pipelineHandlers == null) - throw new ArgumentNullException(nameof(pipelineHandlers)); - - HttpMessageHandler pipeline = pipelineTerminus; - foreach (DelegatingHandler pipelineHandler in pipelineHandlers.Reverse()) - { - pipelineHandler.InnerHandler = pipeline; - pipeline = pipelineHandler; - } - - return pipeline; - } - - /// - /// Build / configure an HTTP message handler to act as the message pipeline terminus. - /// - /// - /// The configured . - /// - HttpMessageHandler BuildMessagePipelineTerminus() - { - HttpMessageHandler pipelineTerminus = null; - foreach (Func terminusConfiguration in _pipelineTerminusConfigurators) - pipelineTerminus = terminusConfiguration(pipelineTerminus); - - if (pipelineTerminus == null) - throw new InvalidOperationException("One or more configuration delegates for the message pipeline terminus returned null."); - - return pipelineTerminus; - } - } + /// The configured . + /// + HttpMessageHandler BuildMessagePipelineTerminus() + { + HttpMessageHandler pipelineTerminus = null; + foreach (Func terminusConfiguration in _pipelineTerminusConfigurators) + pipelineTerminus = terminusConfiguration(pipelineTerminus); + + if (pipelineTerminus == null) + throw new InvalidOperationException("One or more configuration delegates for the message pipeline terminus returned null."); + + return pipelineTerminus; + } + } } diff --git a/src/HTTPlease.Core/ClientBuilderExtensions.cs b/src/HTTPlease.Core/ClientBuilderExtensions.cs index 04f35e1..125e687 100644 --- a/src/HTTPlease.Core/ClientBuilderExtensions.cs +++ b/src/HTTPlease.Core/ClientBuilderExtensions.cs @@ -6,219 +6,219 @@ namespace HTTPlease { - /// - /// General-purpose extensions for . - /// - public static class ClientBuilderExtensions - { - /// - /// The CLR type. - /// - static readonly Type DelegatingHandlerType = typeof(DelegatingHandler); + /// + /// General-purpose extensions for . + /// + public static class ClientBuilderExtensions + { + /// + /// The CLR type. + /// + static readonly Type DelegatingHandlerType = typeof(DelegatingHandler); - /// - /// Create a new . - /// - /// - /// The HTTP client builder. - /// - /// - /// The base URI for the HTTP client. - /// - /// - /// The client credentials used for authentication. - /// - /// - /// The new . - /// - public static HttpClient CreateClient(this ClientBuilder clientBuilder, Uri baseUri, ICredentials credentials) - { - HttpClientHandler clientHandler = null; - try - { - clientHandler = new HttpClientHandler - { - Credentials = credentials - }; + /// + /// Create a new . + /// + /// + /// The HTTP client builder. + /// + /// + /// The base URI for the HTTP client. + /// + /// + /// The client credentials used for authentication. + /// + /// + /// The new . + /// + public static HttpClient CreateClient(this ClientBuilder clientBuilder, Uri baseUri, ICredentials credentials) + { + HttpClientHandler clientHandler = null; + try + { + clientHandler = new HttpClientHandler + { + Credentials = credentials + }; - return clientBuilder.CreateClient(baseUri, clientHandler); - } - catch (Exception) - { - using (clientHandler) - { - throw; - } - } - } + return clientBuilder.CreateClient(baseUri, clientHandler); + } + catch (Exception) + { + using (clientHandler) + { + throw; + } + } + } - /// - /// Create a new . - /// - /// - /// The HTTP client builder. - /// - /// - /// The base URI for the HTTP client. - /// - /// - /// The client credentials used for authentication. - /// - /// - /// The new . - /// - public static HttpClient CreateClient(this ClientBuilder clientBuilder, string baseUri, ICredentials credentials) - { - HttpClientHandler clientHandler = null; - try - { - clientHandler = new HttpClientHandler - { - Credentials = credentials - }; + /// + /// Create a new . + /// + /// + /// The HTTP client builder. + /// + /// + /// The base URI for the HTTP client. + /// + /// + /// The client credentials used for authentication. + /// + /// + /// The new . + /// + public static HttpClient CreateClient(this ClientBuilder clientBuilder, string baseUri, ICredentials credentials) + { + HttpClientHandler clientHandler = null; + try + { + clientHandler = new HttpClientHandler + { + Credentials = credentials + }; - return clientBuilder.CreateClient(baseUri, clientHandler); - } - catch (Exception) - { - using (clientHandler) - { - throw; - } - } - } + return clientBuilder.CreateClient(baseUri, clientHandler); + } + catch (Exception) + { + using (clientHandler) + { + throw; + } + } + } - /// - /// Create a new . - /// - /// - /// The HTTP client builder. - /// - /// - /// The base URI for the HTTP client. - /// - /// - /// An optional that will form the message pipeline terminus. - /// - /// If not specified, a new is used. - /// - /// - /// The new . - /// - public static HttpClient CreateClient(this ClientBuilder clientBuilder, string baseUri, HttpMessageHandler messagePipelineTerminus = null) - { - if (clientBuilder == null) - throw new ArgumentNullException(nameof(clientBuilder)); + /// + /// Create a new . + /// + /// + /// The HTTP client builder. + /// + /// + /// The base URI for the HTTP client. + /// + /// + /// An optional that will form the message pipeline terminus. + /// + /// If not specified, a new is used. + /// + /// + /// The new . + /// + public static HttpClient CreateClient(this ClientBuilder clientBuilder, string baseUri, HttpMessageHandler messagePipelineTerminus = null) + { + if (clientBuilder == null) + throw new ArgumentNullException(nameof(clientBuilder)); - if (String.IsNullOrWhiteSpace(baseUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'baseUri'.", nameof(baseUri)); + if (String.IsNullOrWhiteSpace(baseUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'baseUri'.", nameof(baseUri)); - return clientBuilder.CreateClient( - new Uri(baseUri, UriKind.Absolute), - messagePipelineTerminus - ); - } + return clientBuilder.CreateClient( + new Uri(baseUri, UriKind.Absolute), + messagePipelineTerminus + ); + } - /// - /// Register a message handler type for the pipeline used by clients created by the factory. - /// - /// - /// The message handler type. - /// - /// Must be a sub-type of (not itself). - /// - /// - /// The HTTP client builder. - /// - /// - /// The client factory (enables inline use / method chaining). - /// - public static ClientBuilder WithMessageHandler(this ClientBuilder clientBuilder) - where TMessageHandler : DelegatingHandler, new() - { - if (clientBuilder == null) - throw new ArgumentNullException(nameof(clientBuilder)); + /// + /// Register a message handler type for the pipeline used by clients created by the factory. + /// + /// + /// The message handler type. + /// + /// Must be a sub-type of (not itself). + /// + /// + /// The HTTP client builder. + /// + /// + /// The client factory (enables inline use / method chaining). + /// + public static ClientBuilder WithMessageHandler(this ClientBuilder clientBuilder) + where TMessageHandler : DelegatingHandler, new() + { + if (clientBuilder == null) + throw new ArgumentNullException(nameof(clientBuilder)); - if (typeof(TMessageHandler) == DelegatingHandlerType) - throw new NotSupportedException("TMessageHandler must be a sub-type of DelegatingHandler (it cannot be DelegatingHandler)."); + if (typeof(TMessageHandler) == DelegatingHandlerType) + throw new NotSupportedException("TMessageHandler must be a sub-type of DelegatingHandler (it cannot be DelegatingHandler)."); - clientBuilder.AddHandler( - () => new TMessageHandler() - ); + clientBuilder.AddHandler( + () => new TMessageHandler() + ); - return clientBuilder; - } + return clientBuilder; + } - /// - /// Create a copy of the , but with the specified configuration action for its (message pipeline terminus). + /// + /// Create a copy of the , but with the specified configuration action for its (message pipeline terminus). /// - /// - /// The HTTP client builder. - /// + /// + /// The HTTP client builder. + /// /// - /// A delegate that configures the that will form the message pipeline terminus for each produced by the builder. - /// + /// A delegate that configures the that will form the message pipeline terminus for each produced by the builder. + /// /// - /// The configured . - /// - public static ClientBuilder ConfigureHttpClientHandler(this ClientBuilder clientBuilder, Action clientHandlerConfigurator) - { - if (clientHandlerConfigurator == null) - throw new ArgumentNullException(nameof(clientHandlerConfigurator)); - - return clientBuilder.ConfigureMessagePipelineTerminus(clientHandlerConfigurator); - } + /// The configured . + /// + public static ClientBuilder ConfigureHttpClientHandler(this ClientBuilder clientBuilder, Action clientHandlerConfigurator) + { + if (clientHandlerConfigurator == null) + throw new ArgumentNullException(nameof(clientHandlerConfigurator)); + + return clientBuilder.ConfigureMessagePipelineTerminus(clientHandlerConfigurator); + } - /// - /// Create a copy of the , but with the specified configuration action for its message pipeline terminus. + /// + /// Create a copy of the , but with the specified configuration action for its message pipeline terminus. /// - /// - /// The type of message handler to expect / configure. - /// - /// - /// The HTTP client builder. - /// + /// + /// The type of message handler to expect / configure. + /// + /// + /// The HTTP client builder. + /// /// - /// A delegate that configures the that will form the message pipeline terminus for each produced by the builder. - /// + /// A delegate that configures the that will form the message pipeline terminus for each produced by the builder. + /// /// - /// The configured . - /// - public static ClientBuilder ConfigureMessagePipelineTerminus(this ClientBuilder clientBuilder, Action pipelineTerminusConfigurator) - where TMessageHandler : HttpMessageHandler - { - if (pipelineTerminusConfigurator == null) - throw new ArgumentNullException(nameof(pipelineTerminusConfigurator)); - - return clientBuilder.WithMessagePipelineTerminus(existingTerminator => - { - if (existingTerminator == null) - throw new InvalidOperationException($"Cannot configure pipeline terminus (expected a handler of type '{typeof(TMessageHandler).FullName}', but the previous factory function returned null)."); + /// The configured . + /// + public static ClientBuilder ConfigureMessagePipelineTerminus(this ClientBuilder clientBuilder, Action pipelineTerminusConfigurator) + where TMessageHandler : HttpMessageHandler + { + if (pipelineTerminusConfigurator == null) + throw new ArgumentNullException(nameof(pipelineTerminusConfigurator)); + + return clientBuilder.WithMessagePipelineTerminus(existingTerminator => + { + if (existingTerminator == null) + throw new InvalidOperationException($"Cannot configure pipeline terminus (expected a handler of type '{typeof(TMessageHandler).FullName}', but the previous factory function returned null)."); - if (existingTerminator is TMessageHandler typedHandler) - pipelineTerminusConfigurator(typedHandler); - else - throw new InvalidOperationException($"Cannot configure pipeline terminus (expected a handler of type '{typeof(TMessageHandler).FullName}', but the previous factory function returned a handler of type '{existingTerminator.GetType().FullName}')."); - - return existingTerminator; - }); - } + if (existingTerminator is TMessageHandler typedHandler) + pipelineTerminusConfigurator(typedHandler); + else + throw new InvalidOperationException($"Cannot configure pipeline terminus (expected a handler of type '{typeof(TMessageHandler).FullName}', but the previous factory function returned a handler of type '{existingTerminator.GetType().FullName}')."); + + return existingTerminator; + }); + } - /// - /// Create a copy of the , but using the specified X.509 certificate for client authentication. + /// + /// Create a copy of the , but using the specified X.509 certificate for client authentication. /// - /// - /// The HTTP client builder. - /// + /// + /// The HTTP client builder. + /// /// - /// The X.509 certificate to use. - /// + /// The X.509 certificate to use. + /// /// - /// The configured . - /// - public static ClientBuilder WithClientCertificate(this ClientBuilder clientBuilder, X509Certificate2 clientCertificate) - { - if (clientBuilder == null) + /// The configured . + /// + public static ClientBuilder WithClientCertificate(this ClientBuilder clientBuilder, X509Certificate2 clientCertificate) + { + if (clientBuilder == null) throw new ArgumentNullException(nameof(clientBuilder)); if (clientCertificate == null) @@ -228,93 +228,93 @@ public static ClientBuilder WithClientCertificate(this ClientBuilder clientBuild throw new InvalidOperationException($"Cannot use certificate '{clientCertificate.Subject}' as a client certificate (no private key is not available for it)."); return clientBuilder.ConfigureHttpClientHandler(clientHandler => - { - clientHandler.ClientCertificates.Add(clientCertificate); - clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual; - }); - } + { + clientHandler.ClientCertificates.Add(clientCertificate); + clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual; + }); + } - /// - /// Create a copy of the , but using the specified X.509 certificate for server authentication. + /// + /// Create a copy of the , but using the specified X.509 certificate for server authentication. /// - /// - /// The HTTP client builder. - /// + /// + /// The HTTP client builder. + /// /// - /// The X.509 certificate to expect the server to use. - /// - /// - /// An optional delegate called if an unexpected error is encountered while validating the server certificate. - /// - /// Use this delegate to log the error. - /// + /// The X.509 certificate to expect the server to use. + /// + /// + /// An optional delegate called if an unexpected error is encountered while validating the server certificate. + /// + /// Use this delegate to log the error. + /// /// - /// The configured . - /// - /// - /// Will accept the certificate or (if it's a CA certificate) any certificate issued by it. - /// - public static ClientBuilder WithServerCertificate(this ClientBuilder clientBuilder, X509Certificate2 expectServerCertificate, Action logError = null) - { - if (clientBuilder == null) + /// The configured . + /// + /// + /// Will accept the certificate or (if it's a CA certificate) any certificate issued by it. + /// + public static ClientBuilder WithServerCertificate(this ClientBuilder clientBuilder, X509Certificate2 expectServerCertificate, Action logError = null) + { + if (clientBuilder == null) throw new ArgumentNullException(nameof(clientBuilder)); if (expectServerCertificate == null) throw new ArgumentNullException(nameof(expectServerCertificate)); return clientBuilder.ConfigureHttpClientHandler(clientHandler => - { - clientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, sslPolicyErrors) => - { - if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors) - return false; + { + clientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, sslPolicyErrors) => + { + if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors) + return false; - try - { - using (X509Chain certificateChain = new X509Chain()) - { - certificateChain.ChainPolicy.ExtraStore.Add(expectServerCertificate); - certificateChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - - return certificateChain.Build(certificate); - } - } - catch (Exception chainException) - { - if (logError != null) - logError(chainException); - - return false; - } - }; - }); - } + try + { + using (X509Chain certificateChain = new X509Chain()) + { + certificateChain.ChainPolicy.ExtraStore.Add(expectServerCertificate); + certificateChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + return certificateChain.Build(certificate); + } + } + catch (Exception chainException) + { + if (logError != null) + logError(chainException); + + return false; + } + }; + }); + } - /// - /// Create a copy of the , but with verification of the server's SSL certificate disabled (useful when the server has a self-signed certificate). + /// + /// Create a copy of the , but with verification of the server's SSL certificate disabled (useful when the server has a self-signed certificate). /// - /// - /// The HTTP client builder. - /// - /// - /// The configured . - /// - /// - /// Will accept any certificate. - /// - public static ClientBuilder AcceptAnyServerCertificate(this ClientBuilder clientBuilder) - { - if (clientBuilder == null) + /// + /// The HTTP client builder. + /// + /// + /// The configured . + /// + /// + /// Will accept any certificate. + /// + public static ClientBuilder AcceptAnyServerCertificate(this ClientBuilder clientBuilder) + { + if (clientBuilder == null) throw new ArgumentNullException(nameof(clientBuilder)); return clientBuilder.ConfigureHttpClientHandler(clientHandler => - { - clientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, sslPolicyErrors) => - { - return true; // Verification disabled. - }; - }); - } - } + { + clientHandler.ServerCertificateCustomValidationCallback = (request, certificate, chain, sslPolicyErrors) => + { + return true; // Verification disabled. + }; + }); + } + } } diff --git a/src/HTTPlease.Core/ClientExtensions.Streamed.cs b/src/HTTPlease.Core/ClientExtensions.Streamed.cs index 70ea30d..5f0fb03 100644 --- a/src/HTTPlease.Core/ClientExtensions.Streamed.cs +++ b/src/HTTPlease.Core/ClientExtensions.Streamed.cs @@ -6,16 +6,16 @@ namespace HTTPlease { - using Core; + using Core; - /// - /// Invocation-related extension methods for s that use an . - /// - public static partial class ClientExtensions + /// + /// Invocation-related extension methods for s that use an . + /// + public static partial class ClientExtensions { - #region Invoke (streamed) + #region Invoke (streamed) - /// + /// /// Asynchronously execute a request as a streamed HTTP GET. /// /// @@ -34,12 +34,12 @@ public static async Task GetStreamedAsync(this HttpClient h { using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get, baseUri: httpClient.BaseAddress)) { - requestMessage.MarkAsStreamed(); + requestMessage.MarkAsStreamed(); return await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead); } } - #endregion // Invoke (streamed) - } + #endregion // Invoke (streamed) + } } diff --git a/src/HTTPlease.Core/ClientExtensions.cs b/src/HTTPlease.Core/ClientExtensions.cs index bc2e798..d82619f 100644 --- a/src/HTTPlease.Core/ClientExtensions.cs +++ b/src/HTTPlease.Core/ClientExtensions.cs @@ -6,263 +6,263 @@ namespace HTTPlease { - using Core; + using Core; - /// - /// Invocation-related extension methods for s that use an . - /// - public static partial class ClientExtensions + /// + /// Invocation-related extension methods for s that use an . + /// + public static partial class ClientExtensions { - #region Invoke + #region Invoke - /// - /// Asynchronously execute a request as an HTTP HEAD. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task HeadAsync(this HttpClient httpClient, HttpRequest request, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP HEAD. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task HeadAsync(this HttpClient httpClient, HttpRequest request, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Head, cancellationToken: cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Head, cancellationToken: cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP GET. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task GetAsync(this HttpClient httpClient, HttpRequest request, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP GET. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task GetAsync(this HttpClient httpClient, HttpRequest request, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Get, cancellationToken: cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Get, cancellationToken: cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP POST. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PostAsync(this HttpClient httpClient, HttpRequest request, HttpContent postBody = null, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP POST. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PostAsync(this HttpClient httpClient, HttpRequest request, HttpContent postBody = null, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Post, postBody, cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Post, postBody, cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP PUT. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// representing the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PutAsync(this HttpClient httpClient, HttpRequest request, HttpContent putBody, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP PUT. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// representing the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PutAsync(this HttpClient httpClient, HttpRequest request, HttpContent putBody, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (putBody == null) - throw new ArgumentNullException(nameof(putBody)); + if (putBody == null) + throw new ArgumentNullException(nameof(putBody)); - return await httpClient.SendAsync(request, HttpMethod.Put, putBody, cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Put, putBody, cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP PATCH. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// representing the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PatchAsync(this HttpClient httpClient, HttpRequest request, HttpContent patchBody, CancellationToken cancellationToken = default) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously execute a request as an HTTP PATCH. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// representing the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PatchAsync(this HttpClient httpClient, HttpRequest request, HttpContent patchBody, CancellationToken cancellationToken = default) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (patchBody == null) - throw new ArgumentNullException(nameof(patchBody)); + if (patchBody == null) + throw new ArgumentNullException(nameof(patchBody)); - return await httpClient.SendAsync(request, OtherHttpMethods.Patch, patchBody, cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, OtherHttpMethods.Patch, patchBody, cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP DELETE. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task DeleteAsync(this HttpClient httpClient, HttpRequest request, CancellationToken cancellationToken = default) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously execute a request as an HTTP DELETE. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task DeleteAsync(this HttpClient httpClient, HttpRequest request, CancellationToken cancellationToken = default) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Delete, cancellationToken: cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Delete, cancellationToken: cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute the request using the specified HTTP method. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An representing the method to use. - /// - /// - /// Optional representing the request body (if any). - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task SendAsync(this HttpClient httpClient, HttpRequest request, HttpMethod method, HttpContent body = null, CancellationToken cancellationToken = default) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously execute the request using the specified HTTP method. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An representing the method to use. + /// + /// + /// Optional representing the request body (if any). + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task SendAsync(this HttpClient httpClient, HttpRequest request, HttpMethod method, HttpContent body = null, CancellationToken cancellationToken = default) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, body, httpClient.BaseAddress)) - { - HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); - try - { - request.ExecuteResponseActions(responseMessage); - } - catch - { - using (responseMessage) - { - throw; - } - } + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, body, httpClient.BaseAddress)) + { + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + try + { + request.ExecuteResponseActions(responseMessage); + } + catch + { + using (responseMessage) + { + throw; + } + } - return responseMessage; - } - } + return responseMessage; + } + } - #endregion // Invoke + #endregion // Invoke - #region Helpers + #region Helpers - /// - /// Execute the request's configured response actions (if any) against the specified response message. - /// - /// - /// The . - /// - /// - /// The HTTP response message. - /// - static void ExecuteResponseActions(this HttpRequest request, HttpResponseMessage responseMessage) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Execute the request's configured response actions (if any) against the specified response message. + /// + /// + /// The . + /// + /// + /// The HTTP response message. + /// + static void ExecuteResponseActions(this HttpRequest request, HttpResponseMessage responseMessage) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); - List responseActionExceptions = new List(); - foreach (ResponseAction responseAction in request.ResponseActions) - { - try - { - responseAction(responseMessage, HttpRequest.DefaultContext); - } - catch (Exception eResponseAction) - { - responseActionExceptions.Add(eResponseAction); - } - } + List responseActionExceptions = new List(); + foreach (ResponseAction responseAction in request.ResponseActions) + { + try + { + responseAction(responseMessage, HttpRequest.DefaultContext); + } + catch (Exception eResponseAction) + { + responseActionExceptions.Add(eResponseAction); + } + } - if (responseActionExceptions.Count > 0) - throw new AggregateException("One or more errors occurred while processing the response message.", responseActionExceptions); - } + if (responseActionExceptions.Count > 0) + throw new AggregateException("One or more errors occurred while processing the response message.", responseActionExceptions); + } - #endregion // Helpers - } + #endregion // Helpers + } } diff --git a/src/HTTPlease.Core/Core/HttpRequestBase.cs b/src/HTTPlease.Core/Core/HttpRequestBase.cs index 39f91d3..3fe7480 100644 --- a/src/HTTPlease.Core/Core/HttpRequestBase.cs +++ b/src/HTTPlease.Core/Core/HttpRequestBase.cs @@ -5,259 +5,259 @@ namespace HTTPlease.Core { - using Utilities; - - using RequestProperties = ImmutableDictionary; - - /// - /// The base class for HTTP request templates. - /// - public abstract class HttpRequestBase - : IHttpRequestProperties - { - #region Instance data - - /// - /// The request properties. - /// - readonly RequestProperties _properties; - - #endregion // Instance data - - #region Construction - - /// - /// Create a new HTTP request. - /// - /// - /// The request properties. - /// - protected HttpRequestBase(ImmutableDictionary properties) - { - if (properties == null) - throw new ArgumentNullException(nameof(properties)); - - _properties = properties; - - EnsurePropertyType(nameof(Uri)); - EnsurePropertyType(nameof(IsUriTemplate)); - } - - #endregion // Construction - - #region IHttpRequest - - /// - /// The request URI. - /// - public Uri Uri => GetOptionalProperty(); - - /// - /// Is the request URI a template? - /// - public bool IsUriTemplate => GetOptionalProperty(); - - /// - /// All properties for the request. - /// - public ImmutableDictionary Properties => _properties; - - #endregion // IHttpRequest - - #region Request properties - - /// - /// Determine whether the specified property is defined for the request. - /// - /// - /// The property name. - /// - /// - /// true, if the request is defined; otherwise, false. - /// - protected bool HaveProperty([CallerMemberName] string propertyName = null) - { - if (String.IsNullOrWhiteSpace(propertyName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); - - return _properties.ContainsKey(propertyName); - } - - /// - /// Get the specified request property. - /// - /// - /// The type of property to retrieve. - /// - /// - /// The name of the property to retrieve. - /// - /// - /// The property value. - /// - /// - /// is null, empty, or entirely composed of whitespace. - /// - /// - /// The specified property is not defined. - /// - protected TProperty GetProperty([CallerMemberName] string propertyName = null) - { - if (String.IsNullOrWhiteSpace(propertyName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); - - object propertyValue; - if (!_properties.TryGetValue(propertyName, out propertyValue)) - throw new KeyNotFoundException($"Property '{propertyName}' is not defined."); - - return (TProperty)propertyValue; - } - - /// - /// Get the specified request property. - /// - /// - /// The type of property to retrieve. - /// - /// - /// The name of the property to retrieve. - /// - /// - /// The default value to return if the property is not defined. - /// - /// - /// The property value, or the default value if the property is not defined. - /// - /// - /// is null, empty, or entirely composed of whitespace. - /// - /// - /// The specified property is not defined. - /// - protected TProperty GetOptionalProperty([CallerMemberName] string propertyName = null, TProperty defaultValue = default(TProperty)) - { - if (String.IsNullOrWhiteSpace(propertyName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); - - object propertyValue; - if (_properties.TryGetValue(propertyName, out propertyValue)) - return (TProperty)propertyValue; - - return defaultValue; - } - - /// - /// Ensure that the specified property (if defined) is of the correct type. - /// - /// - /// The expected property type. - /// - /// - /// The name of the property to validate. - /// - /// - /// is null, empty, or entirely composed of whitespace. - /// - protected void EnsurePropertyType(string propertyName) - { - if (String.IsNullOrWhiteSpace(propertyName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); - - object propertyValue; - if (!_properties.TryGetValue(propertyName, out propertyValue)) - return; - - if (propertyValue is TProperty) - return; - - // It's not of the correct type, but is that because it's null? - Type propertyType = typeof(TProperty); - if (propertyValue != null) - { - throw new InvalidOperationException( - $"Value for property '{propertyName}' has unexpected type '{propertyType.FullName}' (should be '{propertyValue.GetType().FullName}')." - ); - } - - // It's null; is that legal? - if (typeof(TProperty).IsNullable()) - return; - - throw new InvalidOperationException( - $"Property '{propertyName}' is null but its type ('{propertyType.FullName}') is not nullable." - ); - } - - /// - /// Clone the request properties, but with the specified changes. - /// - /// - /// A delegate that modifies the request properties. - /// - /// - /// The cloned request properties. - /// - protected ImmutableDictionary CloneProperties(Action modifications) - { - if (modifications == null) - throw new ArgumentNullException(nameof(modifications)); - - RequestProperties.Builder requestProperties = _properties.ToBuilder(); - modifications(requestProperties); - - return requestProperties.ToImmutable(); - } - - #endregion // Request properties - - #region Cloning - - /// - /// Clone the request. - /// - /// - /// A delegate that performs modifications to the request properties. - /// - /// - /// The cloned request. - /// - public virtual HttpRequestBase Clone(Action> modifications) - { - if (modifications == null) - throw new ArgumentNullException(nameof(modifications)); - - return CreateInstance( - CloneProperties(modifications) - ); - } - - /// - /// Create a new instance of the HTTP request using the specified properties. - /// - /// - /// The request properties. - /// - /// - /// The new HTTP request instance. - /// - protected abstract HttpRequestBase CreateInstance(ImmutableDictionary requestProperties); - - #endregion // Cloning - - #region ToString - - /// - /// Convert the HTTP request to a textual representation. - /// - /// - /// The textual representation. - /// - public override string ToString() - { - return $"HTTP Request ({Uri?.ToString() ?? "empty"})"; - } - - #endregion // ToString - } + using Utilities; + + using RequestProperties = ImmutableDictionary; + + /// + /// The base class for HTTP request templates. + /// + public abstract class HttpRequestBase + : IHttpRequestProperties + { + #region Instance data + + /// + /// The request properties. + /// + readonly RequestProperties _properties; + + #endregion // Instance data + + #region Construction + + /// + /// Create a new HTTP request. + /// + /// + /// The request properties. + /// + protected HttpRequestBase(ImmutableDictionary properties) + { + if (properties == null) + throw new ArgumentNullException(nameof(properties)); + + _properties = properties; + + EnsurePropertyType(nameof(Uri)); + EnsurePropertyType(nameof(IsUriTemplate)); + } + + #endregion // Construction + + #region IHttpRequest + + /// + /// The request URI. + /// + public Uri Uri => GetOptionalProperty(); + + /// + /// Is the request URI a template? + /// + public bool IsUriTemplate => GetOptionalProperty(); + + /// + /// All properties for the request. + /// + public ImmutableDictionary Properties => _properties; + + #endregion // IHttpRequest + + #region Request properties + + /// + /// Determine whether the specified property is defined for the request. + /// + /// + /// The property name. + /// + /// + /// true, if the request is defined; otherwise, false. + /// + protected bool HaveProperty([CallerMemberName] string propertyName = null) + { + if (String.IsNullOrWhiteSpace(propertyName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); + + return _properties.ContainsKey(propertyName); + } + + /// + /// Get the specified request property. + /// + /// + /// The type of property to retrieve. + /// + /// + /// The name of the property to retrieve. + /// + /// + /// The property value. + /// + /// + /// is null, empty, or entirely composed of whitespace. + /// + /// + /// The specified property is not defined. + /// + protected TProperty GetProperty([CallerMemberName] string propertyName = null) + { + if (String.IsNullOrWhiteSpace(propertyName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); + + object propertyValue; + if (!_properties.TryGetValue(propertyName, out propertyValue)) + throw new KeyNotFoundException($"Property '{propertyName}' is not defined."); + + return (TProperty)propertyValue; + } + + /// + /// Get the specified request property. + /// + /// + /// The type of property to retrieve. + /// + /// + /// The name of the property to retrieve. + /// + /// + /// The default value to return if the property is not defined. + /// + /// + /// The property value, or the default value if the property is not defined. + /// + /// + /// is null, empty, or entirely composed of whitespace. + /// + /// + /// The specified property is not defined. + /// + protected TProperty GetOptionalProperty([CallerMemberName] string propertyName = null, TProperty defaultValue = default(TProperty)) + { + if (String.IsNullOrWhiteSpace(propertyName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); + + object propertyValue; + if (_properties.TryGetValue(propertyName, out propertyValue)) + return (TProperty)propertyValue; + + return defaultValue; + } + + /// + /// Ensure that the specified property (if defined) is of the correct type. + /// + /// + /// The expected property type. + /// + /// + /// The name of the property to validate. + /// + /// + /// is null, empty, or entirely composed of whitespace. + /// + protected void EnsurePropertyType(string propertyName) + { + if (String.IsNullOrWhiteSpace(propertyName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'propertyName'.", nameof(propertyName)); + + object propertyValue; + if (!_properties.TryGetValue(propertyName, out propertyValue)) + return; + + if (propertyValue is TProperty) + return; + + // It's not of the correct type, but is that because it's null? + Type propertyType = typeof(TProperty); + if (propertyValue != null) + { + throw new InvalidOperationException( + $"Value for property '{propertyName}' has unexpected type '{propertyType.FullName}' (should be '{propertyValue.GetType().FullName}')." + ); + } + + // It's null; is that legal? + if (typeof(TProperty).IsNullable()) + return; + + throw new InvalidOperationException( + $"Property '{propertyName}' is null but its type ('{propertyType.FullName}') is not nullable." + ); + } + + /// + /// Clone the request properties, but with the specified changes. + /// + /// + /// A delegate that modifies the request properties. + /// + /// + /// The cloned request properties. + /// + protected ImmutableDictionary CloneProperties(Action modifications) + { + if (modifications == null) + throw new ArgumentNullException(nameof(modifications)); + + RequestProperties.Builder requestProperties = _properties.ToBuilder(); + modifications(requestProperties); + + return requestProperties.ToImmutable(); + } + + #endregion // Request properties + + #region Cloning + + /// + /// Clone the request. + /// + /// + /// A delegate that performs modifications to the request properties. + /// + /// + /// The cloned request. + /// + public virtual HttpRequestBase Clone(Action> modifications) + { + if (modifications == null) + throw new ArgumentNullException(nameof(modifications)); + + return CreateInstance( + CloneProperties(modifications) + ); + } + + /// + /// Create a new instance of the HTTP request using the specified properties. + /// + /// + /// The request properties. + /// + /// + /// The new HTTP request instance. + /// + protected abstract HttpRequestBase CreateInstance(ImmutableDictionary requestProperties); + + #endregion // Cloning + + #region ToString + + /// + /// Convert the HTTP request to a textual representation. + /// + /// + /// The textual representation. + /// + public override string ToString() + { + return $"HTTP Request ({Uri?.ToString() ?? "empty"})"; + } + + #endregion // ToString + } } diff --git a/src/HTTPlease.Core/Core/RequestActions.cs b/src/HTTPlease.Core/Core/RequestActions.cs index 9887dd0..b39df5d 100644 --- a/src/HTTPlease.Core/Core/RequestActions.cs +++ b/src/HTTPlease.Core/Core/RequestActions.cs @@ -2,25 +2,25 @@ namespace HTTPlease.Core { - /// - /// Delegate that performs configuration of an outgoing HTTP request message. - /// - /// - /// The outgoing request message. - /// - public delegate void RequestAction(HttpRequestMessage requestMessage); - - /// - /// Delegate that performs configuration of an outgoing HTTP request message. - /// - /// - /// The type of object used by the request when resolving deferred parameters. - /// - /// - /// The outgoing request message. - /// - /// - /// The object used by the request when resolving deferred parameters. - /// - public delegate void RequestAction(HttpRequestMessage requestMessage, TContext context); + /// + /// Delegate that performs configuration of an outgoing HTTP request message. + /// + /// + /// The outgoing request message. + /// + public delegate void RequestAction(HttpRequestMessage requestMessage); + + /// + /// Delegate that performs configuration of an outgoing HTTP request message. + /// + /// + /// The type of object used by the request when resolving deferred parameters. + /// + /// + /// The outgoing request message. + /// + /// + /// The object used by the request when resolving deferred parameters. + /// + public delegate void RequestAction(HttpRequestMessage requestMessage, TContext context); } diff --git a/src/HTTPlease.Core/Core/ResponseActions.cs b/src/HTTPlease.Core/Core/ResponseActions.cs index b8b63ce..e887196 100644 --- a/src/HTTPlease.Core/Core/ResponseActions.cs +++ b/src/HTTPlease.Core/Core/ResponseActions.cs @@ -2,25 +2,25 @@ namespace HTTPlease.Core { - /// - /// Delegate that performs processing of an incoming HTTP response message. - /// - /// - /// The incoming response message. - /// - public delegate void ResponseAction(HttpResponseMessage responseMessage); - - /// - /// Delegate that performs processing of an incoming HTTP response message. - /// - /// - /// The type of object used by the response when resolving deferred parameters. - /// - /// - /// The incoming response message. - /// - /// - /// The object used by the response when resolving deferred parameters. - /// - public delegate void ResponseAction(HttpResponseMessage responseMessage, TContext context); + /// + /// Delegate that performs processing of an incoming HTTP response message. + /// + /// + /// The incoming response message. + /// + public delegate void ResponseAction(HttpResponseMessage responseMessage); + + /// + /// Delegate that performs processing of an incoming HTTP response message. + /// + /// + /// The type of object used by the response when resolving deferred parameters. + /// + /// + /// The incoming response message. + /// + /// + /// The object used by the response when resolving deferred parameters. + /// + public delegate void ResponseAction(HttpResponseMessage responseMessage, TContext context); } diff --git a/src/HTTPlease.Core/Core/Templates/ITemplateEvaluationContext.cs b/src/HTTPlease.Core/Core/Templates/ITemplateEvaluationContext.cs index 9220fa7..96002c9 100644 --- a/src/HTTPlease.Core/Core/Templates/ITemplateEvaluationContext.cs +++ b/src/HTTPlease.Core/Core/Templates/ITemplateEvaluationContext.cs @@ -3,45 +3,45 @@ namespace HTTPlease.Core.Templates { - /// - /// Represents the evaluation context for a URI template. - /// - interface ITemplateEvaluationContext - { - /// - /// Determine whether the specified parameter is defined. - /// - /// - /// The parameter name. - /// - /// - /// true, if the parameter is defined; otherwise, false. - /// - bool IsParameterDefined(string parameterName); + /// + /// Represents the evaluation context for a URI template. + /// + interface ITemplateEvaluationContext + { + /// + /// Determine whether the specified parameter is defined. + /// + /// + /// The parameter name. + /// + /// + /// true, if the parameter is defined; otherwise, false. + /// + bool IsParameterDefined(string parameterName); - /// - /// The value of the specified template parameter - /// - /// - /// The name of the template parameter. - /// - /// - /// Is the parameter optional? If so, return null if it is not present, rather than throwing an exception. - /// - /// Default is true. - /// - /// - /// The parameter value, or null. - /// - /// - /// is null, empty, or entirely composed of whitespace. - /// - /// - /// The parameter is not , and is not preset. - /// - string this[string parameterName, bool isOptional = false] - { - get; - } - } + /// + /// The value of the specified template parameter + /// + /// + /// The name of the template parameter. + /// + /// + /// Is the parameter optional? If so, return null if it is not present, rather than throwing an exception. + /// + /// Default is true. + /// + /// + /// The parameter value, or null. + /// + /// + /// is null, empty, or entirely composed of whitespace. + /// + /// + /// The parameter is not , and is not preset. + /// + string this[string parameterName, bool isOptional = false] + { + get; + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs b/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs index c099f3a..9505152 100644 --- a/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs +++ b/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs @@ -2,61 +2,61 @@ namespace HTTPlease.Core.Templates { - /// - /// A template segment that represents a literal query parameter (i.e. one that has a constant value). - /// - sealed class LiteralQuerySegment - : QuerySegment - { - /// - /// The value for the query parameter that the segment represents. - /// - readonly string _queryParameterValue; + /// + /// A template segment that represents a literal query parameter (i.e. one that has a constant value). + /// + sealed class LiteralQuerySegment + : QuerySegment + { + /// + /// The value for the query parameter that the segment represents. + /// + readonly string _queryParameterValue; - /// - /// Create a new literal query segment. - /// - /// - /// The name of the query parameter that the segment represents. - /// - /// - /// The value for the query parameter that the segment represents. - /// - public LiteralQuerySegment(string queryParameterName, string queryParameterValue) - : base(queryParameterName) - { - if (String.IsNullOrWhiteSpace(queryParameterValue)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(queryParameterValue)); + /// + /// Create a new literal query segment. + /// + /// + /// The name of the query parameter that the segment represents. + /// + /// + /// The value for the query parameter that the segment represents. + /// + public LiteralQuerySegment(string queryParameterName, string queryParameterValue) + : base(queryParameterName) + { + if (String.IsNullOrWhiteSpace(queryParameterValue)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(queryParameterValue)); - _queryParameterValue = queryParameterValue; - } + _queryParameterValue = queryParameterValue; + } - /// - /// The value for the query parameter that the segment represents. - /// - public string QueryParameterValue - { - get - { - return _queryParameterValue; - } - } + /// + /// The value for the query parameter that the segment represents. + /// + public string QueryParameterValue + { + get + { + return _queryParameterValue; + } + } - /// - /// Get the value of the segment (if any). - /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment has no value. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) - { - if (evaluationContext == null) - throw new ArgumentNullException(nameof(evaluationContext)); + /// + /// Get the value of the segment (if any). + /// + /// + /// The current template evaluation context. + /// + /// + /// The segment value, or null if the segment has no value. + /// + public override string GetValue(ITemplateEvaluationContext evaluationContext) + { + if (evaluationContext == null) + throw new ArgumentNullException(nameof(evaluationContext)); - return _queryParameterValue; - } - } + return _queryParameterValue; + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs b/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs index 052cb2b..ed9216c 100644 --- a/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs @@ -2,61 +2,61 @@ namespace HTTPlease.Core.Templates { - /// - /// Represents a literal URI segment (i.e. one that has a constant value). - /// - sealed class LiteralUriSegment - : UriSegment - { - /// - /// The segment value; - /// - readonly string _value; + /// + /// Represents a literal URI segment (i.e. one that has a constant value). + /// + sealed class LiteralUriSegment + : UriSegment + { + /// + /// The segment value; + /// + readonly string _value; - /// - /// Create a new literal URI segment. - /// - /// - /// The segment value. - /// - /// - /// Does the segment represent a directory (i.e. have a trailing slash?). - /// - public LiteralUriSegment(string value, bool isDirectory) - : base(isDirectory) - { - if (String.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(value)); + /// + /// Create a new literal URI segment. + /// + /// + /// The segment value. + /// + /// + /// Does the segment represent a directory (i.e. have a trailing slash?). + /// + public LiteralUriSegment(string value, bool isDirectory) + : base(isDirectory) + { + if (String.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(value)); - _value = value; - } + _value = value; + } - /// - /// The segment value; - /// - public string Value - { - get - { - return _value; - } - } + /// + /// The segment value; + /// + public string Value + { + get + { + return _value; + } + } - /// - /// Get the value of the segment (if any). - /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment is missing. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) - { - if (evaluationContext == null) - throw new ArgumentNullException(nameof(evaluationContext)); + /// + /// Get the value of the segment (if any). + /// + /// + /// The current template evaluation context. + /// + /// + /// The segment value, or null if the segment is missing. + /// + public override string GetValue(ITemplateEvaluationContext evaluationContext) + { + if (evaluationContext == null) + throw new ArgumentNullException(nameof(evaluationContext)); - return _value; - } - } + return _value; + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs b/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs index d33547b..96e96e5 100644 --- a/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs +++ b/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs @@ -2,92 +2,92 @@ namespace HTTPlease.Core.Templates { - /// - /// A template segment that represents a query parameter whose value comes from a template parameter. - /// - sealed class ParameterizedQuerySegment - : QuerySegment - { - /// - /// The name of the template parameter whose value becomes the query parameter. - /// - readonly string _templateParameterName; + /// + /// A template segment that represents a query parameter whose value comes from a template parameter. + /// + sealed class ParameterizedQuerySegment + : QuerySegment + { + /// + /// The name of the template parameter whose value becomes the query parameter. + /// + readonly string _templateParameterName; - /// - /// Is the segment optional? - /// - /// - /// If true, then the query parameter will be omitted if its associated template variable is not defined. - /// - readonly bool _isOptional; + /// + /// Is the segment optional? + /// + /// + /// If true, then the query parameter will be omitted if its associated template variable is not defined. + /// + readonly bool _isOptional; - /// - /// Create a new literal query segment. - /// - /// - /// The name of the query parameter that the segment represents. - /// - /// - /// The value for the query parameter that the segment represents. - /// - /// - /// Is the segment optional? - /// - public ParameterizedQuerySegment(string queryParameterName, string templateParameterName, bool isOptional = false) - : base(queryParameterName) - { - if (String.IsNullOrWhiteSpace(templateParameterName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(templateParameterName)); + /// + /// Create a new literal query segment. + /// + /// + /// The name of the query parameter that the segment represents. + /// + /// + /// The value for the query parameter that the segment represents. + /// + /// + /// Is the segment optional? + /// + public ParameterizedQuerySegment(string queryParameterName, string templateParameterName, bool isOptional = false) + : base(queryParameterName) + { + if (String.IsNullOrWhiteSpace(templateParameterName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(templateParameterName)); - _templateParameterName = templateParameterName; - _isOptional = isOptional; - } + _templateParameterName = templateParameterName; + _isOptional = isOptional; + } - /// - /// The name of the template parameter whose value becomes the query parameter. - /// - public string TemplateParameterName - { - get - { - return _templateParameterName; - } - } + /// + /// The name of the template parameter whose value becomes the query parameter. + /// + public string TemplateParameterName + { + get + { + return _templateParameterName; + } + } - /// - /// Is the segment optional? - /// - /// - /// If true, then the query parameter will be omitted if its associated template variable is not defined. - /// - public bool IsOptional - { - get - { - return _isOptional; - } - } + /// + /// Is the segment optional? + /// + /// + /// If true, then the query parameter will be omitted if its associated template variable is not defined. + /// + public bool IsOptional + { + get + { + return _isOptional; + } + } - /// - /// Does the segment have a parameterised (non-constant) value? - /// - public override bool IsParameterized => true; + /// + /// Does the segment have a parameterised (non-constant) value? + /// + public override bool IsParameterized => true; - /// - /// Get the value of the segment (if any). - /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment has no value. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) - { - if (evaluationContext == null) - throw new ArgumentNullException(nameof(evaluationContext)); + /// + /// Get the value of the segment (if any). + /// + /// + /// The current template evaluation context. + /// + /// + /// The segment value, or null if the segment has no value. + /// + public override string GetValue(ITemplateEvaluationContext evaluationContext) + { + if (evaluationContext == null) + throw new ArgumentNullException(nameof(evaluationContext)); - return evaluationContext[_templateParameterName, _isOptional]; - } - } + return evaluationContext[_templateParameterName, _isOptional]; + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs b/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs index cc0b76b..7b19ef7 100644 --- a/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs @@ -2,94 +2,94 @@ namespace HTTPlease.Core.Templates { - /// - /// Represents a literal URI segment (i.e. one that has a constant value). - /// - sealed class ParameterizedUriSegment - : UriSegment - { - /// - /// The name of the parameter from which the URI segment obtains its value. - /// - readonly string _templateParameterName; + /// + /// Represents a literal URI segment (i.e. one that has a constant value). + /// + sealed class ParameterizedUriSegment + : UriSegment + { + /// + /// The name of the parameter from which the URI segment obtains its value. + /// + readonly string _templateParameterName; - /// - /// Is the segment optional? - /// - /// - /// If true, then the segment is not rendered when its associated parameter is missing. - /// - readonly bool _isOptional; + /// + /// Is the segment optional? + /// + /// + /// If true, then the segment is not rendered when its associated parameter is missing. + /// + readonly bool _isOptional; - /// - /// Create a new literal URI segment. - /// - /// - /// The name of the parameter from which the URI segment obtains its value. - /// - /// - /// Does the segment represent a directory (i.e. have a trailing slash?). - /// - /// - /// Is the segment optional? - /// - /// Default is false. - /// - public ParameterizedUriSegment(string templateParameterName, bool isDirectory, bool isOptional = false) - : base(isDirectory) - { - if (String.IsNullOrWhiteSpace(templateParameterName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(templateParameterName)); + /// + /// Create a new literal URI segment. + /// + /// + /// The name of the parameter from which the URI segment obtains its value. + /// + /// + /// Does the segment represent a directory (i.e. have a trailing slash?). + /// + /// + /// Is the segment optional? + /// + /// Default is false. + /// + public ParameterizedUriSegment(string templateParameterName, bool isDirectory, bool isOptional = false) + : base(isDirectory) + { + if (String.IsNullOrWhiteSpace(templateParameterName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(templateParameterName)); - _templateParameterName = templateParameterName; - _isOptional = isOptional; - } + _templateParameterName = templateParameterName; + _isOptional = isOptional; + } - /// - /// The name of the parameter from which the URI segment obtains its value. - /// - public string TemplateParameterName - { - get - { - return _templateParameterName; - } - } + /// + /// The name of the parameter from which the URI segment obtains its value. + /// + public string TemplateParameterName + { + get + { + return _templateParameterName; + } + } - /// - /// Is the segment optional? - /// - /// - /// If true, then the segment is not rendered when its associated parameter is missing. - /// - public bool IsOptional - { - get - { - return _isOptional; - } - } + /// + /// Is the segment optional? + /// + /// + /// If true, then the segment is not rendered when its associated parameter is missing. + /// + public bool IsOptional + { + get + { + return _isOptional; + } + } - /// - /// Does the segment have a parameterised (non-constant) value? - /// - public override bool IsParameterized => true; + /// + /// Does the segment have a parameterised (non-constant) value? + /// + public override bool IsParameterized => true; - /// - /// Get the value of the segment (if any). - /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment is missing. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) - { - if (evaluationContext == null) - throw new ArgumentNullException(nameof(evaluationContext)); + /// + /// Get the value of the segment (if any). + /// + /// + /// The current template evaluation context. + /// + /// + /// The segment value, or null if the segment is missing. + /// + public override string GetValue(ITemplateEvaluationContext evaluationContext) + { + if (evaluationContext == null) + throw new ArgumentNullException(nameof(evaluationContext)); - return evaluationContext[_templateParameterName, _isOptional]; - } - } + return evaluationContext[_templateParameterName, _isOptional]; + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/QuerySegment.cs b/src/HTTPlease.Core/Core/Templates/QuerySegment.cs index 8131a6b..6b202ec 100644 --- a/src/HTTPlease.Core/Core/Templates/QuerySegment.cs +++ b/src/HTTPlease.Core/Core/Templates/QuerySegment.cs @@ -2,40 +2,40 @@ namespace HTTPlease.Core.Templates { - /// - /// The base class for template segments that represent components of a URI's query. - /// - abstract class QuerySegment - : TemplateSegment - { - /// - /// The name of the query parameter that the segment represents. - /// - readonly string _queryParameterName; + /// + /// The base class for template segments that represent components of a URI's query. + /// + abstract class QuerySegment + : TemplateSegment + { + /// + /// The name of the query parameter that the segment represents. + /// + readonly string _queryParameterName; - /// - /// Create a new query segment. - /// - /// - /// The name of the query parameter that the segment represents. - /// - protected QuerySegment(string queryParameterName) - { - if (String.IsNullOrWhiteSpace(queryParameterName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'queryParameterName'.", nameof(queryParameterName)); + /// + /// Create a new query segment. + /// + /// + /// The name of the query parameter that the segment represents. + /// + protected QuerySegment(string queryParameterName) + { + if (String.IsNullOrWhiteSpace(queryParameterName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'queryParameterName'.", nameof(queryParameterName)); - _queryParameterName = queryParameterName; - } + _queryParameterName = queryParameterName; + } - /// - /// The name of the query parameter that the segment represents. - /// - public string QueryParameterName - { - get - { - return _queryParameterName; - } - } - } + /// + /// The name of the query parameter that the segment represents. + /// + public string QueryParameterName + { + get + { + return _queryParameterName; + } + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs b/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs index 180534a..c57ed3b 100644 --- a/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs @@ -2,40 +2,40 @@ namespace HTTPlease.Core.Templates { - /// - /// A literal URI segment representing the root folder ("/"). - /// - sealed class RootUriSegment - : UriSegment - { - /// - /// The singleton instance of the root URI segment. - /// - public static readonly RootUriSegment Instance = new RootUriSegment(); + /// + /// A literal URI segment representing the root folder ("/"). + /// + sealed class RootUriSegment + : UriSegment + { + /// + /// The singleton instance of the root URI segment. + /// + public static readonly RootUriSegment Instance = new RootUriSegment(); - /// - /// Create a new literal URI segment. - /// - RootUriSegment() - : base(isDirectory: true) - { - } + /// + /// Create a new literal URI segment. + /// + RootUriSegment() + : base(isDirectory: true) + { + } - /// - /// Get the value of the segment (if any). - /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment is missing. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) - { - if (evaluationContext == null) - throw new ArgumentNullException(nameof(evaluationContext)); + /// + /// Get the value of the segment (if any). + /// + /// + /// The current template evaluation context. + /// + /// + /// The segment value, or null if the segment is missing. + /// + public override string GetValue(ITemplateEvaluationContext evaluationContext) + { + if (evaluationContext == null) + throw new ArgumentNullException(nameof(evaluationContext)); - return String.Empty; - } - } + return String.Empty; + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/TemplateEvaluationContext.cs b/src/HTTPlease.Core/Core/Templates/TemplateEvaluationContext.cs index 16505d4..358027d 100644 --- a/src/HTTPlease.Core/Core/Templates/TemplateEvaluationContext.cs +++ b/src/HTTPlease.Core/Core/Templates/TemplateEvaluationContext.cs @@ -3,108 +3,108 @@ namespace HTTPlease.Core.Templates { - /// - /// The default evaluation context for a URI template. - /// - sealed class TemplateEvaluationContext - : ITemplateEvaluationContext - { - /// - /// The template parameters. - /// - readonly Dictionary _templateParameters = new Dictionary(); + /// + /// The default evaluation context for a URI template. + /// + sealed class TemplateEvaluationContext + : ITemplateEvaluationContext + { + /// + /// The template parameters. + /// + readonly Dictionary _templateParameters = new Dictionary(); - /// - /// Create a new template evaluation context. - /// - public TemplateEvaluationContext() - { - } + /// + /// Create a new template evaluation context. + /// + public TemplateEvaluationContext() + { + } - /// - /// Create a new template evaluation context. - /// - /// - /// A dictionary of template parameters (and their values) used to populate the evaluation context. - /// - public TemplateEvaluationContext(IDictionary templateParameters) - { - if (templateParameters == null) - throw new ArgumentNullException(nameof(templateParameters)); + /// + /// Create a new template evaluation context. + /// + /// + /// A dictionary of template parameters (and their values) used to populate the evaluation context. + /// + public TemplateEvaluationContext(IDictionary templateParameters) + { + if (templateParameters == null) + throw new ArgumentNullException(nameof(templateParameters)); - foreach (KeyValuePair templateParameter in templateParameters) - _templateParameters[templateParameter.Key] = templateParameter.Value; - } + foreach (KeyValuePair templateParameter in templateParameters) + _templateParameters[templateParameter.Key] = templateParameter.Value; + } - /// - /// The template parameters. - /// - public Dictionary TemplateParameters - { - get - { - return _templateParameters; - } - } + /// + /// The template parameters. + /// + public Dictionary TemplateParameters + { + get + { + return _templateParameters; + } + } - /// - /// Determine whether the specified parameter is defined. - /// - /// - /// The parameter name. - /// - /// - /// true, if the parameter is defined; otherwise, false. - /// - public bool IsParameterDefined(string parameterName) - { - if (String.IsNullOrWhiteSpace(parameterName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'parameterName'.", nameof(parameterName)); + /// + /// Determine whether the specified parameter is defined. + /// + /// + /// The parameter name. + /// + /// + /// true, if the parameter is defined; otherwise, false. + /// + public bool IsParameterDefined(string parameterName) + { + if (String.IsNullOrWhiteSpace(parameterName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'parameterName'.", nameof(parameterName)); - return _templateParameters.ContainsKey(parameterName); - } + return _templateParameters.ContainsKey(parameterName); + } - /// - /// The value of the specified template parameter - /// - /// - /// The name of the template parameter. - /// - /// - /// Is the parameter optional? If so, return null if it is not present, rather than throwing an exception. - /// - /// Default is true. - /// - /// - /// The parameter value, or null. - /// - /// - /// is null, empty, or entirely composed of whitespace. - /// - /// - /// The parameter is not , and is not preset. - /// - public string this[string parameterName, bool isOptional] - { - get - { - if (String.IsNullOrWhiteSpace(parameterName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'parameterName'.", nameof(parameterName)); + /// + /// The value of the specified template parameter + /// + /// + /// The name of the template parameter. + /// + /// + /// Is the parameter optional? If so, return null if it is not present, rather than throwing an exception. + /// + /// Default is true. + /// + /// + /// The parameter value, or null. + /// + /// + /// is null, empty, or entirely composed of whitespace. + /// + /// + /// The parameter is not , and is not preset. + /// + public string this[string parameterName, bool isOptional] + { + get + { + if (String.IsNullOrWhiteSpace(parameterName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'parameterName'.", nameof(parameterName)); - string parameterValue; - if (!_templateParameters.TryGetValue(parameterName, out parameterValue)) - { - if (!isOptional) - { - throw new UriTemplateException( - "Required template parameter '{0}' is not defined.", - parameterName - ); - } - } + string parameterValue; + if (!_templateParameters.TryGetValue(parameterName, out parameterValue)) + { + if (!isOptional) + { + throw new UriTemplateException( + "Required template parameter '{0}' is not defined.", + parameterName + ); + } + } - return parameterValue; - } - } - } + return parameterValue; + } + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs b/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs index ba5999e..d0a336a 100644 --- a/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs @@ -5,195 +5,195 @@ namespace HTTPlease.Core.Templates { - /// - /// The base class for the segments that comprise a URI template. - /// - abstract class TemplateSegment - { - /// - /// The regular expression used to match variables. - /// - static readonly Regex VariableRegex = new Regex( - @"\{(?\w+)(?\?)?\}\/?", - RegexOptions.Compiled | RegexOptions.Singleline - ); - - /// - /// Create a new URI template segment. - /// - protected TemplateSegment() - { - } - - /// - /// Does the segment have a parameterised (non-constant) value? - /// - public virtual bool IsParameterized => true; - - /// - /// Get the value of the segment (if any). - /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment has no value. - /// - public abstract string GetValue(ITemplateEvaluationContext evaluationContext); - - /// - /// Parse the specified URI into template segments. - /// - /// - /// The URI to parse. - /// - /// - /// The template segments. - /// - public static IReadOnlyList Parse(string template) - { - if (template == null) - throw new ArgumentNullException(nameof(template)); - - List segments = new List(); - - try - { - Uri templateUri = new Uri( - new Uri("http://localhost/"), - template.Replace("?}", "%3F}") // Special case for '?' because it messes with Uri's parser. - ); - segments.AddRange( - ParsePathSegments(templateUri) - ); - segments.AddRange( - ParseQuerySegments(templateUri) - ); - } - catch (Exception eParse) - { - throw new UriTemplateException(eParse, "'{0}' is not a valid URI template.", template); - } - - return segments; - } - - /// - /// Parse URI segments from the specified template. - /// - /// - /// The URI template. - /// - /// - /// A sequence of 0 or more URI segments. - /// - static IEnumerable ParsePathSegments(Uri template) - { - if (template == null) - throw new ArgumentNullException(nameof(template)); - - bool haveRoot = false; - bool isLastSegmentDirectory = template.AbsolutePath[template.AbsolutePath.Length - 1] == '/'; - - string[] pathSegments = - template.AbsolutePath - .Split('/') - .Select( - segment => Uri.UnescapeDataString(segment) - ) - .ToArray(); - - int lastSegmentIndex = pathSegments.Length - 1; - for (int segmentIndex = 0; segmentIndex < pathSegments.Length; segmentIndex++) - { - string pathSegment = pathSegments[segmentIndex]; - if (pathSegment != String.Empty) - { - bool isDirectory = isLastSegmentDirectory || segmentIndex < lastSegmentIndex; - - Match variableMatch = VariableRegex.Match(pathSegment); - if (variableMatch.Success) - { - string templateParameterName = variableMatch.Groups["VariableName"].Value; - if (String.IsNullOrWhiteSpace(templateParameterName)) - yield return new LiteralUriSegment(pathSegment, isDirectory); - - bool isOptional = variableMatch.Groups["VariableIsOptional"].Value.Length > 0; - - yield return new ParameterizedUriSegment(templateParameterName, isDirectory, isOptional); - } - else - yield return new LiteralUriSegment(pathSegment, isDirectory); - } - else - { - if (haveRoot) - continue; - - haveRoot = true; - - yield return RootUriSegment.Instance; - } - } - } - - /// - /// Parse query segments from the specified template. - /// - /// - /// The URI template. - /// - /// - /// A sequence of 0 or more query segments. - /// - static IEnumerable ParseQuerySegments(Uri template) - { - if (template == null) - throw new ArgumentNullException(nameof(template)); - - if (template.Query == String.Empty) - yield break; - - string[] queryParameters = - template.Query.Substring(1).Split( - separator: new char[] - { - '&' - }, - options: StringSplitOptions.RemoveEmptyEntries - - ); - - foreach (string queryParameter in queryParameters) - { - string[] parameterNameAndValue = queryParameter.Split( - separator: new char[] - { - '=' - }, - count: 2 - ); - - if (parameterNameAndValue.Length != 2) - continue; // Remove parameter. - - string queryParameterName = parameterNameAndValue[0]; - string queryParameterValue = Uri.UnescapeDataString(parameterNameAndValue[1]); - - Match variableMatch = VariableRegex.Match(queryParameterValue); - if (variableMatch.Success) - { - string templateParameterName = variableMatch.Groups["VariableName"].Value; - if (String.IsNullOrWhiteSpace(templateParameterName)) - yield return new LiteralQuerySegment(queryParameterName, queryParameterValue); - - bool isOptional = variableMatch.Groups["VariableIsOptional"].Value.Length > 0; - - yield return new ParameterizedQuerySegment(queryParameterName, templateParameterName, isOptional); - } - else - yield return new LiteralQuerySegment(queryParameterName, queryParameterValue); - } - } - } + /// + /// The base class for the segments that comprise a URI template. + /// + abstract class TemplateSegment + { + /// + /// The regular expression used to match variables. + /// + static readonly Regex VariableRegex = new Regex( + @"\{(?\w+)(?\?)?\}\/?", + RegexOptions.Compiled | RegexOptions.Singleline + ); + + /// + /// Create a new URI template segment. + /// + protected TemplateSegment() + { + } + + /// + /// Does the segment have a parameterised (non-constant) value? + /// + public virtual bool IsParameterized => true; + + /// + /// Get the value of the segment (if any). + /// + /// + /// The current template evaluation context. + /// + /// + /// The segment value, or null if the segment has no value. + /// + public abstract string GetValue(ITemplateEvaluationContext evaluationContext); + + /// + /// Parse the specified URI into template segments. + /// + /// + /// The URI to parse. + /// + /// + /// The template segments. + /// + public static IReadOnlyList Parse(string template) + { + if (template == null) + throw new ArgumentNullException(nameof(template)); + + List segments = new List(); + + try + { + Uri templateUri = new Uri( + new Uri("http://localhost/"), + template.Replace("?}", "%3F}") // Special case for '?' because it messes with Uri's parser. + ); + segments.AddRange( + ParsePathSegments(templateUri) + ); + segments.AddRange( + ParseQuerySegments(templateUri) + ); + } + catch (Exception eParse) + { + throw new UriTemplateException(eParse, "'{0}' is not a valid URI template.", template); + } + + return segments; + } + + /// + /// Parse URI segments from the specified template. + /// + /// + /// The URI template. + /// + /// + /// A sequence of 0 or more URI segments. + /// + static IEnumerable ParsePathSegments(Uri template) + { + if (template == null) + throw new ArgumentNullException(nameof(template)); + + bool haveRoot = false; + bool isLastSegmentDirectory = template.AbsolutePath[template.AbsolutePath.Length - 1] == '/'; + + string[] pathSegments = + template.AbsolutePath + .Split('/') + .Select( + segment => Uri.UnescapeDataString(segment) + ) + .ToArray(); + + int lastSegmentIndex = pathSegments.Length - 1; + for (int segmentIndex = 0; segmentIndex < pathSegments.Length; segmentIndex++) + { + string pathSegment = pathSegments[segmentIndex]; + if (pathSegment != String.Empty) + { + bool isDirectory = isLastSegmentDirectory || segmentIndex < lastSegmentIndex; + + Match variableMatch = VariableRegex.Match(pathSegment); + if (variableMatch.Success) + { + string templateParameterName = variableMatch.Groups["VariableName"].Value; + if (String.IsNullOrWhiteSpace(templateParameterName)) + yield return new LiteralUriSegment(pathSegment, isDirectory); + + bool isOptional = variableMatch.Groups["VariableIsOptional"].Value.Length > 0; + + yield return new ParameterizedUriSegment(templateParameterName, isDirectory, isOptional); + } + else + yield return new LiteralUriSegment(pathSegment, isDirectory); + } + else + { + if (haveRoot) + continue; + + haveRoot = true; + + yield return RootUriSegment.Instance; + } + } + } + + /// + /// Parse query segments from the specified template. + /// + /// + /// The URI template. + /// + /// + /// A sequence of 0 or more query segments. + /// + static IEnumerable ParseQuerySegments(Uri template) + { + if (template == null) + throw new ArgumentNullException(nameof(template)); + + if (template.Query == String.Empty) + yield break; + + string[] queryParameters = + template.Query.Substring(1).Split( + separator: new char[] + { + '&' + }, + options: StringSplitOptions.RemoveEmptyEntries + + ); + + foreach (string queryParameter in queryParameters) + { + string[] parameterNameAndValue = queryParameter.Split( + separator: new char[] + { + '=' + }, + count: 2 + ); + + if (parameterNameAndValue.Length != 2) + continue; // Remove parameter. + + string queryParameterName = parameterNameAndValue[0]; + string queryParameterValue = Uri.UnescapeDataString(parameterNameAndValue[1]); + + Match variableMatch = VariableRegex.Match(queryParameterValue); + if (variableMatch.Success) + { + string templateParameterName = variableMatch.Groups["VariableName"].Value; + if (String.IsNullOrWhiteSpace(templateParameterName)) + yield return new LiteralQuerySegment(queryParameterName, queryParameterValue); + + bool isOptional = variableMatch.Groups["VariableIsOptional"].Value.Length > 0; + + yield return new ParameterizedQuerySegment(queryParameterName, templateParameterName, isOptional); + } + else + yield return new LiteralQuerySegment(queryParameterName, queryParameterValue); + } + } + } } diff --git a/src/HTTPlease.Core/Core/Templates/UriSegment.cs b/src/HTTPlease.Core/Core/Templates/UriSegment.cs index 917d7bc..507ab8d 100644 --- a/src/HTTPlease.Core/Core/Templates/UriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/UriSegment.cs @@ -1,36 +1,36 @@ namespace HTTPlease.Core.Templates { - /// - /// The base class for URI template segments that represent segments of the URI. - /// - abstract class UriSegment - : TemplateSegment - { - /// - /// Does the segment represent a directory (i.e. have a trailing slash?). - /// - readonly bool _isDirectory; + /// + /// The base class for URI template segments that represent segments of the URI. + /// + abstract class UriSegment + : TemplateSegment + { + /// + /// Does the segment represent a directory (i.e. have a trailing slash?). + /// + readonly bool _isDirectory; - /// - /// Create a new URI segment. - /// - /// - /// Does the segment represent a directory (i.e. have a trailing slash?). - /// - protected UriSegment(bool isDirectory) - { - _isDirectory = isDirectory; - } + /// + /// Create a new URI segment. + /// + /// + /// Does the segment represent a directory (i.e. have a trailing slash?). + /// + protected UriSegment(bool isDirectory) + { + _isDirectory = isDirectory; + } - /// - /// Does the segment represent a directory (i.e. have a trailing slash?). - /// - public bool IsDirectory - { - get - { - return _isDirectory; - } - } - } + /// + /// Does the segment represent a directory (i.e. have a trailing slash?). + /// + public bool IsDirectory + { + get + { + return _isDirectory; + } + } + } } diff --git a/src/HTTPlease.Core/Core/Utilities/DisposableObject.cs b/src/HTTPlease.Core/Core/Utilities/DisposableObject.cs index 05adaec..0bd8380 100644 --- a/src/HTTPlease.Core/Core/Utilities/DisposableObject.cs +++ b/src/HTTPlease.Core/Core/Utilities/DisposableObject.cs @@ -3,101 +3,101 @@ namespace HTTPlease.Core.Utilities { - /// - /// A base class for disposable objects. + /// + /// A base class for disposable objects. /// public abstract class DisposableObject : IDisposable { - /// - /// Is the object being disposed? - /// - /// - /// 1, if the object is being disposed; otherwise, 0. - /// - int _isDisposing; + /// + /// Is the object being disposed? + /// + /// + /// 1, if the object is being disposed; otherwise, 0. + /// + int _isDisposing; /// - /// Has the object been disposed? + /// Has the object been disposed? /// - bool _isDisposed; + bool _isDisposed; /// - /// Create a new disposable object. + /// Create a new disposable object. /// protected DisposableObject() { } /// - /// Finaliser for . + /// Finaliser for . /// - ~DisposableObject() + ~DisposableObject() { - // Don't call disposal implementation more than once. - if (_isDisposed) - return; + // Don't call disposal implementation more than once. + if (_isDisposed) + return; - int wasDisposing = Interlocked.Exchange(ref _isDisposing, 1); - if (wasDisposing == 1) - return; + int wasDisposing = Interlocked.Exchange(ref _isDisposing, 1); + if (wasDisposing == 1) + return; - try - { - Dispose(false); - } - finally - { - _isDisposed = true; - _isDisposing = 0; - } + try + { + Dispose(false); + } + finally + { + _isDisposed = true; + _isDisposing = 0; + } } /// - /// Dispose of resources being used by the object. + /// Dispose of resources being used by the object. /// public void Dispose() { - // Don't call disposal implementation more than once. - if (_isDisposed) - return; + // Don't call disposal implementation more than once. + if (_isDisposed) + return; - int wasDisposing = Interlocked.Exchange(ref _isDisposing, 1); - if (wasDisposing == 1) - return; + int wasDisposing = Interlocked.Exchange(ref _isDisposing, 1); + if (wasDisposing == 1) + return; - try - { - Dispose(true); - GC.SuppressFinalize(this); - } - finally - { - _isDisposed = true; - _isDisposing = 0; - } + try + { + Dispose(true); + GC.SuppressFinalize(this); + } + finally + { + _isDisposed = true; + _isDisposing = 0; + } } /// - /// Dispose of resources being used by the object. + /// Dispose of resources being used by the object. /// /// - /// Explicit disposal? + /// Explicit disposal? /// protected virtual void Dispose(bool disposing) { } - /// - /// Has the object been disposed? - /// - protected bool IsDisposed => _isDisposed; + /// + /// Has the object been disposed? + /// + protected bool IsDisposed => _isDisposed; - /// - /// Check if the object has been disposed. + /// + /// Check if the object has been disposed. /// /// - /// The object has been disposed. + /// The object has been disposed. /// protected void CheckDisposed() { @@ -105,24 +105,24 @@ protected void CheckDisposed() throw new ObjectDisposedException(GetType().Name); } - /// - /// Determine if a has been disposed. - /// - /// - /// The to examine. - /// - /// - /// true, if the object has been disposed; otherwise, false. - /// - /// - /// is null. - /// - public static bool IsObjectDisposed(DisposableObject disposableObject) - { - if (disposableObject == null) - throw new ArgumentNullException(nameof(disposableObject)); + /// + /// Determine if a has been disposed. + /// + /// + /// The to examine. + /// + /// + /// true, if the object has been disposed; otherwise, false. + /// + /// + /// is null. + /// + public static bool IsObjectDisposed(DisposableObject disposableObject) + { + if (disposableObject == null) + throw new ArgumentNullException(nameof(disposableObject)); - return disposableObject.IsDisposed; - } + return disposableObject.IsDisposed; + } } } \ No newline at end of file diff --git a/src/HTTPlease.Core/Core/Utilities/DisposalHelpers.cs b/src/HTTPlease.Core/Core/Utilities/DisposalHelpers.cs index 4f7b944..3631ff9 100644 --- a/src/HTTPlease.Core/Core/Utilities/DisposalHelpers.cs +++ b/src/HTTPlease.Core/Core/Utilities/DisposalHelpers.cs @@ -4,81 +4,81 @@ namespace HTTPlease.Core.Utilities { - /// - /// Helper methods for . - /// - static class DisposalHelpers - { - /// - /// Create an aggregate that disposes of the specified s when it is disposed. - /// - /// - /// The s to aggregate. - /// - /// - /// An aggregate representing the supplied disposables. - /// - /// - /// One or more aggregated disposables throw exceptions during disposal. - /// - public static AggregateDisposable ToAggregateDisposable(this IEnumerable disposables) - { - if (disposables == null) - return new AggregateDisposable(); + /// + /// Helper methods for . + /// + static class DisposalHelpers + { + /// + /// Create an aggregate that disposes of the specified s when it is disposed. + /// + /// + /// The s to aggregate. + /// + /// + /// An aggregate representing the supplied disposables. + /// + /// + /// One or more aggregated disposables throw exceptions during disposal. + /// + public static AggregateDisposable ToAggregateDisposable(this IEnumerable disposables) + { + if (disposables == null) + return new AggregateDisposable(); - return new AggregateDisposable(disposables); - } + return new AggregateDisposable(disposables); + } - #region AggregateDisposable + #region AggregateDisposable - /// - /// Implements disposal of multiple s. - /// - public struct AggregateDisposable - : IDisposable - { - /// - /// The disposables to dispose of. - /// - readonly IReadOnlyList _disposables; + /// + /// Implements disposal of multiple s. + /// + public struct AggregateDisposable + : IDisposable + { + /// + /// The disposables to dispose of. + /// + readonly IReadOnlyList _disposables; - /// - /// Create a new aggregate disposable. - /// - /// - /// A sequence of s to aggregate. - /// - public AggregateDisposable(IEnumerable disposables) - { - if (disposables == null) - throw new ArgumentNullException(nameof(disposables)); + /// + /// Create a new aggregate disposable. + /// + /// + /// A sequence of s to aggregate. + /// + public AggregateDisposable(IEnumerable disposables) + { + if (disposables == null) + throw new ArgumentNullException(nameof(disposables)); - _disposables = disposables.ToArray(); - } + _disposables = disposables.ToArray(); + } - /// - /// Dispose the disposables. - /// - public void Dispose() - { - List disposalExceptions = new List(); - foreach (IDisposable disposable in _disposables) - { - try - { - disposable.Dispose(); - } - catch (Exception eDisposal) - { - disposalExceptions.Add(eDisposal); - } - } + /// + /// Dispose the disposables. + /// + public void Dispose() + { + List disposalExceptions = new List(); + foreach (IDisposable disposable in _disposables) + { + try + { + disposable.Dispose(); + } + catch (Exception eDisposal) + { + disposalExceptions.Add(eDisposal); + } + } - if (disposalExceptions.Count > 0) - throw new AggregateException("One or more exceptions were encountered during object disposal.", disposalExceptions); - } - } + if (disposalExceptions.Count > 0) + throw new AggregateException("One or more exceptions were encountered during object disposal.", disposalExceptions); + } + } - #endregion // AggregateDisposable - } + #endregion // AggregateDisposable + } } diff --git a/src/HTTPlease.Core/Core/Utilities/ReflectionHelper.cs b/src/HTTPlease.Core/Core/Utilities/ReflectionHelper.cs index e9b6a19..02587b9 100644 --- a/src/HTTPlease.Core/Core/Utilities/ReflectionHelper.cs +++ b/src/HTTPlease.Core/Core/Utilities/ReflectionHelper.cs @@ -4,38 +4,38 @@ namespace HTTPlease.Core.Utilities { - /// - /// Helper methods for working with Reflection. - /// + /// + /// Helper methods for working with Reflection. + /// public static class ReflectionHelper { - /// - /// Types that are known to be nullable. - /// - static readonly ConcurrentDictionary _nullableTypes = new ConcurrentDictionary(); + /// + /// Types that are known to be nullable. + /// + static readonly ConcurrentDictionary _nullableTypes = new ConcurrentDictionary(); - /// - /// Determine whether a reference to an instance of the type can be null. - /// - /// - /// The type. - /// - /// - /// true, if the represents a reference type or a nullable value type. - /// - public static bool IsNullable(this Type type) - { - if (type == null) - throw new ArgumentNullException(nameof(type)); + /// + /// Determine whether a reference to an instance of the type can be null. + /// + /// + /// The type. + /// + /// + /// true, if the represents a reference type or a nullable value type. + /// + public static bool IsNullable(this Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); - return _nullableTypes.GetOrAdd(type, targetType => - { - if (type.GetTypeInfo().IsClass) - return true; + return _nullableTypes.GetOrAdd(type, targetType => + { + if (type.GetTypeInfo().IsClass) + return true; - // For non-nullable types, Nullable.GetUnderlyingType just returns the type supplied to it. - return Nullable.GetUnderlyingType(type) != type; - }); - } + // For non-nullable types, Nullable.GetUnderlyingType just returns the type supplied to it. + return Nullable.GetUnderlyingType(type) != type; + }); + } } } diff --git a/src/HTTPlease.Core/Core/Utilities/UriHelper.cs b/src/HTTPlease.Core/Core/Utilities/UriHelper.cs index 9f6a617..0c34f85 100644 --- a/src/HTTPlease.Core/Core/Utilities/UriHelper.cs +++ b/src/HTTPlease.Core/Core/Utilities/UriHelper.cs @@ -6,304 +6,304 @@ namespace HTTPlease.Core.Utilities { - using QueryParameterDictionary = Dictionary>; + using QueryParameterDictionary = Dictionary>; /// - /// Helper methods for working with s. + /// Helper methods for working with s. /// public static class UriHelper - { - /// - /// Parse the URI's query parameters. - /// - /// - /// The URI. - /// - /// - /// A containing key / value pairs representing the query parameters. - /// - public static QueryParameterDictionary ParseQueryParameters(this Uri uri) - { - if (uri == null) - throw new ArgumentNullException(nameof(uri)); - - QueryParameterDictionary queryParameters = new QueryParameterDictionary(); - if (String.IsNullOrWhiteSpace(uri.Query)) - return queryParameters; - - Debug.Assert(uri.Query[0] == '?', "Query string does not start with '?'."); - - string[] keyValuePairs = uri.Query.Substring(1).Split( - separator: new char[] { '&' }, - options: StringSplitOptions.RemoveEmptyEntries - ); - - foreach (string keyValuePair in keyValuePairs) - { - string[] keyAndValue = keyValuePair.Split( - separator: new char[] { '=' }, - count: 2 - ); - - string parameterName = keyAndValue[0]; - string parameterValue = keyAndValue.Length == 2 ? keyAndValue[1] : null; - - List parameterValues; - if (!queryParameters.TryGetValue(parameterName, out parameterValues)) - { - parameterValues = new List(capacity: 1); // Optimise for most common case. - queryParameters.Add(parameterName, parameterValues); - } - parameterValues.Add(parameterValue); - } - - return queryParameters; - } - - /// - /// Create a copy of URI with its query component populated with the supplied parameters. - /// - /// - /// The used to construct the URI. - /// - /// - /// A representing the query parameters. - /// - /// - /// A new URI with the specified query. - /// - public static Uri WithQueryParameters(this Uri uri, QueryParameterDictionary parameters) - { - if (uri == null) - throw new ArgumentNullException(nameof(uri)); - - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - return - new UriBuilder(uri) - .WithQueryParameters(parameters) - .Uri; - } - - /// - /// Populate the query component of the URI. - /// - /// - /// The used to construct the URI - /// - /// - /// A representing the query parameters. - /// - /// - /// The URI builder (enables inline use). - /// - public static UriBuilder WithQueryParameters(this UriBuilder uriBuilder, QueryParameterDictionary parameters) - { - if (uriBuilder == null) - throw new ArgumentNullException(nameof(uriBuilder)); - - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - if (parameters.Count == 0) - return uriBuilder; - - // Yes, you could do this using String.Join, but it seems a bit wasteful to allocate all those "key=value" strings only to throw them away again. - - bool isFirstItem = true; - StringBuilder queryBuilder = new StringBuilder(); - foreach (KeyValuePair> parameter in parameters) - { - string parameterName = parameter.Key; - List parameterValues = parameter.Value; - - // For multiple values, render them as separate query parameters (e.g. "?foo=value1&foo=value2"). - foreach (string parameterValue in parameterValues) - { - if (parameterValue == null) - continue; - - if (!isFirstItem) - queryBuilder.Append('&'); - else - isFirstItem = false; - - queryBuilder.Append(parameterName); - - // Support for /foo/bar?x=1&y&z=2 - if (parameterValue != String.Empty) - { - queryBuilder.Append('='); - queryBuilder.Append( - Uri.EscapeUriString(parameterValue) - ); - } - } - } - - uriBuilder.Query = queryBuilder.ToString(); - - return uriBuilder; - } - - /// - /// Append a relative URI to the base URI. - /// - /// - /// The base URI. - /// - /// A trailing "/" will be appended, if necessary. - /// - /// - /// The relative URI to append (leading slash will be trimmed, if required). - /// - /// - /// The concatenated URI. - /// - /// - /// This function is required because, sometimes, appending of a relative path to a URI can behave counter-intuitively. - /// If the base URI does not have a trailing "/", then its last path segment is *replaced* by the relative UI. This is hardly ever what you actually want. - /// - internal static Uri AppendRelativeUri(this Uri baseUri, Uri relativeUri) - { - if (baseUri == null) - throw new ArgumentNullException(nameof(baseUri)); - - if (relativeUri == null) - throw new ArgumentNullException(nameof(relativeUri)); - - if (relativeUri.IsAbsoluteUri) - return relativeUri; - - if (baseUri.IsAbsoluteUri) - { - // Working with relative URIs is painful (e.g. you can't use .PathAndQuery). - string relativeUriString = relativeUri.ToString(); - - // Handle the case where the relative URI only contains query parameters (no path). - if (relativeUriString[0] == '?') - { - StringBuilder absoluteUriBuilder = new StringBuilder(baseUri.AbsoluteUri); - if (String.IsNullOrWhiteSpace(baseUri.Query)) - absoluteUriBuilder.Append('?'); - else - absoluteUriBuilder.Append('&'); - - absoluteUriBuilder.Append(relativeUriString, - startIndex: 1, - count: relativeUriString.Length - 1 - ); - - return new Uri( - absoluteUriBuilder.ToString() - ); - } - - // Retain URI-concatenation semantics, except that we behave the same whether trailing slash is present or absent. - UriBuilder uriBuilder = new UriBuilder(baseUri); - - string[] relativePathAndQuery = - relativeUriString.Split( - new[] { '?' }, - count: 2, - options: StringSplitOptions.RemoveEmptyEntries - ); - - uriBuilder.Path = AppendPaths(uriBuilder.Path, relativePathAndQuery[0]); - - // Merge query parameters, if required. - if (relativePathAndQuery.Length == 2) - { - uriBuilder.Query = MergeQueryStrings( - baseQueryString: uriBuilder.Query, - additionalQueryString: relativePathAndQuery[1] - ); - } - - return uriBuilder.Uri; - } - - // Irritatingly, you can't use UriBuilder with a relative path. - return new Uri( - AppendPaths(baseUri.ToString(), relativeUri.ToString()), - UriKind.Relative - ); - } - - /// - /// Contatenate 2 relative URI paths. - /// - /// - /// The base URI path. - /// - /// - /// The relative URI path to append to the base URI path. - /// - /// - /// The appended paths, separated by a single slash. - /// - static string AppendPaths(string basePath, string relativePath) - { - if (basePath == null) - throw new ArgumentNullException(nameof(basePath)); - - if (relativePath == null) - throw new ArgumentNullException(nameof(relativePath)); - - StringBuilder pathBuilder = new StringBuilder(basePath); - if (pathBuilder.Length == 0 || pathBuilder[pathBuilder.Length - 1] != '/') - pathBuilder.Append("/"); - - int relativePathStartIndex = - (relativePath.Length > 0 && relativePath[0] == '/') ? 1 : 0; - - pathBuilder.Append( - relativePath, - startIndex: (relativePath.Length > 0 && relativePath[0] == '/') ? 1 : 0, - count: relativePath.Length - relativePathStartIndex - ); - - return pathBuilder.ToString(); - } - - /// - /// Merge 2 query strings. - /// - /// - /// The base query string. - /// - /// If empty, the additional query string is used. - /// - /// - /// The additional query string. - /// - /// If empty, the base query string is used. - /// - /// - /// - /// Does not remove duplicate parameters. - /// - static string MergeQueryStrings(string baseQueryString, string additionalQueryString) - { - if (String.IsNullOrWhiteSpace(additionalQueryString)) - return baseQueryString; - - if (String.IsNullOrWhiteSpace(baseQueryString)) - return additionalQueryString; - - StringBuilder combinedQueryParameters = new StringBuilder(); - if (baseQueryString[0] != '?') - combinedQueryParameters.Append('?'); - - combinedQueryParameters.Append(baseQueryString); - - if (additionalQueryString[0] != '?') - combinedQueryParameters.Append(additionalQueryString); - else - combinedQueryParameters.Append(additionalQueryString, 1, additionalQueryString.Length - 1); - - return combinedQueryParameters.ToString(); - } - } + { + /// + /// Parse the URI's query parameters. + /// + /// + /// The URI. + /// + /// + /// A containing key / value pairs representing the query parameters. + /// + public static QueryParameterDictionary ParseQueryParameters(this Uri uri) + { + if (uri == null) + throw new ArgumentNullException(nameof(uri)); + + QueryParameterDictionary queryParameters = new QueryParameterDictionary(); + if (String.IsNullOrWhiteSpace(uri.Query)) + return queryParameters; + + Debug.Assert(uri.Query[0] == '?', "Query string does not start with '?'."); + + string[] keyValuePairs = uri.Query.Substring(1).Split( + separator: new char[] { '&' }, + options: StringSplitOptions.RemoveEmptyEntries + ); + + foreach (string keyValuePair in keyValuePairs) + { + string[] keyAndValue = keyValuePair.Split( + separator: new char[] { '=' }, + count: 2 + ); + + string parameterName = keyAndValue[0]; + string parameterValue = keyAndValue.Length == 2 ? keyAndValue[1] : null; + + List parameterValues; + if (!queryParameters.TryGetValue(parameterName, out parameterValues)) + { + parameterValues = new List(capacity: 1); // Optimise for most common case. + queryParameters.Add(parameterName, parameterValues); + } + parameterValues.Add(parameterValue); + } + + return queryParameters; + } + + /// + /// Create a copy of URI with its query component populated with the supplied parameters. + /// + /// + /// The used to construct the URI. + /// + /// + /// A representing the query parameters. + /// + /// + /// A new URI with the specified query. + /// + public static Uri WithQueryParameters(this Uri uri, QueryParameterDictionary parameters) + { + if (uri == null) + throw new ArgumentNullException(nameof(uri)); + + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + return + new UriBuilder(uri) + .WithQueryParameters(parameters) + .Uri; + } + + /// + /// Populate the query component of the URI. + /// + /// + /// The used to construct the URI + /// + /// + /// A representing the query parameters. + /// + /// + /// The URI builder (enables inline use). + /// + public static UriBuilder WithQueryParameters(this UriBuilder uriBuilder, QueryParameterDictionary parameters) + { + if (uriBuilder == null) + throw new ArgumentNullException(nameof(uriBuilder)); + + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + if (parameters.Count == 0) + return uriBuilder; + + // Yes, you could do this using String.Join, but it seems a bit wasteful to allocate all those "key=value" strings only to throw them away again. + + bool isFirstItem = true; + StringBuilder queryBuilder = new StringBuilder(); + foreach (KeyValuePair> parameter in parameters) + { + string parameterName = parameter.Key; + List parameterValues = parameter.Value; + + // For multiple values, render them as separate query parameters (e.g. "?foo=value1&foo=value2"). + foreach (string parameterValue in parameterValues) + { + if (parameterValue == null) + continue; + + if (!isFirstItem) + queryBuilder.Append('&'); + else + isFirstItem = false; + + queryBuilder.Append(parameterName); + + // Support for /foo/bar?x=1&y&z=2 + if (parameterValue != String.Empty) + { + queryBuilder.Append('='); + queryBuilder.Append( + Uri.EscapeUriString(parameterValue) + ); + } + } + } + + uriBuilder.Query = queryBuilder.ToString(); + + return uriBuilder; + } + + /// + /// Append a relative URI to the base URI. + /// + /// + /// The base URI. + /// + /// A trailing "/" will be appended, if necessary. + /// + /// + /// The relative URI to append (leading slash will be trimmed, if required). + /// + /// + /// The concatenated URI. + /// + /// + /// This function is required because, sometimes, appending of a relative path to a URI can behave counter-intuitively. + /// If the base URI does not have a trailing "/", then its last path segment is *replaced* by the relative UI. This is hardly ever what you actually want. + /// + internal static Uri AppendRelativeUri(this Uri baseUri, Uri relativeUri) + { + if (baseUri == null) + throw new ArgumentNullException(nameof(baseUri)); + + if (relativeUri == null) + throw new ArgumentNullException(nameof(relativeUri)); + + if (relativeUri.IsAbsoluteUri) + return relativeUri; + + if (baseUri.IsAbsoluteUri) + { + // Working with relative URIs is painful (e.g. you can't use .PathAndQuery). + string relativeUriString = relativeUri.ToString(); + + // Handle the case where the relative URI only contains query parameters (no path). + if (relativeUriString[0] == '?') + { + StringBuilder absoluteUriBuilder = new StringBuilder(baseUri.AbsoluteUri); + if (String.IsNullOrWhiteSpace(baseUri.Query)) + absoluteUriBuilder.Append('?'); + else + absoluteUriBuilder.Append('&'); + + absoluteUriBuilder.Append(relativeUriString, + startIndex: 1, + count: relativeUriString.Length - 1 + ); + + return new Uri( + absoluteUriBuilder.ToString() + ); + } + + // Retain URI-concatenation semantics, except that we behave the same whether trailing slash is present or absent. + UriBuilder uriBuilder = new UriBuilder(baseUri); + + string[] relativePathAndQuery = + relativeUriString.Split( + new[] { '?' }, + count: 2, + options: StringSplitOptions.RemoveEmptyEntries + ); + + uriBuilder.Path = AppendPaths(uriBuilder.Path, relativePathAndQuery[0]); + + // Merge query parameters, if required. + if (relativePathAndQuery.Length == 2) + { + uriBuilder.Query = MergeQueryStrings( + baseQueryString: uriBuilder.Query, + additionalQueryString: relativePathAndQuery[1] + ); + } + + return uriBuilder.Uri; + } + + // Irritatingly, you can't use UriBuilder with a relative path. + return new Uri( + AppendPaths(baseUri.ToString(), relativeUri.ToString()), + UriKind.Relative + ); + } + + /// + /// Contatenate 2 relative URI paths. + /// + /// + /// The base URI path. + /// + /// + /// The relative URI path to append to the base URI path. + /// + /// + /// The appended paths, separated by a single slash. + /// + static string AppendPaths(string basePath, string relativePath) + { + if (basePath == null) + throw new ArgumentNullException(nameof(basePath)); + + if (relativePath == null) + throw new ArgumentNullException(nameof(relativePath)); + + StringBuilder pathBuilder = new StringBuilder(basePath); + if (pathBuilder.Length == 0 || pathBuilder[pathBuilder.Length - 1] != '/') + pathBuilder.Append("/"); + + int relativePathStartIndex = + (relativePath.Length > 0 && relativePath[0] == '/') ? 1 : 0; + + pathBuilder.Append( + relativePath, + startIndex: (relativePath.Length > 0 && relativePath[0] == '/') ? 1 : 0, + count: relativePath.Length - relativePathStartIndex + ); + + return pathBuilder.ToString(); + } + + /// + /// Merge 2 query strings. + /// + /// + /// The base query string. + /// + /// If empty, the additional query string is used. + /// + /// + /// The additional query string. + /// + /// If empty, the base query string is used. + /// + /// + /// + /// Does not remove duplicate parameters. + /// + static string MergeQueryStrings(string baseQueryString, string additionalQueryString) + { + if (String.IsNullOrWhiteSpace(additionalQueryString)) + return baseQueryString; + + if (String.IsNullOrWhiteSpace(baseQueryString)) + return additionalQueryString; + + StringBuilder combinedQueryParameters = new StringBuilder(); + if (baseQueryString[0] != '?') + combinedQueryParameters.Append('?'); + + combinedQueryParameters.Append(baseQueryString); + + if (additionalQueryString[0] != '?') + combinedQueryParameters.Append(additionalQueryString); + else + combinedQueryParameters.Append(additionalQueryString, 1, additionalQueryString.Length - 1); + + return combinedQueryParameters.ToString(); + } + } } diff --git a/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs b/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs index d5e902f..278edad 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/IValueProvider.cs @@ -2,37 +2,37 @@ namespace HTTPlease.Core.ValueProviders { - /// - /// Represents the provider for a value from an instance of . - /// - /// - /// The source type from which the value is extracted. - /// - /// - /// The type of value returned by the provider. - /// - public interface IValueProvider - { - /// - /// Extract a value from the specified context. - /// - /// - /// The instance from which the value is to be extracted. - /// - /// - /// The value. - /// - TValue Get(TContext source); + /// + /// Represents the provider for a value from an instance of . + /// + /// + /// The source type from which the value is extracted. + /// + /// + /// The type of value returned by the provider. + /// + public interface IValueProvider + { + /// + /// Extract a value from the specified context. + /// + /// + /// The instance from which the value is to be extracted. + /// + /// + /// The value. + /// + TValue Get(TContext source); - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - IEnumerable GetMultiple(TContext source); - } + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + IEnumerable GetMultiple(TContext source); + } } diff --git a/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs b/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs index eabc807..246590b 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/MultiValueProvider.cs @@ -4,234 +4,234 @@ namespace HTTPlease.Core.ValueProviders { - /// - /// Factory methods for creating multi-value providers. - /// - /// - /// The type used as a context for each request. - /// - public static class MultiValueProvider - { - /// - /// Create a value provider from the specified selector function. - /// - /// - /// The type of values returned by the selector. - /// - /// - /// A selector function that, when given an instance of , returns values of type derived from the context. - /// - /// - /// The value provider. - /// - public static IValueProvider FromSelector(Func> selector) - { - if (selector == null) - throw new ArgumentNullException(nameof(selector)); - - return new SelectorMultiValueProvider(selector); - } - - /// - /// Create a value provider from the specified function. - /// - /// - /// The type of values returned by the function. - /// - /// - /// A function that returns values of type . - /// - /// - /// The value provider. - /// - public static IValueProvider FromFunction(Func> getValues) - { - if (getValues == null) - throw new ArgumentNullException(nameof(getValues)); - - return new FunctionMultiValueProvider(getValues); - } - - /// - /// Create a value provider from the specified constant values. - /// - /// - /// The type of value returned by the provider. - /// - /// - /// The constant values that are returned by the provider. - /// - /// - /// The value provider. - /// - public static IValueProvider FromConstantValues(IEnumerable values) - { - return new StaticMultiValueProvider(values); - } - - /// - /// A multi-value provider that invokes a selector function on the context to extract its values. - /// - /// - /// The type of value returned by the provider. - /// - class SelectorMultiValueProvider - : IValueProvider - { - /// - /// The selector function that extracts values from the context. - /// - readonly Func> _selector; - - /// - /// Create a new selector-based value provider. - /// - /// - /// The selector function that extracts a value from the context. - /// - public SelectorMultiValueProvider(Func> selector) - { - if (selector == null) - throw new ArgumentNullException(nameof(selector)); - - _selector = selector; - } - - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - public IEnumerable GetMultiple(TContext source) - { - if (source == null) - throw new InvalidOperationException("The current request template has one more more deferred parameters that refer to its context; the context parameter must therefore be supplied."); - - return _selector(source); - } - - /// - /// Extract a value from the specified context. - /// - /// - /// The TContext instance from which the value is to be extracted. - /// - /// - /// The value. - /// - public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); - } - - /// - /// A multi-value provider that invokes a function to extract its value. - /// - /// - /// The type of value returned by the provider. - /// - class FunctionMultiValueProvider - : IValueProvider - { - /// - /// The function that is invoked to provide a value. - /// - readonly Func> _getValues; - - /// - /// Create a new function-based value provider. - /// - /// - /// The function that is invoked to provide values. - /// - public FunctionMultiValueProvider(Func> getValues) - { - if (getValues == null) - throw new ArgumentNullException(nameof(getValues)); - - _getValues = getValues; - } - - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - public IEnumerable GetMultiple(TContext source) - { - if (source == null) - return Enumerable.Empty(); - - return _getValues(); - } - - /// - /// Extract a value from the specified context. - /// - /// - /// The TContext instance from which the value is to be extracted. - /// - /// - /// The value. - /// - public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); - } - - /// - /// A multi-value provider that returns a constant value. - /// - /// - /// The type of value returned by the provider. - /// - class StaticMultiValueProvider - : IValueProvider - { - /// - /// The values returned by the provider. - /// - readonly IEnumerable _values; - - /// - /// Create a new constant value provider. - /// - /// - /// The values returned by the provider. - /// - public StaticMultiValueProvider(IEnumerable values) - { - if (values == null) - throw new ArgumentNullException(nameof(values)); - - _values = values; - } - - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - public IEnumerable GetMultiple(TContext source) => _values; - - /// - /// Extract a value from the specified context. - /// - /// - /// The TContext instance from which the value is to be extracted. - /// - /// - /// The value. - /// - public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); - } - } + /// + /// Factory methods for creating multi-value providers. + /// + /// + /// The type used as a context for each request. + /// + public static class MultiValueProvider + { + /// + /// Create a value provider from the specified selector function. + /// + /// + /// The type of values returned by the selector. + /// + /// + /// A selector function that, when given an instance of , returns values of type derived from the context. + /// + /// + /// The value provider. + /// + public static IValueProvider FromSelector(Func> selector) + { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return new SelectorMultiValueProvider(selector); + } + + /// + /// Create a value provider from the specified function. + /// + /// + /// The type of values returned by the function. + /// + /// + /// A function that returns values of type . + /// + /// + /// The value provider. + /// + public static IValueProvider FromFunction(Func> getValues) + { + if (getValues == null) + throw new ArgumentNullException(nameof(getValues)); + + return new FunctionMultiValueProvider(getValues); + } + + /// + /// Create a value provider from the specified constant values. + /// + /// + /// The type of value returned by the provider. + /// + /// + /// The constant values that are returned by the provider. + /// + /// + /// The value provider. + /// + public static IValueProvider FromConstantValues(IEnumerable values) + { + return new StaticMultiValueProvider(values); + } + + /// + /// A multi-value provider that invokes a selector function on the context to extract its values. + /// + /// + /// The type of value returned by the provider. + /// + class SelectorMultiValueProvider + : IValueProvider + { + /// + /// The selector function that extracts values from the context. + /// + readonly Func> _selector; + + /// + /// Create a new selector-based value provider. + /// + /// + /// The selector function that extracts a value from the context. + /// + public SelectorMultiValueProvider(Func> selector) + { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + _selector = selector; + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + if (source == null) + throw new InvalidOperationException("The current request template has one more more deferred parameters that refer to its context; the context parameter must therefore be supplied."); + + return _selector(source); + } + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); + } + + /// + /// A multi-value provider that invokes a function to extract its value. + /// + /// + /// The type of value returned by the provider. + /// + class FunctionMultiValueProvider + : IValueProvider + { + /// + /// The function that is invoked to provide a value. + /// + readonly Func> _getValues; + + /// + /// Create a new function-based value provider. + /// + /// + /// The function that is invoked to provide values. + /// + public FunctionMultiValueProvider(Func> getValues) + { + if (getValues == null) + throw new ArgumentNullException(nameof(getValues)); + + _getValues = getValues; + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + if (source == null) + return Enumerable.Empty(); + + return _getValues(); + } + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); + } + + /// + /// A multi-value provider that returns a constant value. + /// + /// + /// The type of value returned by the provider. + /// + class StaticMultiValueProvider + : IValueProvider + { + /// + /// The values returned by the provider. + /// + readonly IEnumerable _values; + + /// + /// Create a new constant value provider. + /// + /// + /// The values returned by the provider. + /// + public StaticMultiValueProvider(IEnumerable values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + + _values = values; + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) => _values; + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => GetMultiple(source).FirstOrDefault(); + } + } } diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs index 76bf963..440d0b8 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProvider.cs @@ -3,234 +3,234 @@ namespace HTTPlease.Core.ValueProviders { - /// - /// Factory methods for creating value providers. - /// - /// - /// The type used as a context for each request. - /// - public static class ValueProvider - { - /// - /// Create a value provider from the specified selector function. - /// - /// - /// The type of value returned by the selector. - /// - /// - /// A selector function that, when given an instance of , returns a value of type derived from the context. - /// - /// - /// The value provider. - /// - public static IValueProvider FromSelector(Func selector) - { - if (selector == null) - throw new ArgumentNullException(nameof(selector)); - - return new SelectorValueProvider(selector); - } - - /// - /// Create a value provider from the specified function. - /// - /// - /// The type of value returned by the function. - /// - /// - /// A function that returns a value of type . - /// - /// - /// The value provider. - /// - public static IValueProvider FromFunction(Func getValue) - { - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return new FunctionValueProvider(getValue); - } - - /// - /// Create a value provider from the specified constant value. - /// - /// - /// The type of value returned by the provider. - /// - /// - /// A constant value that is returned by the provider. - /// - /// - /// The value provider. - /// - public static IValueProvider FromConstantValue(TValue value) - { - return new StaticValueProvider(value); - } - - /// - /// Value provider that invokes a selector function on the context to extract its value. - /// - /// - /// The type of value returned by the provider. - /// - class SelectorValueProvider - : IValueProvider - { - /// - /// The selector function that extracts a value from the context. - /// - readonly Func _selector; - - /// - /// Create a new selector-based value provider. - /// - /// - /// The selector function that extracts a value from the context. - /// - public SelectorValueProvider(Func selector) - { - if (selector == null) - throw new ArgumentNullException(nameof(selector)); - - _selector = selector; - } - - /// - /// Extract the value from the specified context. - /// - /// - /// The TContext instance from which the value is to be extracted. - /// - /// - /// The value. - /// - public TValue Get(TContext source) - { - if (source == null) - throw new InvalidOperationException("The current request template has one more more deferred parameters that refer to its context; the context parameter must therefore be supplied."); - - return _selector(source); - } - - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - public IEnumerable GetMultiple(TContext source) - { - yield return Get(source); - } - } - - /// - /// Value provider that invokes a function to extract its value. - /// - /// - /// The type of value returned by the provider. - /// - class FunctionValueProvider - : IValueProvider - { - /// - /// The function that is invoked to provide a value. - /// - readonly Func _getValue; - - /// - /// Create a new function-based value provider. - /// - /// - /// The function that is invoked to provide a value. - /// - public FunctionValueProvider(Func getValue) - { - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - _getValue = getValue; - } - - /// - /// Extract a value from the specified context. - /// - /// - /// The TContext instance from which the value is to be extracted. - /// - /// - /// The value. - /// - public TValue Get(TContext source) => _getValue(); - - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - public IEnumerable GetMultiple(TContext source) - { - yield return Get(source); - } - } - - /// - /// Value provider that returns a static set of values. - /// - /// - /// The type of value returned by the provider. - /// - class StaticValueProvider - : IValueProvider - { - /// - /// The value returned by the provider. - /// - readonly TValue _value; - - /// - /// Create a new constant value provider. - /// - /// - /// The value returned by the provider. - /// - public StaticValueProvider(TValue value) - { - _value = value; - } - - /// - /// Extract the value from the specified context. - /// - /// - /// The TContext instance from which the value is to be extracted. - /// - /// - /// The value. - /// - public TValue Get(TContext source) => _value; - - /// - /// Extract values from the specified context. - /// - /// - /// The TContext instance from which the values are to be extracted. - /// - /// - /// The values. - /// - public IEnumerable GetMultiple(TContext source) - { - yield return Get(source); - } - } - } + /// + /// Factory methods for creating value providers. + /// + /// + /// The type used as a context for each request. + /// + public static class ValueProvider + { + /// + /// Create a value provider from the specified selector function. + /// + /// + /// The type of value returned by the selector. + /// + /// + /// A selector function that, when given an instance of , returns a value of type derived from the context. + /// + /// + /// The value provider. + /// + public static IValueProvider FromSelector(Func selector) + { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + return new SelectorValueProvider(selector); + } + + /// + /// Create a value provider from the specified function. + /// + /// + /// The type of value returned by the function. + /// + /// + /// A function that returns a value of type . + /// + /// + /// The value provider. + /// + public static IValueProvider FromFunction(Func getValue) + { + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return new FunctionValueProvider(getValue); + } + + /// + /// Create a value provider from the specified constant value. + /// + /// + /// The type of value returned by the provider. + /// + /// + /// A constant value that is returned by the provider. + /// + /// + /// The value provider. + /// + public static IValueProvider FromConstantValue(TValue value) + { + return new StaticValueProvider(value); + } + + /// + /// Value provider that invokes a selector function on the context to extract its value. + /// + /// + /// The type of value returned by the provider. + /// + class SelectorValueProvider + : IValueProvider + { + /// + /// The selector function that extracts a value from the context. + /// + readonly Func _selector; + + /// + /// Create a new selector-based value provider. + /// + /// + /// The selector function that extracts a value from the context. + /// + public SelectorValueProvider(Func selector) + { + if (selector == null) + throw new ArgumentNullException(nameof(selector)); + + _selector = selector; + } + + /// + /// Extract the value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) + { + if (source == null) + throw new InvalidOperationException("The current request template has one more more deferred parameters that refer to its context; the context parameter must therefore be supplied."); + + return _selector(source); + } + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + yield return Get(source); + } + } + + /// + /// Value provider that invokes a function to extract its value. + /// + /// + /// The type of value returned by the provider. + /// + class FunctionValueProvider + : IValueProvider + { + /// + /// The function that is invoked to provide a value. + /// + readonly Func _getValue; + + /// + /// Create a new function-based value provider. + /// + /// + /// The function that is invoked to provide a value. + /// + public FunctionValueProvider(Func getValue) + { + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + _getValue = getValue; + } + + /// + /// Extract a value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => _getValue(); + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + yield return Get(source); + } + } + + /// + /// Value provider that returns a static set of values. + /// + /// + /// The type of value returned by the provider. + /// + class StaticValueProvider + : IValueProvider + { + /// + /// The value returned by the provider. + /// + readonly TValue _value; + + /// + /// Create a new constant value provider. + /// + /// + /// The value returned by the provider. + /// + public StaticValueProvider(TValue value) + { + _value = value; + } + + /// + /// Extract the value from the specified context. + /// + /// + /// The TContext instance from which the value is to be extracted. + /// + /// + /// The value. + /// + public TValue Get(TContext source) => _value; + + /// + /// Extract values from the specified context. + /// + /// + /// The TContext instance from which the values are to be extracted. + /// + /// + /// The values. + /// + public IEnumerable GetMultiple(TContext source) + { + yield return Get(source); + } + } + } } diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs index c6162a1..e863e4c 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderCombiner.cs @@ -4,55 +4,55 @@ namespace HTTPlease.Core.ValueProviders { - /// - /// Combination operations for a value provider. - /// - /// - /// The type used as a context for each request. - /// - /// - /// The type of value returned by the value provider. - /// - public struct ValueProviderCombiner - { - /// - /// Create a new value-provider combiner. - /// - /// - /// The value provider being combined. - /// - public ValueProviderCombiner(IValueProvider valueProvider) - : this() - { - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); + /// + /// Combination operations for a value provider. + /// + /// + /// The type used as a context for each request. + /// + /// + /// The type of value returned by the value provider. + /// + public struct ValueProviderCombiner + { + /// + /// Create a new value-provider combiner. + /// + /// + /// The value provider being combined. + /// + public ValueProviderCombiner(IValueProvider valueProvider) + : this() + { + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); - ValueProvider = valueProvider; - } + ValueProvider = valueProvider; + } - /// - /// The value provider being combined. - /// - public IValueProvider ValueProvider { get; } + /// + /// The value provider being combined. + /// + public IValueProvider ValueProvider { get; } - /// - /// Wrap the value provider in a value provider that appends its values to the values supplied by another specified value provider. - /// - /// - /// The value provider whose values come first. - /// - public IValueProvider ByAppendingTo(IValueProvider valueProvider ) - { - // Can't close over members of structs. - IValueProvider existingValueProvider = ValueProvider; + /// + /// Wrap the value provider in a value provider that appends its values to the values supplied by another specified value provider. + /// + /// + /// The value provider whose values come first. + /// + public IValueProvider ByAppendingTo(IValueProvider valueProvider ) + { + // Can't close over members of structs. + IValueProvider existingValueProvider = ValueProvider; - return MultiValueProvider.FromSelector(context => - { - IEnumerable existingValues = valueProvider.GetMultiple(context); - IEnumerable appendValues = existingValueProvider.GetMultiple(context); + return MultiValueProvider.FromSelector(context => + { + IEnumerable existingValues = valueProvider.GetMultiple(context); + IEnumerable appendValues = existingValueProvider.GetMultiple(context); - return existingValues.Concat(appendValues); - }); - } - } + return existingValues.Concat(appendValues); + }); + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderConversion.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderConversion.cs index 74d76f5..e71bfcf 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderConversion.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderConversion.cs @@ -2,83 +2,83 @@ namespace HTTPlease.Core.ValueProviders { - /// - /// Conversion operations for a value provider. - /// - /// - /// The type used as a context for each request. - /// - /// - /// The type of value returned by the value provider. - /// - public struct ValueProviderConversion - { - /// - /// Create a new value-provider conversion. - /// - /// - /// The value provider being converted. - /// - public ValueProviderConversion(IValueProvider valueProvider) - : this() - { - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); + /// + /// Conversion operations for a value provider. + /// + /// + /// The type used as a context for each request. + /// + /// + /// The type of value returned by the value provider. + /// + public struct ValueProviderConversion + { + /// + /// Create a new value-provider conversion. + /// + /// + /// The value provider being converted. + /// + public ValueProviderConversion(IValueProvider valueProvider) + : this() + { + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); - ValueProvider = valueProvider; - } + ValueProvider = valueProvider; + } - /// - /// The value provider being converted. - /// - public IValueProvider ValueProvider { get; } + /// + /// The value provider being converted. + /// + public IValueProvider ValueProvider { get; } - /// - /// Wrap the specified value provider in a value provider that utilises a more-derived context type. - /// - /// - /// The more-derived type used by the new provider as a context for each request. - /// - /// - /// The outer (converting) value provider. - /// - public IValueProvider ContextTo() - where TDerivedContext : TContext - { - // Can't close over members of structs. - IValueProvider valueProvider = ValueProvider; + /// + /// Wrap the specified value provider in a value provider that utilises a more-derived context type. + /// + /// + /// The more-derived type used by the new provider as a context for each request. + /// + /// + /// The outer (converting) value provider. + /// + public IValueProvider ContextTo() + where TDerivedContext : TContext + { + // Can't close over members of structs. + IValueProvider valueProvider = ValueProvider; - return ValueProvider.FromSelector( - context => valueProvider.Get(context) - ); - } + return ValueProvider.FromSelector( + context => valueProvider.Get(context) + ); + } - /// - /// Wrap the value provider in a value provider that converts its value to a string. - /// - /// - /// The outer (converting) value provider. - /// - /// - /// If the underlying value is null then the converted string value will be null, too. - /// - public IValueProvider ValueToString() - { - // Special-case conversion to save on allocations. - if (typeof(TValue) == typeof(string)) - return (IValueProvider)ValueProvider; + /// + /// Wrap the value provider in a value provider that converts its value to a string. + /// + /// + /// The outer (converting) value provider. + /// + /// + /// If the underlying value is null then the converted string value will be null, too. + /// + public IValueProvider ValueToString() + { + // Special-case conversion to save on allocations. + if (typeof(TValue) == typeof(string)) + return (IValueProvider)ValueProvider; - // Can't close over members of structs. - IValueProvider valueProvider = ValueProvider; + // Can't close over members of structs. + IValueProvider valueProvider = ValueProvider; - return ValueProvider.FromSelector( - context => - { - TValue value = valueProvider.Get(context); + return ValueProvider.FromSelector( + context => + { + TValue value = valueProvider.Get(context); - return value != null ? value.ToString() : null; - } - ); - } - } + return value != null ? value.ToString() : null; + } + ); + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs index 15bb9b9..41dacdf 100644 --- a/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs +++ b/src/HTTPlease.Core/Core/ValueProviders/ValueProviderExtensions.cs @@ -2,55 +2,55 @@ namespace HTTPlease.Core.ValueProviders { - /// - /// Extension methods for . - /// - public static class ValueProviderExtensions - { - /// - /// Perform a conversion on the value provider. - /// - /// - /// The source type from which the value is extracted. - /// - /// - /// The type of value extracted by the provider. - /// - /// - /// The value provider. - /// - /// - /// A whose methods can be used to select the conversion to perform on the value converter. - /// - public static ValueProviderConversion Convert(this IValueProvider valueProvider) - { - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); + /// + /// Extension methods for . + /// + public static class ValueProviderExtensions + { + /// + /// Perform a conversion on the value provider. + /// + /// + /// The source type from which the value is extracted. + /// + /// + /// The type of value extracted by the provider. + /// + /// + /// The value provider. + /// + /// + /// A whose methods can be used to select the conversion to perform on the value converter. + /// + public static ValueProviderConversion Convert(this IValueProvider valueProvider) + { + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); - return new ValueProviderConversion(valueProvider); - } + return new ValueProviderConversion(valueProvider); + } - /// - /// Perform a conversion on the value provider. - /// - /// - /// The source type from which the value is extracted. - /// - /// - /// The type of value extracted by the provider. - /// - /// - /// The value provider. - /// - /// - /// A whose methods can be used to select the conversion to perform on the value mergeer. - /// - public static ValueProviderCombiner Combine(this IValueProvider valueProvider) - { - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); + /// + /// Perform a conversion on the value provider. + /// + /// + /// The source type from which the value is extracted. + /// + /// + /// The type of value extracted by the provider. + /// + /// + /// The value provider. + /// + /// + /// A whose methods can be used to select the conversion to perform on the value mergeer. + /// + public static ValueProviderCombiner Combine(this IValueProvider valueProvider) + { + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); - return new ValueProviderCombiner(valueProvider); - } - } + return new ValueProviderCombiner(valueProvider); + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Core/FactoryExtensions.cs b/src/HTTPlease.Core/FactoryExtensions.cs index 8a72b6f..1c5cdf3 100644 --- a/src/HTTPlease.Core/FactoryExtensions.cs +++ b/src/HTTPlease.Core/FactoryExtensions.cs @@ -2,34 +2,34 @@ namespace HTTPlease { - /// - /// Extension methods for . - /// - public static class FactoryExtensions + /// + /// Extension methods for . + /// + public static class FactoryExtensions { - /// - /// Create a new HTTP request with the specified request URI. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Create(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request with the specified request URI. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Create(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return requestFactory.Create( - new Uri(requestUri, UriKind.RelativeOrAbsolute) - ); - } - } + return requestFactory.Create( + new Uri(requestUri, UriKind.RelativeOrAbsolute) + ); + } + } } diff --git a/src/HTTPlease.Core/HttpRequest.cs b/src/HTTPlease.Core/HttpRequest.cs index 5c7df16..6f19706 100644 --- a/src/HTTPlease.Core/HttpRequest.cs +++ b/src/HTTPlease.Core/HttpRequest.cs @@ -8,394 +8,394 @@ namespace HTTPlease { - using Core; - using Core.Utilities; - using Core.ValueProviders; - - using QueryParameterDictionary = Dictionary>; - using RequestProperties = ImmutableDictionary; - - /// - /// A template for an HTTP request. - /// - public sealed class HttpRequest - : HttpRequestBase, IHttpRequest, IHttpRequest - { - #region Constants - - /// - /// The used a context for all untyped HTTP requests. - /// - internal static readonly object DefaultContext = new object(); - - /// - /// The base properties for s. - /// - static readonly RequestProperties BaseProperties = - new Dictionary - { - [nameof(RequestActions)] = ImmutableList>.Empty, - [nameof(ResponseActions)] = ImmutableList>.Empty, - [nameof(TemplateParameters)] = ImmutableDictionary>.Empty, - [nameof(QueryParameters)] = ImmutableDictionary>.Empty - } - .ToImmutableDictionary(); - - /// - /// An empty . - /// - public static readonly HttpRequest Empty = new HttpRequest(BaseProperties); - - /// - /// The default factory for s. - /// - public static HttpRequestFactory Factory { get; } = new HttpRequestFactory(Empty); - - #endregion // Constants - - #region Construction - - /// - /// Create a new HTTP request. - /// - /// - /// The request properties. - /// - HttpRequest(ImmutableDictionary properties) - : base(properties) - { - EnsurePropertyType>>( - propertyName: nameof(RequestActions) - ); - EnsurePropertyType>>( - propertyName: nameof(TemplateParameters) - ); - EnsurePropertyType>>( - propertyName: nameof(QueryParameters) - ); - } - - /// - /// Create a new HTTP request with the specified request URI. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Create(string requestUri) => Factory.Create(requestUri); - - /// - /// Create a new HTTP request with the specified request URI. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Create(Uri requestUri) => Factory.Create(requestUri); - - #endregion // Construction - - #region Properties - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - public ImmutableList> RequestActions => GetProperty>>(); - - /// - /// Actions (if any) to perform on the incoming response message. - /// - public ImmutableList> ResponseActions => GetProperty>>(); - - /// - /// The request's URI template parameters (if any). - /// - public ImmutableDictionary> TemplateParameters => GetProperty>>(); - - /// - /// The request's query parameters (if any). - /// - public ImmutableDictionary> QueryParameters => GetProperty>>(); - - #endregion // Properties - - #region Invocation - - /// - /// Build and configure a new HTTP request message. - /// - /// - /// The HTTP request method to use. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - public HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, HttpContent body = null, Uri baseUri = null) - { - if (httpMethod == null) - throw new ArgumentNullException(nameof(httpMethod)); - - // Ensure we have an absolute URI. - Uri requestUri = Uri; - if (requestUri == null) - throw new InvalidOperationException("Cannot build a request message; the request does not have a URI."); - - if (!requestUri.IsAbsoluteUri) - { - if (baseUri == null) - throw new InvalidOperationException("Cannot build a request message; the request does not have an absolute request URI, and no base URI was supplied."); - - // Make relative to base URI. - requestUri = baseUri.AppendRelativeUri(requestUri); - } - else - { - // Extract base URI to which request URI is already (by definition) relative. - baseUri = new Uri( - requestUri.GetComponents( - UriComponents.Scheme | UriComponents.StrongAuthority, - UriFormat.UriEscaped - ) - ); - } - - if (IsUriTemplate) - { - UriTemplate template = new UriTemplate( - requestUri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped) - ); - - IDictionary templateParameterValues = GetTemplateParameterValues(); - - requestUri = template.Populate(baseUri, templateParameterValues); - } - - // Merge in any other query parameters defined directly on the request. - requestUri = MergeQueryParameters(requestUri); - - HttpRequestMessage requestMessage = null; - try - { - requestMessage = new HttpRequestMessage(httpMethod, requestUri); - SetStandardMessageProperties(requestMessage); - - if (body != null) - requestMessage.Content = body; - - List configurationActionExceptions = new List(); - foreach (RequestAction requestAction in RequestActions) - { - if (requestAction == null) - continue; - - try - { - requestAction(requestMessage, DefaultContext); - } - catch (Exception eConfigurationAction) - { - configurationActionExceptions.Add(eConfigurationAction); - } - } - - if (configurationActionExceptions.Count > 0) - { - throw new AggregateException( - "One or more unhandled exceptions were encountered while configuring the outgoing request message.", - configurationActionExceptions - ); - } - } - catch - { - using (requestMessage) - { - throw; - } - } - - return requestMessage; - } - - /// - /// Build and configure a new HTTP request message. - /// - /// - /// The HTTP request method to use. - /// - /// - /// The object used as a context for resolving deferred template values. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - HttpRequestMessage IHttpRequest.BuildRequestMessage(HttpMethod httpMethod, object context, HttpContent body, Uri baseUri) - { - return BuildRequestMessage(httpMethod, body, baseUri); - } - - #endregion // Invocation - - #region IHttpRequestProperties - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - IReadOnlyList> IHttpRequestProperties.RequestActions => RequestActions; - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - IReadOnlyList> IHttpRequestProperties.ResponseActions => ResponseActions; - - /// - /// The request's URI template parameters (if any). - /// - IReadOnlyDictionary> IHttpRequestProperties.TemplateParameters => TemplateParameters; - - /// - /// The request's query parameters (if any). - /// - IReadOnlyDictionary> IHttpRequestProperties.QueryParameters => QueryParameters; - - #endregion // IHttpRequestProperties - - #region Cloning - - /// - /// Clone the request. - /// - /// - /// A delegate that performs modifications to the request properties. - /// - /// - /// The cloned request. - /// - public new HttpRequest Clone(Action> modifications) - { - if (modifications == null) - throw new ArgumentNullException(nameof(modifications)); - - return (HttpRequest)base.Clone(modifications); - } - - /// - /// Create a new instance of the HTTP request using the specified properties. - /// - /// - /// The request properties. - /// - /// - /// The new HTTP request instance. - /// - protected override HttpRequestBase CreateInstance(ImmutableDictionary requestProperties) - { - return new HttpRequest(requestProperties); - } - - #endregion // Cloning - - #region Helpers - - /// - /// Merge the request's query parameters (if any) into the request URI. - /// - /// - /// The request URI. - /// - /// - /// The request URI with query parameters merged into it. - /// - Uri MergeQueryParameters(Uri requestUri) - { - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); - - if (QueryParameters.Count == 0) - return requestUri; - - // Unlike other types of template parameters, query parameters are a special case - each parameter can have multiple values (which are rendered as multiple parameters of the form "param=value1¶m=value2". - QueryParameterDictionary queryParameters = requestUri.ParseQueryParameters(); - foreach (KeyValuePair> queryParameter in QueryParameters) - { - string parameterName = queryParameter.Key; - IValueProvider parameterValueProvider = queryParameter.Value; - - List existingValues; - if (!queryParameters.TryGetValue(queryParameter.Key, out existingValues)) - { - existingValues = new List(); - queryParameters.Add(queryParameter.Key, existingValues); - } - - existingValues.AddRange( - parameterValueProvider.GetMultiple(DefaultContext) - ); - if (existingValues.Count == 0) - queryParameters.Remove(queryParameter.Key); - } - - return requestUri.WithQueryParameters(queryParameters); - } - - /// - /// Get a dictionary mapping template parameters (if any) to their current values. - /// - /// - /// A dictionary of key / value pairs (any parameters whose value-getters return null will be omitted). - /// - IDictionary GetTemplateParameterValues() - { - return - TemplateParameters.Select(templateParameter => - { - Debug.Assert(templateParameter.Value != null); - - return new - { - templateParameter.Key, - Value = templateParameter.Value.Get(DefaultContext) - }; - }) - .Where( - templateParameter => templateParameter.Value != null - ) - .ToDictionary( - templateParameter => templateParameter.Key, - templateParameter => templateParameter.Value - ); - } - - /// - /// Configure standard properties for the specified . - /// - /// - /// The . - /// - void SetStandardMessageProperties(HttpRequestMessage requestMessage) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); - - requestMessage.Properties[MessageProperties.Request] = this; - } - - #endregion // Helpers - } + using Core; + using Core.Utilities; + using Core.ValueProviders; + + using QueryParameterDictionary = Dictionary>; + using RequestProperties = ImmutableDictionary; + + /// + /// A template for an HTTP request. + /// + public sealed class HttpRequest + : HttpRequestBase, IHttpRequest, IHttpRequest + { + #region Constants + + /// + /// The used a context for all untyped HTTP requests. + /// + internal static readonly object DefaultContext = new object(); + + /// + /// The base properties for s. + /// + static readonly RequestProperties BaseProperties = + new Dictionary + { + [nameof(RequestActions)] = ImmutableList>.Empty, + [nameof(ResponseActions)] = ImmutableList>.Empty, + [nameof(TemplateParameters)] = ImmutableDictionary>.Empty, + [nameof(QueryParameters)] = ImmutableDictionary>.Empty + } + .ToImmutableDictionary(); + + /// + /// An empty . + /// + public static readonly HttpRequest Empty = new HttpRequest(BaseProperties); + + /// + /// The default factory for s. + /// + public static HttpRequestFactory Factory { get; } = new HttpRequestFactory(Empty); + + #endregion // Constants + + #region Construction + + /// + /// Create a new HTTP request. + /// + /// + /// The request properties. + /// + HttpRequest(ImmutableDictionary properties) + : base(properties) + { + EnsurePropertyType>>( + propertyName: nameof(RequestActions) + ); + EnsurePropertyType>>( + propertyName: nameof(TemplateParameters) + ); + EnsurePropertyType>>( + propertyName: nameof(QueryParameters) + ); + } + + /// + /// Create a new HTTP request with the specified request URI. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Create(string requestUri) => Factory.Create(requestUri); + + /// + /// Create a new HTTP request with the specified request URI. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Create(Uri requestUri) => Factory.Create(requestUri); + + #endregion // Construction + + #region Properties + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + public ImmutableList> RequestActions => GetProperty>>(); + + /// + /// Actions (if any) to perform on the incoming response message. + /// + public ImmutableList> ResponseActions => GetProperty>>(); + + /// + /// The request's URI template parameters (if any). + /// + public ImmutableDictionary> TemplateParameters => GetProperty>>(); + + /// + /// The request's query parameters (if any). + /// + public ImmutableDictionary> QueryParameters => GetProperty>>(); + + #endregion // Properties + + #region Invocation + + /// + /// Build and configure a new HTTP request message. + /// + /// + /// The HTTP request method to use. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + public HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, HttpContent body = null, Uri baseUri = null) + { + if (httpMethod == null) + throw new ArgumentNullException(nameof(httpMethod)); + + // Ensure we have an absolute URI. + Uri requestUri = Uri; + if (requestUri == null) + throw new InvalidOperationException("Cannot build a request message; the request does not have a URI."); + + if (!requestUri.IsAbsoluteUri) + { + if (baseUri == null) + throw new InvalidOperationException("Cannot build a request message; the request does not have an absolute request URI, and no base URI was supplied."); + + // Make relative to base URI. + requestUri = baseUri.AppendRelativeUri(requestUri); + } + else + { + // Extract base URI to which request URI is already (by definition) relative. + baseUri = new Uri( + requestUri.GetComponents( + UriComponents.Scheme | UriComponents.StrongAuthority, + UriFormat.UriEscaped + ) + ); + } + + if (IsUriTemplate) + { + UriTemplate template = new UriTemplate( + requestUri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped) + ); + + IDictionary templateParameterValues = GetTemplateParameterValues(); + + requestUri = template.Populate(baseUri, templateParameterValues); + } + + // Merge in any other query parameters defined directly on the request. + requestUri = MergeQueryParameters(requestUri); + + HttpRequestMessage requestMessage = null; + try + { + requestMessage = new HttpRequestMessage(httpMethod, requestUri); + SetStandardMessageProperties(requestMessage); + + if (body != null) + requestMessage.Content = body; + + List configurationActionExceptions = new List(); + foreach (RequestAction requestAction in RequestActions) + { + if (requestAction == null) + continue; + + try + { + requestAction(requestMessage, DefaultContext); + } + catch (Exception eConfigurationAction) + { + configurationActionExceptions.Add(eConfigurationAction); + } + } + + if (configurationActionExceptions.Count > 0) + { + throw new AggregateException( + "One or more unhandled exceptions were encountered while configuring the outgoing request message.", + configurationActionExceptions + ); + } + } + catch + { + using (requestMessage) + { + throw; + } + } + + return requestMessage; + } + + /// + /// Build and configure a new HTTP request message. + /// + /// + /// The HTTP request method to use. + /// + /// + /// The object used as a context for resolving deferred template values. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + HttpRequestMessage IHttpRequest.BuildRequestMessage(HttpMethod httpMethod, object context, HttpContent body, Uri baseUri) + { + return BuildRequestMessage(httpMethod, body, baseUri); + } + + #endregion // Invocation + + #region IHttpRequestProperties + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + IReadOnlyList> IHttpRequestProperties.RequestActions => RequestActions; + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + IReadOnlyList> IHttpRequestProperties.ResponseActions => ResponseActions; + + /// + /// The request's URI template parameters (if any). + /// + IReadOnlyDictionary> IHttpRequestProperties.TemplateParameters => TemplateParameters; + + /// + /// The request's query parameters (if any). + /// + IReadOnlyDictionary> IHttpRequestProperties.QueryParameters => QueryParameters; + + #endregion // IHttpRequestProperties + + #region Cloning + + /// + /// Clone the request. + /// + /// + /// A delegate that performs modifications to the request properties. + /// + /// + /// The cloned request. + /// + public new HttpRequest Clone(Action> modifications) + { + if (modifications == null) + throw new ArgumentNullException(nameof(modifications)); + + return (HttpRequest)base.Clone(modifications); + } + + /// + /// Create a new instance of the HTTP request using the specified properties. + /// + /// + /// The request properties. + /// + /// + /// The new HTTP request instance. + /// + protected override HttpRequestBase CreateInstance(ImmutableDictionary requestProperties) + { + return new HttpRequest(requestProperties); + } + + #endregion // Cloning + + #region Helpers + + /// + /// Merge the request's query parameters (if any) into the request URI. + /// + /// + /// The request URI. + /// + /// + /// The request URI with query parameters merged into it. + /// + Uri MergeQueryParameters(Uri requestUri) + { + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); + + if (QueryParameters.Count == 0) + return requestUri; + + // Unlike other types of template parameters, query parameters are a special case - each parameter can have multiple values (which are rendered as multiple parameters of the form "param=value1¶m=value2". + QueryParameterDictionary queryParameters = requestUri.ParseQueryParameters(); + foreach (KeyValuePair> queryParameter in QueryParameters) + { + string parameterName = queryParameter.Key; + IValueProvider parameterValueProvider = queryParameter.Value; + + List existingValues; + if (!queryParameters.TryGetValue(queryParameter.Key, out existingValues)) + { + existingValues = new List(); + queryParameters.Add(queryParameter.Key, existingValues); + } + + existingValues.AddRange( + parameterValueProvider.GetMultiple(DefaultContext) + ); + if (existingValues.Count == 0) + queryParameters.Remove(queryParameter.Key); + } + + return requestUri.WithQueryParameters(queryParameters); + } + + /// + /// Get a dictionary mapping template parameters (if any) to their current values. + /// + /// + /// A dictionary of key / value pairs (any parameters whose value-getters return null will be omitted). + /// + IDictionary GetTemplateParameterValues() + { + return + TemplateParameters.Select(templateParameter => + { + Debug.Assert(templateParameter.Value != null); + + return new + { + templateParameter.Key, + Value = templateParameter.Value.Get(DefaultContext) + }; + }) + .Where( + templateParameter => templateParameter.Value != null + ) + .ToDictionary( + templateParameter => templateParameter.Key, + templateParameter => templateParameter.Value + ); + } + + /// + /// Configure standard properties for the specified . + /// + /// + /// The . + /// + void SetStandardMessageProperties(HttpRequestMessage requestMessage) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); + + requestMessage.Properties[MessageProperties.Request] = this; + } + + #endregion // Helpers + } } diff --git a/src/HTTPlease.Core/HttpRequestException.cs b/src/HTTPlease.Core/HttpRequestException.cs index bc3a243..d3be3a9 100644 --- a/src/HTTPlease.Core/HttpRequestException.cs +++ b/src/HTTPlease.Core/HttpRequestException.cs @@ -3,79 +3,79 @@ namespace HTTPlease { - /// - /// Exception thrown when an error response is received while making an HTTP request. - /// - /// - /// TODO: Throw this from response.ReadContentAsAsync<TResponse, TErrorResponse>. - /// - public class HttpRequestException - : HttpRequestException - { - /// - /// Create a new . - /// - /// - /// The response's HTTP status code. - /// - /// - /// The response body. - /// - public HttpRequestException(HttpStatusCode statusCode, TResponse response) - : this(statusCode, response, $"The request failed with unexpected status code '{statusCode}'.") - { - } + /// + /// Exception thrown when an error response is received while making an HTTP request. + /// + /// + /// TODO: Throw this from response.ReadContentAsAsync<TResponse, TErrorResponse>. + /// + public class HttpRequestException + : HttpRequestException + { + /// + /// Create a new . + /// + /// + /// The response's HTTP status code. + /// + /// + /// The response body. + /// + public HttpRequestException(HttpStatusCode statusCode, TResponse response) + : this(statusCode, response, $"The request failed with unexpected status code '{statusCode}'.") + { + } - /// - /// Create a new . - /// - /// - /// The response's HTTP status code. - /// - /// - /// The response body. - /// - /// - /// The exception message. - /// - public HttpRequestException(HttpStatusCode statusCode, TResponse response, string message) - : base(message) - { - StatusCode = statusCode; - Response = response; - } + /// + /// Create a new . + /// + /// + /// The response's HTTP status code. + /// + /// + /// The response body. + /// + /// + /// The exception message. + /// + public HttpRequestException(HttpStatusCode statusCode, TResponse response, string message) + : base(message) + { + StatusCode = statusCode; + Response = response; + } - /// - /// The response's HTTP status code. - /// - public HttpStatusCode StatusCode { get; } + /// + /// The response's HTTP status code. + /// + public HttpStatusCode StatusCode { get; } - /// - /// The response body. - /// - public TResponse Response { get; } + /// + /// The response body. + /// + public TResponse Response { get; } - /// - /// Create a new . - /// - /// - /// The HTTP response status code. - /// - /// - /// A representing the HTTP response body. - /// - /// - /// The configured . - /// - public static HttpRequestException Create(HttpStatusCode statusCode, TResponse response) - { - string message = $"HTTP request failed ({statusCode})."; + /// + /// Create a new . + /// + /// + /// The HTTP response status code. + /// + /// + /// A representing the HTTP response body. + /// + /// + /// The configured . + /// + public static HttpRequestException Create(HttpStatusCode statusCode, TResponse response) + { + string message = $"HTTP request failed ({statusCode})."; - IHttpErrorResponse errorResponse = response as IHttpErrorResponse; - if (errorResponse != null) - message = errorResponse.GetExceptionMesage(); + IHttpErrorResponse errorResponse = response as IHttpErrorResponse; + if (errorResponse != null) + message = errorResponse.GetExceptionMesage(); - return new HttpRequestException(statusCode, response, message); - } - } + return new HttpRequestException(statusCode, response, message); + } + } } diff --git a/src/HTTPlease.Core/HttpRequestFactory.cs b/src/HTTPlease.Core/HttpRequestFactory.cs index ef4b173..c34ecf6 100644 --- a/src/HTTPlease.Core/HttpRequestFactory.cs +++ b/src/HTTPlease.Core/HttpRequestFactory.cs @@ -2,90 +2,90 @@ namespace HTTPlease { - /// - /// A facility for creating s. - /// - public sealed class HttpRequestFactory + /// + /// A facility for creating s. + /// + public sealed class HttpRequestFactory { - /// - /// Create a new . - /// - /// - /// The used as a base for requests created by the factory. - /// - public HttpRequestFactory(HttpRequest baseRequest) - { - if (baseRequest == null) - throw new ArgumentNullException(nameof(baseRequest)); + /// + /// Create a new . + /// + /// + /// The used as a base for requests created by the factory. + /// + public HttpRequestFactory(HttpRequest baseRequest) + { + if (baseRequest == null) + throw new ArgumentNullException(nameof(baseRequest)); - BaseRequest = baseRequest; - } + BaseRequest = baseRequest; + } - /// - /// The used as a base for requests created by the factory. - /// - public HttpRequest BaseRequest { get; } + /// + /// The used as a base for requests created by the factory. + /// + public HttpRequest BaseRequest { get; } - /// - /// Create a new with the specified request URI. - /// - /// - /// The request URI. - /// - /// - /// The new . - /// - public HttpRequest Create(Uri requestUri) - { - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + /// + /// Create a new with the specified request URI. + /// + /// + /// The request URI. + /// + /// + /// The new . + /// + public HttpRequest Create(Uri requestUri) + { + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return BaseRequest.WithUri(requestUri); - } + return BaseRequest.WithUri(requestUri); + } } - /// - /// A facility for creating s. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - public sealed class HttpRequestFactory - { - /// - /// Create a new . - /// - /// - /// The used as a base for requests created by the factory. - /// - public HttpRequestFactory(HttpRequest baseRequest) - { - if (baseRequest == null) - throw new ArgumentNullException(nameof(baseRequest)); + /// + /// A facility for creating s. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + public sealed class HttpRequestFactory + { + /// + /// Create a new . + /// + /// + /// The used as a base for requests created by the factory. + /// + public HttpRequestFactory(HttpRequest baseRequest) + { + if (baseRequest == null) + throw new ArgumentNullException(nameof(baseRequest)); - BaseRequest = baseRequest; - } + BaseRequest = baseRequest; + } - /// - /// The used as a base for requests created by the factory. - /// - public HttpRequest BaseRequest { get; } + /// + /// The used as a base for requests created by the factory. + /// + public HttpRequest BaseRequest { get; } - /// - /// Create a new with the specified request URI. - /// - /// - /// The request URI. - /// - /// - /// The new . - /// - public HttpRequest Create(Uri requestUri) - { - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + /// + /// Create a new with the specified request URI. + /// + /// + /// The request URI. + /// + /// + /// The new . + /// + public HttpRequest Create(Uri requestUri) + { + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return BaseRequest.WithUri(requestUri); - } - } + return BaseRequest.WithUri(requestUri); + } + } } diff --git a/src/HTTPlease.Core/HttpRequestOfTContext.cs b/src/HTTPlease.Core/HttpRequestOfTContext.cs index 7649411..5ac98fb 100644 --- a/src/HTTPlease.Core/HttpRequestOfTContext.cs +++ b/src/HTTPlease.Core/HttpRequestOfTContext.cs @@ -8,377 +8,377 @@ namespace HTTPlease { - using Core; - using Core.Utilities; - using Core.ValueProviders; - - using QueryParameterDictionary = Dictionary>; - using RequestProperties = ImmutableDictionary; - - /// - /// A template for an HTTP request that resolves deferred values from an instance of . - /// - /// - /// The type of object used as a context for resolving deferred values. - /// - public class HttpRequest - : HttpRequestBase, IHttpRequest - { - #region Constants - - /// - /// The base properties for s. - /// - static readonly RequestProperties BaseProperties = - new Dictionary - { - [nameof(RequestActions)] = ImmutableList>.Empty, - [nameof(ResponseActions)] = ImmutableList>.Empty, - [nameof(TemplateParameters)] = ImmutableDictionary>.Empty, - [nameof(QueryParameters)] = ImmutableDictionary>.Empty - } - .ToImmutableDictionary(); - - /// - /// An empty . - /// - public static HttpRequest Empty = new HttpRequest(BaseProperties); - - /// - /// The default factory for s. - /// - public static HttpRequestFactory Factory { get; } = new HttpRequestFactory(Empty); - - #endregion // Constants - - #region Construction - - /// - /// Create a new HTTP request. - /// - /// - /// The request properties. - /// - HttpRequest(ImmutableDictionary properties) - : base(properties) - { - EnsurePropertyType>>( - propertyName: nameof(RequestActions) - ); - EnsurePropertyType>>( - propertyName: nameof(TemplateParameters) - ); - EnsurePropertyType>>( - propertyName: nameof(QueryParameters) - ); - } - - /// - /// Create a new HTTP request with the specified request URI. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Create(string requestUri) => Factory.Create(requestUri); - - /// - /// Create a new HTTP request with the specified request URI. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Create(Uri requestUri) => Factory.Create(requestUri); - - #endregion // Construction - - #region Properties - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - public ImmutableList> RequestActions => GetProperty>>(); - - /// - /// Actions (if any) to perform on the incoming response message. - /// - public ImmutableList> ResponseActions => GetProperty>>(); - - /// - /// The request's URI template parameters (if any). - /// - public ImmutableDictionary> TemplateParameters => GetProperty>>(); - - /// - /// The request's query parameters (if any). - /// - public ImmutableDictionary> QueryParameters => GetProperty>>(); - - #endregion // Properties - - #region Invocation - - /// - /// Build and configure a new HTTP request message. - /// - /// - /// The HTTP request method to use. - /// - /// - /// The object used as a context for resolving deferred template values. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - public HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, TContext context, HttpContent body = null, Uri baseUri = null) - { - if (httpMethod == null) - throw new ArgumentNullException(nameof(httpMethod)); - - // Ensure we have an absolute URI. - Uri requestUri = Uri; - if (requestUri == null) - throw new InvalidOperationException("Cannot build a request message; the request does not have a URI."); - - if (!requestUri.IsAbsoluteUri) - { - if (baseUri == null) - throw new InvalidOperationException("Cannot build a request message; the request does not have an absolute request URI, and no base URI was supplied."); - - // Make relative to base URI. - requestUri = baseUri.AppendRelativeUri(requestUri); - } - else - { - // Extract base URI to which request URI is already (by definition) relative. - baseUri = new Uri( - requestUri.GetComponents( - UriComponents.Scheme | UriComponents.StrongAuthority, - UriFormat.UriEscaped - ) - ); - } - - if (IsUriTemplate) - { - UriTemplate template = new UriTemplate( - requestUri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped) - ); - - IDictionary templateParameterValues = GetTemplateParameterValues(context); - - requestUri = template.Populate(baseUri, templateParameterValues); - } - - // Merge in any other query parameters defined directly on the request. - requestUri = MergeQueryParameters(requestUri, context); - - HttpRequestMessage requestMessage = null; - try - { - requestMessage = new HttpRequestMessage(httpMethod, requestUri); - SetStandardMessageProperties(requestMessage); - - if (body != null) - requestMessage.Content = body; - - List configurationActionExceptions = new List(); - foreach (RequestAction requestAction in RequestActions) - { - if (requestAction == null) - continue; - - try - { - requestAction(requestMessage, context); - } - catch (Exception eConfigurationAction) - { - configurationActionExceptions.Add(eConfigurationAction); - } - } - - if (configurationActionExceptions.Count > 0) - { - throw new AggregateException( - "One or more unhandled exceptions were encountered while configuring the outgoing request message.", - configurationActionExceptions - ); - } - } - catch - { - using (requestMessage) - { - throw; - } - } - - return requestMessage; - } - - #endregion // Invocation - - #region IHttpRequest - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - IReadOnlyList> IHttpRequestProperties.RequestActions => RequestActions; - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - IReadOnlyList> IHttpRequestProperties.ResponseActions => ResponseActions; - - /// - /// The request's URI template parameters (if any). - /// - IReadOnlyDictionary> IHttpRequestProperties.TemplateParameters => TemplateParameters; - - /// - /// The request's query parameters (if any). - /// - IReadOnlyDictionary> IHttpRequestProperties.QueryParameters => QueryParameters; - - #endregion // IHttpRequest - - #region Cloning - - /// - /// Clone the request. - /// - /// - /// A delegate that performs modifications to the request properties. - /// - /// - /// The cloned request. - /// - public new HttpRequest Clone(Action> modifications) - { - if (modifications == null) - throw new ArgumentNullException(nameof(modifications)); - - return (HttpRequest)base.Clone(modifications); - } - - /// - /// Create a new instance of the HTTP request using the specified properties. - /// - /// - /// The request properties. - /// - /// - /// The new HTTP request instance. - /// - protected override HttpRequestBase CreateInstance(ImmutableDictionary requestProperties) - { - return new HttpRequest(requestProperties); - } - - #endregion // Cloning - - #region Helpers - - /// - /// Merge the request's query parameters (if any) into the request URI. - /// - /// - /// The request URI. - /// - /// - /// The from which parameter values will be resolved. - /// - /// - /// The request URI with query parameters merged into it. - /// - Uri MergeQueryParameters(Uri requestUri, TContext context) - { - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); - - if (QueryParameters.Count == 0) - return requestUri; - - // Unlike other types of template parameters, query parameters are a special case - each parameter can have multiple values (which are rendered as multiple parameters of the form "param=value1¶m=value2". - QueryParameterDictionary queryParameters = requestUri.ParseQueryParameters(); - foreach (KeyValuePair> queryParameter in QueryParameters) - { - string parameterName = queryParameter.Key; - - List existingValues; - if (!queryParameters.TryGetValue(queryParameter.Key, out existingValues)) - { - existingValues = new List(); - queryParameters.Add(queryParameter.Key, existingValues); - } - - string queryParameterValue = queryParameter.Value.Get(context); - if (queryParameterValue != null) - existingValues.Add(queryParameterValue); - else if (existingValues.Count == 0) - queryParameters.Remove(queryParameter.Key); - } - - return requestUri.WithQueryParameters(queryParameters); - } - - /// - /// Get a dictionary mapping template parameters (if any) to their current values. - /// - /// - /// The from which parameter values will be resolved. - /// - /// - /// A dictionary of key / value pairs (any parameters whose value-getters return null will be omitted). - /// - IDictionary GetTemplateParameterValues(TContext context) - { - return - TemplateParameters.Select(templateParameter => - { - Debug.Assert(templateParameter.Value != null); - - return new - { - templateParameter.Key, - Value = templateParameter.Value.Get(context) - }; - }) - .Where( - templateParameter => templateParameter.Value != null - ) - .ToDictionary( - templateParameter => templateParameter.Key, - templateParameter => templateParameter.Value - ); - } - - /// - /// Configure standard properties for the specified . - /// - /// - /// The . - /// - void SetStandardMessageProperties(HttpRequestMessage requestMessage) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); - - requestMessage.Properties[MessageProperties.Request] = this; - } - - #endregion // Helpers - } + using Core; + using Core.Utilities; + using Core.ValueProviders; + + using QueryParameterDictionary = Dictionary>; + using RequestProperties = ImmutableDictionary; + + /// + /// A template for an HTTP request that resolves deferred values from an instance of . + /// + /// + /// The type of object used as a context for resolving deferred values. + /// + public class HttpRequest + : HttpRequestBase, IHttpRequest + { + #region Constants + + /// + /// The base properties for s. + /// + static readonly RequestProperties BaseProperties = + new Dictionary + { + [nameof(RequestActions)] = ImmutableList>.Empty, + [nameof(ResponseActions)] = ImmutableList>.Empty, + [nameof(TemplateParameters)] = ImmutableDictionary>.Empty, + [nameof(QueryParameters)] = ImmutableDictionary>.Empty + } + .ToImmutableDictionary(); + + /// + /// An empty . + /// + public static HttpRequest Empty = new HttpRequest(BaseProperties); + + /// + /// The default factory for s. + /// + public static HttpRequestFactory Factory { get; } = new HttpRequestFactory(Empty); + + #endregion // Constants + + #region Construction + + /// + /// Create a new HTTP request. + /// + /// + /// The request properties. + /// + HttpRequest(ImmutableDictionary properties) + : base(properties) + { + EnsurePropertyType>>( + propertyName: nameof(RequestActions) + ); + EnsurePropertyType>>( + propertyName: nameof(TemplateParameters) + ); + EnsurePropertyType>>( + propertyName: nameof(QueryParameters) + ); + } + + /// + /// Create a new HTTP request with the specified request URI. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Create(string requestUri) => Factory.Create(requestUri); + + /// + /// Create a new HTTP request with the specified request URI. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Create(Uri requestUri) => Factory.Create(requestUri); + + #endregion // Construction + + #region Properties + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + public ImmutableList> RequestActions => GetProperty>>(); + + /// + /// Actions (if any) to perform on the incoming response message. + /// + public ImmutableList> ResponseActions => GetProperty>>(); + + /// + /// The request's URI template parameters (if any). + /// + public ImmutableDictionary> TemplateParameters => GetProperty>>(); + + /// + /// The request's query parameters (if any). + /// + public ImmutableDictionary> QueryParameters => GetProperty>>(); + + #endregion // Properties + + #region Invocation + + /// + /// Build and configure a new HTTP request message. + /// + /// + /// The HTTP request method to use. + /// + /// + /// The object used as a context for resolving deferred template values. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + public HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, TContext context, HttpContent body = null, Uri baseUri = null) + { + if (httpMethod == null) + throw new ArgumentNullException(nameof(httpMethod)); + + // Ensure we have an absolute URI. + Uri requestUri = Uri; + if (requestUri == null) + throw new InvalidOperationException("Cannot build a request message; the request does not have a URI."); + + if (!requestUri.IsAbsoluteUri) + { + if (baseUri == null) + throw new InvalidOperationException("Cannot build a request message; the request does not have an absolute request URI, and no base URI was supplied."); + + // Make relative to base URI. + requestUri = baseUri.AppendRelativeUri(requestUri); + } + else + { + // Extract base URI to which request URI is already (by definition) relative. + baseUri = new Uri( + requestUri.GetComponents( + UriComponents.Scheme | UriComponents.StrongAuthority, + UriFormat.UriEscaped + ) + ); + } + + if (IsUriTemplate) + { + UriTemplate template = new UriTemplate( + requestUri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped) + ); + + IDictionary templateParameterValues = GetTemplateParameterValues(context); + + requestUri = template.Populate(baseUri, templateParameterValues); + } + + // Merge in any other query parameters defined directly on the request. + requestUri = MergeQueryParameters(requestUri, context); + + HttpRequestMessage requestMessage = null; + try + { + requestMessage = new HttpRequestMessage(httpMethod, requestUri); + SetStandardMessageProperties(requestMessage); + + if (body != null) + requestMessage.Content = body; + + List configurationActionExceptions = new List(); + foreach (RequestAction requestAction in RequestActions) + { + if (requestAction == null) + continue; + + try + { + requestAction(requestMessage, context); + } + catch (Exception eConfigurationAction) + { + configurationActionExceptions.Add(eConfigurationAction); + } + } + + if (configurationActionExceptions.Count > 0) + { + throw new AggregateException( + "One or more unhandled exceptions were encountered while configuring the outgoing request message.", + configurationActionExceptions + ); + } + } + catch + { + using (requestMessage) + { + throw; + } + } + + return requestMessage; + } + + #endregion // Invocation + + #region IHttpRequest + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + IReadOnlyList> IHttpRequestProperties.RequestActions => RequestActions; + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + IReadOnlyList> IHttpRequestProperties.ResponseActions => ResponseActions; + + /// + /// The request's URI template parameters (if any). + /// + IReadOnlyDictionary> IHttpRequestProperties.TemplateParameters => TemplateParameters; + + /// + /// The request's query parameters (if any). + /// + IReadOnlyDictionary> IHttpRequestProperties.QueryParameters => QueryParameters; + + #endregion // IHttpRequest + + #region Cloning + + /// + /// Clone the request. + /// + /// + /// A delegate that performs modifications to the request properties. + /// + /// + /// The cloned request. + /// + public new HttpRequest Clone(Action> modifications) + { + if (modifications == null) + throw new ArgumentNullException(nameof(modifications)); + + return (HttpRequest)base.Clone(modifications); + } + + /// + /// Create a new instance of the HTTP request using the specified properties. + /// + /// + /// The request properties. + /// + /// + /// The new HTTP request instance. + /// + protected override HttpRequestBase CreateInstance(ImmutableDictionary requestProperties) + { + return new HttpRequest(requestProperties); + } + + #endregion // Cloning + + #region Helpers + + /// + /// Merge the request's query parameters (if any) into the request URI. + /// + /// + /// The request URI. + /// + /// + /// The from which parameter values will be resolved. + /// + /// + /// The request URI with query parameters merged into it. + /// + Uri MergeQueryParameters(Uri requestUri, TContext context) + { + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); + + if (QueryParameters.Count == 0) + return requestUri; + + // Unlike other types of template parameters, query parameters are a special case - each parameter can have multiple values (which are rendered as multiple parameters of the form "param=value1¶m=value2". + QueryParameterDictionary queryParameters = requestUri.ParseQueryParameters(); + foreach (KeyValuePair> queryParameter in QueryParameters) + { + string parameterName = queryParameter.Key; + + List existingValues; + if (!queryParameters.TryGetValue(queryParameter.Key, out existingValues)) + { + existingValues = new List(); + queryParameters.Add(queryParameter.Key, existingValues); + } + + string queryParameterValue = queryParameter.Value.Get(context); + if (queryParameterValue != null) + existingValues.Add(queryParameterValue); + else if (existingValues.Count == 0) + queryParameters.Remove(queryParameter.Key); + } + + return requestUri.WithQueryParameters(queryParameters); + } + + /// + /// Get a dictionary mapping template parameters (if any) to their current values. + /// + /// + /// The from which parameter values will be resolved. + /// + /// + /// A dictionary of key / value pairs (any parameters whose value-getters return null will be omitted). + /// + IDictionary GetTemplateParameterValues(TContext context) + { + return + TemplateParameters.Select(templateParameter => + { + Debug.Assert(templateParameter.Value != null); + + return new + { + templateParameter.Key, + Value = templateParameter.Value.Get(context) + }; + }) + .Where( + templateParameter => templateParameter.Value != null + ) + .ToDictionary( + templateParameter => templateParameter.Key, + templateParameter => templateParameter.Value + ); + } + + /// + /// Configure standard properties for the specified . + /// + /// + /// The . + /// + void SetStandardMessageProperties(HttpRequestMessage requestMessage) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); + + requestMessage.Properties[MessageProperties.Request] = this; + } + + #endregion // Helpers + } } diff --git a/src/HTTPlease.Core/HttpResponse.cs b/src/HTTPlease.Core/HttpResponse.cs index 2cd91d6..f61ed7c 100644 --- a/src/HTTPlease.Core/HttpResponse.cs +++ b/src/HTTPlease.Core/HttpResponse.cs @@ -5,101 +5,101 @@ namespace HTTPlease { - /// - /// The response from an asynchronous invocation of an . - /// - public struct HttpResponse - { - /// - /// Create a new . - /// - /// - /// The request whose response is represented by the . - /// - /// - /// The underlying represented by the . - /// - public HttpResponse(HttpRequest request, Task task) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// The response from an asynchronous invocation of an . + /// + public struct HttpResponse + { + /// + /// Create a new . + /// + /// + /// The request whose response is represented by the . + /// + /// + /// The underlying represented by the . + /// + public HttpResponse(HttpRequest request, Task task) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (task == null) - throw new ArgumentNullException(nameof(task)); + if (task == null) + throw new ArgumentNullException(nameof(task)); - Request = request; - Task = task; - } + Request = request; + Task = task; + } - /// - /// Create a new for the specified asynchronous action. - /// - /// - /// The request whose response is represented by the . - /// - /// - /// An asynchronous delegate that produces the action's resulting . - /// - public HttpResponse(HttpRequest request, Func> asyncAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a new for the specified asynchronous action. + /// + /// + /// The request whose response is represented by the . + /// + /// + /// An asynchronous delegate that produces the action's resulting . + /// + public HttpResponse(HttpRequest request, Func> asyncAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (asyncAction == null) - throw new ArgumentNullException(nameof(asyncAction)); + if (asyncAction == null) + throw new ArgumentNullException(nameof(asyncAction)); - Request = request; - Task = asyncAction(); - if (Task == null) - throw new InvalidOperationException("The asynchronous action delegate returned null."); - } + Request = request; + Task = asyncAction(); + if (Task == null) + throw new InvalidOperationException("The asynchronous action delegate returned null."); + } - /// - /// The request whose response is represented by the . - /// - public HttpRequest Request { get; set; } + /// + /// The request whose response is represented by the . + /// + public HttpRequest Request { get; set; } - /// - /// The underlying represented by the . - /// - public Task Task { get; } + /// + /// The underlying represented by the . + /// + public Task Task { get; } - // TODO: Considering something like Promise's "Then" method to encapsulate the construction of a new HttpResponse using the same HttpRequest but a new async action. + // TODO: Considering something like Promise's "Then" method to encapsulate the construction of a new HttpResponse using the same HttpRequest but a new async action. - /// - /// Get an awaiter for the underlying represented by the . - /// - /// - /// The task awaiter. - /// - /// - /// Enables directly awaiting the . - /// - public TaskAwaiter GetAwaiter() => Task.GetAwaiter(); + /// + /// Get an awaiter for the underlying represented by the . + /// + /// + /// The task awaiter. + /// + /// + /// Enables directly awaiting the . + /// + public TaskAwaiter GetAwaiter() => Task.GetAwaiter(); - /// - /// Configure the way that the response's task is awaited. - /// - /// - /// Should the awaited task return to the ambient synchronisation context? - /// - /// - /// A that can be awaited. - /// - public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => Task.ConfigureAwait(continueOnCapturedContext); + /// + /// Configure the way that the response's task is awaited. + /// + /// + /// Should the awaited task return to the ambient synchronisation context? + /// + /// + /// A that can be awaited. + /// + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => Task.ConfigureAwait(continueOnCapturedContext); - /// - /// Implicit conversion from to - /// - /// - /// The to convert. - /// - /// - /// The 's . - /// - public static implicit operator Task(HttpResponse httpResponse) - { - return httpResponse.Task; - } - } + /// + /// Implicit conversion from to + /// + /// + /// The to convert. + /// + /// + /// The 's . + /// + public static implicit operator Task(HttpResponse httpResponse) + { + return httpResponse.Task; + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Core/IHttpRequest.cs b/src/HTTPlease.Core/IHttpRequest.cs index 0c60d34..9b30cee 100644 --- a/src/HTTPlease.Core/IHttpRequest.cs +++ b/src/HTTPlease.Core/IHttpRequest.cs @@ -3,57 +3,57 @@ namespace HTTPlease { - /// - /// Represents a template for building HTTP requests. - /// - public interface IHttpRequest - : IHttpRequestProperties - { - /// - /// Build and configure a new HTTP request message. - /// - /// - /// The HTTP request method to use. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional base URI to use if the request builder does not already have an absolute request URI. - /// - /// - /// The configured . - /// - HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, HttpContent body = null, Uri baseUri = null); + /// + /// Represents a template for building HTTP requests. + /// + public interface IHttpRequest + : IHttpRequestProperties + { + /// + /// Build and configure a new HTTP request message. + /// + /// + /// The HTTP request method to use. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional base URI to use if the request builder does not already have an absolute request URI. + /// + /// + /// The configured . + /// + HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, HttpContent body = null, Uri baseUri = null); } - /// - /// Represents a template for building HTTP requests with lazily-resolved values extracted from a specific context. - /// - /// - /// The type of object used by the request when resolving deferred values. - /// - public interface IHttpRequest - : IHttpRequestProperties - { - /// - /// Build and configure a new HTTP request message. - /// - /// - /// The HTTP request method to use. - /// - /// - /// The to use as the context for resolving any deferred template or query parameters. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional base URI to use if the request builder does not already have an absolute request URI. - /// - /// - /// The configured . - /// - HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, TContext context, HttpContent body = null, Uri baseUri = null); - } + /// + /// Represents a template for building HTTP requests with lazily-resolved values extracted from a specific context. + /// + /// + /// The type of object used by the request when resolving deferred values. + /// + public interface IHttpRequest + : IHttpRequestProperties + { + /// + /// Build and configure a new HTTP request message. + /// + /// + /// The HTTP request method to use. + /// + /// + /// The to use as the context for resolving any deferred template or query parameters. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional base URI to use if the request builder does not already have an absolute request URI. + /// + /// + /// The configured . + /// + HttpRequestMessage BuildRequestMessage(HttpMethod httpMethod, TContext context, HttpContent body = null, Uri baseUri = null); + } } diff --git a/src/HTTPlease.Core/IHttpRequestProperties.cs b/src/HTTPlease.Core/IHttpRequestProperties.cs index 740cfe7..a9ce6f5 100644 --- a/src/HTTPlease.Core/IHttpRequestProperties.cs +++ b/src/HTTPlease.Core/IHttpRequestProperties.cs @@ -4,57 +4,57 @@ namespace HTTPlease { - using Core; - using Core.ValueProviders; - - /// - /// Represents common properties of templates for building HTTP requests. - /// - public interface IHttpRequestProperties - { - /// - /// The request URI. - /// - Uri Uri { get; } - - /// - /// Is the request URI a template? - /// - bool IsUriTemplate { get; } - - /// - /// Additional properties for the request. - /// - ImmutableDictionary Properties { get; } - } - - /// - /// Represents common properties of templates for building HTTP requests. - /// - /// - /// The type of object used as a context for resolving deferred template parameters. - /// - public interface IHttpRequestProperties - : IHttpRequestProperties - { - /// - /// Actions (if any) to perform on the outgoing request message. - /// - IReadOnlyList> RequestActions { get; } - - /// - /// Actions (if any) to perform on the outgoing request message. - /// - IReadOnlyList> ResponseActions { get; } - - /// - /// The request's URI template parameters (if any). - /// - IReadOnlyDictionary> TemplateParameters { get; } - - /// - /// The request's query parameters (if any). - /// - IReadOnlyDictionary> QueryParameters { get; } - } + using Core; + using Core.ValueProviders; + + /// + /// Represents common properties of templates for building HTTP requests. + /// + public interface IHttpRequestProperties + { + /// + /// The request URI. + /// + Uri Uri { get; } + + /// + /// Is the request URI a template? + /// + bool IsUriTemplate { get; } + + /// + /// Additional properties for the request. + /// + ImmutableDictionary Properties { get; } + } + + /// + /// Represents common properties of templates for building HTTP requests. + /// + /// + /// The type of object used as a context for resolving deferred template parameters. + /// + public interface IHttpRequestProperties + : IHttpRequestProperties + { + /// + /// Actions (if any) to perform on the outgoing request message. + /// + IReadOnlyList> RequestActions { get; } + + /// + /// Actions (if any) to perform on the outgoing request message. + /// + IReadOnlyList> ResponseActions { get; } + + /// + /// The request's URI template parameters (if any). + /// + IReadOnlyDictionary> TemplateParameters { get; } + + /// + /// The request's query parameters (if any). + /// + IReadOnlyDictionary> QueryParameters { get; } + } } diff --git a/src/HTTPlease.Core/MessageExtensions.cs b/src/HTTPlease.Core/MessageExtensions.cs index 379379f..40d2650 100644 --- a/src/HTTPlease.Core/MessageExtensions.cs +++ b/src/HTTPlease.Core/MessageExtensions.cs @@ -3,51 +3,51 @@ namespace HTTPlease { - /// - /// Extension methods for / . - /// - public static class MessageExtensions - { - /// - /// Determine whether the request message has been configured for a streamed response. - /// - /// - /// The HTTP request message. - /// - /// - /// true, if the request message has been configured for a streamed response; otherwise, false. - /// - public static bool IsStreamed(this HttpRequestMessage message) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); - - object isStreamedValue; - message.Properties.TryGetValue(MessageProperties.IsStreamed, out isStreamedValue); + /// + /// Extension methods for / . + /// + public static class MessageExtensions + { + /// + /// Determine whether the request message has been configured for a streamed response. + /// + /// + /// The HTTP request message. + /// + /// + /// true, if the request message has been configured for a streamed response; otherwise, false. + /// + public static bool IsStreamed(this HttpRequestMessage message) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); + + object isStreamedValue; + message.Properties.TryGetValue(MessageProperties.IsStreamed, out isStreamedValue); - return (isStreamedValue as bool?) ?? false; - } + return (isStreamedValue as bool?) ?? false; + } - /// - /// Mark the request message as configured for a streamed / buffered response. - /// - /// - /// The HTTP request message. - /// - /// - /// If true, the request message is configured for a streamed response; otherwise, it is configured for a buffered response. - /// - /// - /// The HTTP request message (enables inline use). - /// - public static HttpRequestMessage MarkAsStreamed(this HttpRequestMessage message, bool isStreamed = true) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); + /// + /// Mark the request message as configured for a streamed / buffered response. + /// + /// + /// The HTTP request message. + /// + /// + /// If true, the request message is configured for a streamed response; otherwise, it is configured for a buffered response. + /// + /// + /// The HTTP request message (enables inline use). + /// + public static HttpRequestMessage MarkAsStreamed(this HttpRequestMessage message, bool isStreamed = true) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); - message.Properties[MessageProperties.IsStreamed] = isStreamed; + message.Properties[MessageProperties.IsStreamed] = isStreamed; - return message; - } - } + return message; + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Core/MessageProperties.cs b/src/HTTPlease.Core/MessageProperties.cs index 4b210d9..e95f0bd 100644 --- a/src/HTTPlease.Core/MessageProperties.cs +++ b/src/HTTPlease.Core/MessageProperties.cs @@ -1,28 +1,28 @@ namespace HTTPlease { - /// - /// The names of well-known HttpRequestMessage / HttpResponseMessage properties. - /// + /// + /// The names of well-known HttpRequestMessage / HttpResponseMessage properties. + /// public static class MessageProperties - { - /// - /// The prefix for HTTPlease property names. - /// - static readonly string Prefix = "HTTPlease."; + { + /// + /// The prefix for HTTPlease property names. + /// + static readonly string Prefix = "HTTPlease."; - /// - /// The that created the message. - /// - public static readonly string Request = Prefix + "Request"; + /// + /// The that created the message. + /// + public static readonly string Request = Prefix + "Request"; - /// - /// The message's collection of content formatters. - /// - public static readonly string ContentFormatters = Prefix + "ContentFormatters"; + /// + /// The message's collection of content formatters. + /// + public static readonly string ContentFormatters = Prefix + "ContentFormatters"; - /// - /// Is the request configured for a streamed response? - /// - public static readonly string IsStreamed = Prefix + "IsStreamed"; - } + /// + /// Is the request configured for a streamed response? + /// + public static readonly string IsStreamed = Prefix + "IsStreamed"; + } } diff --git a/src/HTTPlease.Core/OtherHttpMethods.cs b/src/HTTPlease.Core/OtherHttpMethods.cs index 92f8275..a784d39 100644 --- a/src/HTTPlease.Core/OtherHttpMethods.cs +++ b/src/HTTPlease.Core/OtherHttpMethods.cs @@ -2,14 +2,14 @@ namespace HTTPlease { - /// - /// Additional standard HTTP methods. - /// - public static class OtherHttpMethods - { - /// - /// The HTTP PATCH method. - /// - public static readonly HttpMethod Patch = new HttpMethod("PATCH"); - } + /// + /// Additional standard HTTP methods. + /// + public static class OtherHttpMethods + { + /// + /// The HTTP PATCH method. + /// + public static readonly HttpMethod Patch = new HttpMethod("PATCH"); + } } diff --git a/src/HTTPlease.Core/RequestExtensions.Headers.cs b/src/HTTPlease.Core/RequestExtensions.Headers.cs index 96654ed..95a69ac 100644 --- a/src/HTTPlease.Core/RequestExtensions.Headers.cs +++ b/src/HTTPlease.Core/RequestExtensions.Headers.cs @@ -3,272 +3,272 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// / extension methods for HTTP headers. - /// - public static partial class RequestExtensions + /// + /// / extension methods for HTTP headers. + /// + public static partial class RequestExtensions { - /// - /// Create a copy of the request that adds a header to each request. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// The header value. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeader(this HttpRequest request, string headerName, string headerValue, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); - - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - return request.WithHeaderFromProvider(headerName, - ValueProvider.FromConstantValue(headerValue), - ensureQuoted - ); - } - - /// - /// Create a copy of the request that adds a header with its value obtained from the specified delegate. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// A delegate that returns the header value for each request. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeader(this HttpRequest request, string headerName, Func getValue, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'headerName'.", nameof(headerName)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeaderFromProvider(headerName, - ValueProvider.FromFunction(getValue).Convert().ValueToString(), - ensureQuoted - ); - } - - /// - /// Create a copy of the request, but with the specified media type added to the "Accept" header. - /// - /// - /// The HTTP request. - /// - /// - /// The media-type name. - /// - /// - /// An optional media-type quality. - /// - /// - /// The new . - /// - public static HttpRequest AcceptMediaType(this HttpRequest request, string mediaType, double? quality = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(mediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); - - MediaTypeWithQualityHeaderValue mediaTypeHeader = - quality.HasValue ? - new MediaTypeWithQualityHeaderValue(mediaType, quality.Value) - : - new MediaTypeWithQualityHeaderValue(mediaType); - - return request.WithRequestAction(requestMessage => - { - requestMessage.Headers.Accept.Add(mediaTypeHeader); - }); - } - - /// - /// Create a copy of the request, but with no media types in the "Accept" header. - /// - /// - /// The HTTP request. - /// - /// - /// The new . - /// - public static HttpRequest AcceptNoMediaTypes(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - return request.WithRequestAction(requestMessage => - { - requestMessage.Headers.Accept.Clear(); - }); - } - - /// - /// Create a copy of the request that adds an "If-Match" header to each request. - /// - /// - /// The HTTP request. - /// - /// - /// The header value. - /// - /// - /// The new . - /// - public static HttpRequest WithIfMatchHeader(this HttpRequest request, string headerValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - return request.WithHeader("If-Match", () => headerValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-Match" header with its value obtained from the specified delegate. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that returns the header value for each request. - /// - /// - /// The new . - /// - public static HttpRequest WithIfMatchHeader(this HttpRequest request, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeader("If-Match", getValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-None-Match" header to each request. - /// - /// - /// The HTTP request. - /// - /// - /// The header value. - /// - /// - /// The new . - /// - public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, string headerValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - return request.WithHeader("If-None-Match", () => headerValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-None-Match" header with its value obtained from the specified delegate. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that returns the header value for each request. - /// - /// - /// The new . - /// - public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeader("If-None-Match", getValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds a header to each request. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// The header value provider. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeaderFromProvider(this HttpRequest request, string headerName, IValueProvider valueProvider, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); - - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); - - return request.WithRequestAction((requestMessage, context) => - { - requestMessage.Headers.Remove(headerName); - - string headerValue = valueProvider.Get(context); - if (headerValue == null) - return; - - if (ensureQuoted) - headerValue = EnsureQuoted(headerValue); - - requestMessage.Headers.Add(headerName, headerValue); - }); - } - } + /// + /// Create a copy of the request that adds a header to each request. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// The header value. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeader(this HttpRequest request, string headerName, string headerValue, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); + + if (headerValue == null) + throw new ArgumentNullException(nameof(headerValue)); + + return request.WithHeaderFromProvider(headerName, + ValueProvider.FromConstantValue(headerValue), + ensureQuoted + ); + } + + /// + /// Create a copy of the request that adds a header with its value obtained from the specified delegate. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// A delegate that returns the header value for each request. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeader(this HttpRequest request, string headerName, Func getValue, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'headerName'.", nameof(headerName)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeaderFromProvider(headerName, + ValueProvider.FromFunction(getValue).Convert().ValueToString(), + ensureQuoted + ); + } + + /// + /// Create a copy of the request, but with the specified media type added to the "Accept" header. + /// + /// + /// The HTTP request. + /// + /// + /// The media-type name. + /// + /// + /// An optional media-type quality. + /// + /// + /// The new . + /// + public static HttpRequest AcceptMediaType(this HttpRequest request, string mediaType, double? quality = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(mediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); + + MediaTypeWithQualityHeaderValue mediaTypeHeader = + quality.HasValue ? + new MediaTypeWithQualityHeaderValue(mediaType, quality.Value) + : + new MediaTypeWithQualityHeaderValue(mediaType); + + return request.WithRequestAction(requestMessage => + { + requestMessage.Headers.Accept.Add(mediaTypeHeader); + }); + } + + /// + /// Create a copy of the request, but with no media types in the "Accept" header. + /// + /// + /// The HTTP request. + /// + /// + /// The new . + /// + public static HttpRequest AcceptNoMediaTypes(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + return request.WithRequestAction(requestMessage => + { + requestMessage.Headers.Accept.Clear(); + }); + } + + /// + /// Create a copy of the request that adds an "If-Match" header to each request. + /// + /// + /// The HTTP request. + /// + /// + /// The header value. + /// + /// + /// The new . + /// + public static HttpRequest WithIfMatchHeader(this HttpRequest request, string headerValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (headerValue == null) + throw new ArgumentNullException(nameof(headerValue)); + + return request.WithHeader("If-Match", () => headerValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-Match" header with its value obtained from the specified delegate. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that returns the header value for each request. + /// + /// + /// The new . + /// + public static HttpRequest WithIfMatchHeader(this HttpRequest request, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeader("If-Match", getValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-None-Match" header to each request. + /// + /// + /// The HTTP request. + /// + /// + /// The header value. + /// + /// + /// The new . + /// + public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, string headerValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (headerValue == null) + throw new ArgumentNullException(nameof(headerValue)); + + return request.WithHeader("If-None-Match", () => headerValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-None-Match" header with its value obtained from the specified delegate. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that returns the header value for each request. + /// + /// + /// The new . + /// + public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeader("If-None-Match", getValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds a header to each request. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// The header value provider. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeaderFromProvider(this HttpRequest request, string headerName, IValueProvider valueProvider, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); + + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + return request.WithRequestAction((requestMessage, context) => + { + requestMessage.Headers.Remove(headerName); + + string headerValue = valueProvider.Get(context); + if (headerValue == null) + return; + + if (ensureQuoted) + headerValue = EnsureQuoted(headerValue); + + requestMessage.Headers.Add(headerName, headerValue); + }); + } + } } diff --git a/src/HTTPlease.Core/RequestExtensions.Helpers.cs b/src/HTTPlease.Core/RequestExtensions.Helpers.cs index 81bd812..65ef721 100644 --- a/src/HTTPlease.Core/RequestExtensions.Helpers.cs +++ b/src/HTTPlease.Core/RequestExtensions.Helpers.cs @@ -5,106 +5,106 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// Helper methods for / extensions. - /// - public static partial class RequestExtensions + /// + /// Helper methods for / extensions. + /// + public static partial class RequestExtensions { - /// - /// Configure the request URI (and template status) in the request properties. - /// - /// - /// The request properties to modify. - /// - /// - /// The request URI. - /// - static void SetUri(this IDictionary requestProperties, Uri requestUri) - { - if (requestProperties == null) - throw new ArgumentNullException(nameof(requestProperties)); + /// + /// Configure the request URI (and template status) in the request properties. + /// + /// + /// The request properties to modify. + /// + /// + /// The request URI. + /// + static void SetUri(this IDictionary requestProperties, Uri requestUri) + { + if (requestProperties == null) + throw new ArgumentNullException(nameof(requestProperties)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - requestProperties[nameof(IHttpRequest.Uri)] = requestUri; - requestProperties[nameof(IHttpRequest.IsUriTemplate)] = UriTemplate.IsTemplate(requestUri); - } + requestProperties[nameof(IHttpRequest.Uri)] = requestUri; + requestProperties[nameof(IHttpRequest.IsUriTemplate)] = UriTemplate.IsTemplate(requestUri); + } - /// - /// Ensure that the specified string is surrounted by quotes. - /// - /// - /// The string to examine. - /// - /// - /// The string, with quotes prepended / appended as required. - /// - /// - /// Some HTTP headers (such as If-Match) require their values to be quoted. - /// - static string EnsureQuoted(string str) - { - if (str == null) - throw new ArgumentNullException(nameof(str)); + /// + /// Ensure that the specified string is surrounted by quotes. + /// + /// + /// The string to examine. + /// + /// + /// The string, with quotes prepended / appended as required. + /// + /// + /// Some HTTP headers (such as If-Match) require their values to be quoted. + /// + static string EnsureQuoted(string str) + { + if (str == null) + throw new ArgumentNullException(nameof(str)); - if (str.Length == 0) - return "\"\""; + if (str.Length == 0) + return "\"\""; - StringBuilder quotedStringBuilder = new StringBuilder(str); + StringBuilder quotedStringBuilder = new StringBuilder(str); - if (quotedStringBuilder[0] != '\"') - quotedStringBuilder.Insert(0, '\"'); + if (quotedStringBuilder[0] != '\"') + quotedStringBuilder.Insert(0, '\"'); - if (quotedStringBuilder[quotedStringBuilder.Length - 1] != '\"') - quotedStringBuilder.Append('\"'); + if (quotedStringBuilder[quotedStringBuilder.Length - 1] != '\"') + quotedStringBuilder.Append('\"'); - return quotedStringBuilder.ToString(); - } + return quotedStringBuilder.ToString(); + } - /// - /// Convert the specified object's properties to deferred parameters. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The type of object whose properties will form the parameters. - /// - /// - /// The object whose properties will form the parameters. - /// - /// - /// A sequence of key / value pairs representing the parameters. - /// - static IEnumerable>> CreateDeferredParameters(this TParameters parameters) - { - if (Equals(parameters, null)) - throw new ArgumentNullException(nameof(parameters)); - - // TODO: Refactor PropertyInfo retrieval logic (move it out to an extension method). + /// + /// Convert the specified object's properties to deferred parameters. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The type of object whose properties will form the parameters. + /// + /// + /// The object whose properties will form the parameters. + /// + /// + /// A sequence of key / value pairs representing the parameters. + /// + static IEnumerable>> CreateDeferredParameters(this TParameters parameters) + { + if (Equals(parameters, null)) + throw new ArgumentNullException(nameof(parameters)); + + // TODO: Refactor PropertyInfo retrieval logic (move it out to an extension method). - // Yes yes yes, reflection might be "slow", but it's still blazingly fast compared to making a request over the network. - foreach (PropertyInfo property in typeof(TParameters).GetTypeInfo().DeclaredProperties) - { - // Ignore write-only properties. - if (!property.CanRead) - continue; - - // Public instance properties only. - if (!property.GetMethod.IsPublic || property.GetMethod.IsStatic) - continue; + // Yes yes yes, reflection might be "slow", but it's still blazingly fast compared to making a request over the network. + foreach (PropertyInfo property in typeof(TParameters).GetTypeInfo().DeclaredProperties) + { + // Ignore write-only properties. + if (!property.CanRead) + continue; + + // Public instance properties only. + if (!property.GetMethod.IsPublic || property.GetMethod.IsStatic) + continue; - yield return new KeyValuePair>( - property.Name, - ValueProvider.FromSelector( - context => property.GetValue(parameters) - ) - .Convert().ValueToString() - ); - } - } - } + yield return new KeyValuePair>( + property.Name, + ValueProvider.FromSelector( + context => property.GetValue(parameters) + ) + .Convert().ValueToString() + ); + } + } + } } diff --git a/src/HTTPlease.Core/RequestExtensions.Messages.cs b/src/HTTPlease.Core/RequestExtensions.Messages.cs index 8f0e13d..decd123 100644 --- a/src/HTTPlease.Core/RequestExtensions.Messages.cs +++ b/src/HTTPlease.Core/RequestExtensions.Messages.cs @@ -1,10 +1,10 @@ namespace HTTPlease { - /// - /// / extension methods for building request messages. - /// - public static partial class RequestExtensions + /// + /// / extension methods for building request messages. + /// + public static partial class RequestExtensions { - // TODO: Implement abstraction for content formatters then add BuildRequestMessage extension method for HttpRequest that adds body content. - } + // TODO: Implement abstraction for content formatters then add BuildRequestMessage extension method for HttpRequest that adds body content. + } } diff --git a/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs b/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs index 04c5c01..a3f8aa5 100644 --- a/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs +++ b/src/HTTPlease.Core/RequestExtensions.QueryParameters.cs @@ -4,248 +4,248 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// / extension methods for query parameters. - /// - public static partial class RequestExtensions + /// + /// / extension methods for query parameters. + /// + public static partial class RequestExtensions { - /// - /// Create a copy of the request builder with the specified request URI query parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter data-type. - /// - /// - /// The parameter name. - /// - /// - /// The parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameter(this HttpRequest request, string name, TParameter value) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - return request.WithQueryParameterFromProvider(name, - ValueProvider.FromConstantValue(value?.ToString()) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI query parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter data-type. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that returns the parameter value (cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameter(this HttpRequest request, string name, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithQueryParameterFromProvider( - name, - ValueProvider.FromFunction(getValue) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI query parameter. - /// - /// - /// The parameter data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that, given the current context, returns the parameter value (cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); - - return request.Clone(properties => - { - IValueProvider stringValueProvider = valueProvider.Convert().ValueToString(); - - if (request.QueryParameters.TryGetValue(name, out IValueProvider existingStringValueProvider)) - stringValueProvider = stringValueProvider.Combine().ByAppendingTo(existingStringValueProvider); - - properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.SetItem( - key: name, - value: stringValueProvider - ); - }); - } - - /// - /// Create a copy of the request, but with query parameters from the specified object's properties. - /// - /// - /// The type of object whose properties will form the parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The object whose properties will form the parameters. - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameters(this HttpRequest request, TParameters parameters) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - return request.WithQueryParametersFromProviders( - CreateDeferredParameters(parameters) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI query parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A sequence of 0 or more key / value pairs representing the query parameters (values cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParametersFromProviders(this HttpRequest request, IEnumerable>> queryParameters) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (queryParameters == null) - throw new ArgumentNullException(nameof(queryParameters)); - - bool modified = false; - ImmutableDictionary>.Builder queryParametersBuilder = request.QueryParameters.ToBuilder(); - foreach (KeyValuePair> queryParameter in queryParameters) - { - if (queryParameter.Value == null) - { - throw new ArgumentException( - String.Format( - "Query parameter '{0}' has a null getter; this is not supported.", - queryParameter.Key - ), - nameof(queryParameters) - ); - } - - queryParametersBuilder[queryParameter.Key] = queryParameter.Value; - modified = true; - } - - if (!modified) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = queryParametersBuilder.ToImmutable(); - }); - } - - /// - /// Create a copy of the request builder without the specified request URI query parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// The new . - /// - public static HttpRequest WithoutQueryParameter(this HttpRequest request, string name) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (!request.QueryParameters.ContainsKey(name)) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.Remove(name); - }); - } - - /// - /// Create a copy of the request builder without the specified request URI query parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter names. - /// - /// - /// The new . - /// - public static HttpRequest WithoutQueryParameters(this HttpRequest request, IEnumerable names) - { - if (names == null) - throw new ArgumentNullException(nameof(names)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.RemoveRange(names); - }); - } - } + /// + /// Create a copy of the request builder with the specified request URI query parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter data-type. + /// + /// + /// The parameter name. + /// + /// + /// The parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameter(this HttpRequest request, string name, TParameter value) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + return request.WithQueryParameterFromProvider(name, + ValueProvider.FromConstantValue(value?.ToString()) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI query parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter data-type. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that returns the parameter value (cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameter(this HttpRequest request, string name, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithQueryParameterFromProvider( + name, + ValueProvider.FromFunction(getValue) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI query parameter. + /// + /// + /// The parameter data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that, given the current context, returns the parameter value (cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + return request.Clone(properties => + { + IValueProvider stringValueProvider = valueProvider.Convert().ValueToString(); + + if (request.QueryParameters.TryGetValue(name, out IValueProvider existingStringValueProvider)) + stringValueProvider = stringValueProvider.Combine().ByAppendingTo(existingStringValueProvider); + + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.SetItem( + key: name, + value: stringValueProvider + ); + }); + } + + /// + /// Create a copy of the request, but with query parameters from the specified object's properties. + /// + /// + /// The type of object whose properties will form the parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The object whose properties will form the parameters. + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameters(this HttpRequest request, TParameters parameters) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + return request.WithQueryParametersFromProviders( + CreateDeferredParameters(parameters) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI query parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A sequence of 0 or more key / value pairs representing the query parameters (values cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParametersFromProviders(this HttpRequest request, IEnumerable>> queryParameters) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (queryParameters == null) + throw new ArgumentNullException(nameof(queryParameters)); + + bool modified = false; + ImmutableDictionary>.Builder queryParametersBuilder = request.QueryParameters.ToBuilder(); + foreach (KeyValuePair> queryParameter in queryParameters) + { + if (queryParameter.Value == null) + { + throw new ArgumentException( + String.Format( + "Query parameter '{0}' has a null getter; this is not supported.", + queryParameter.Key + ), + nameof(queryParameters) + ); + } + + queryParametersBuilder[queryParameter.Key] = queryParameter.Value; + modified = true; + } + + if (!modified) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = queryParametersBuilder.ToImmutable(); + }); + } + + /// + /// Create a copy of the request builder without the specified request URI query parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// The new . + /// + public static HttpRequest WithoutQueryParameter(this HttpRequest request, string name) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (!request.QueryParameters.ContainsKey(name)) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.Remove(name); + }); + } + + /// + /// Create a copy of the request builder without the specified request URI query parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter names. + /// + /// + /// The new . + /// + public static HttpRequest WithoutQueryParameters(this HttpRequest request, IEnumerable names) + { + if (names == null) + throw new ArgumentNullException(nameof(names)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.RemoveRange(names); + }); + } + } } diff --git a/src/HTTPlease.Core/RequestExtensions.RequestActions.cs b/src/HTTPlease.Core/RequestExtensions.RequestActions.cs index 076fcab..e6536b2 100644 --- a/src/HTTPlease.Core/RequestExtensions.RequestActions.cs +++ b/src/HTTPlease.Core/RequestExtensions.RequestActions.cs @@ -3,130 +3,130 @@ namespace HTTPlease { - using Core; - - /// - /// / extension methods for request-configuration actions. - /// - public static partial class RequestExtensions + using Core; + + /// + /// / extension methods for request-configuration actions. + /// + public static partial class RequestExtensions { - /// - /// Create a copy of the request with the specified request-configuration action. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified request-configuration action. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (requestAction == null) - throw new ArgumentNullException(nameof(requestAction)); + if (requestAction == null) + throw new ArgumentNullException(nameof(requestAction)); - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add( - (message, context) => requestAction(message) - ); - }); - } - - /// - /// Create a copy of the request with the specified request-configuration action. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add( + (message, context) => requestAction(message) + ); + }); + } + + /// + /// Create a copy of the request with the specified request-configuration action. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (requestAction == null) - throw new ArgumentNullException(nameof(requestAction)); + if (requestAction == null) + throw new ArgumentNullException(nameof(requestAction)); - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add(requestAction); - }); - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add(requestAction); + }); + } - /// - /// Create a copy of the request with the specified request-configuration actions. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified request-configuration actions. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (requestActions == null) - throw new ArgumentNullException(nameof(requestActions)); + if (requestActions == null) + throw new ArgumentNullException(nameof(requestActions)); - if (requestActions.Length == 0) - return request; + if (requestActions.Length == 0) + return request; - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange( - requestActions.Select(requestAction => - { - RequestAction requestActionWithContext = (message, context) => requestAction(message); - - return requestActionWithContext; - }) - ); - }); - } - - /// - /// Create a copy of the request with the specified request-configuration actions. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange( + requestActions.Select(requestAction => + { + RequestAction requestActionWithContext = (message, context) => requestAction(message); + + return requestActionWithContext; + }) + ); + }); + } + + /// + /// Create a copy of the request with the specified request-configuration actions. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (requestActions == null) - throw new ArgumentNullException(nameof(requestActions)); + if (requestActions == null) + throw new ArgumentNullException(nameof(requestActions)); - if (requestActions.Length == 0) - return request; + if (requestActions.Length == 0) + return request; - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange(requestActions); - }); - } - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange(requestActions); + }); + } + } } diff --git a/src/HTTPlease.Core/RequestExtensions.RequestUri.cs b/src/HTTPlease.Core/RequestExtensions.RequestUri.cs index 6ef80e6..ef7ef04 100644 --- a/src/HTTPlease.Core/RequestExtensions.RequestUri.cs +++ b/src/HTTPlease.Core/RequestExtensions.RequestUri.cs @@ -2,165 +2,165 @@ namespace HTTPlease { - using Core.Utilities; + using Core.Utilities; - /// - /// / extension methods for request URIs. - /// - public static partial class RequestExtensions + /// + /// / extension methods for request URIs. + /// + public static partial class RequestExtensions { - /// - /// Ensure that the has an absolute URI. - /// - /// - /// The request's absolute URI. - /// - /// - /// The request has a relative URI. - /// - public static bool HasAbsoluteUri(this IHttpRequest httpRequest) - { - if (httpRequest == null) - throw new ArgumentNullException(nameof(httpRequest)); - - return httpRequest.Uri.IsAbsoluteUri; - } - - /// - /// Ensure that the has an absolute URI. - /// - /// - /// The request's absolute URI. - /// - /// - /// The request has a relative URI. - /// - public static Uri EnsureAbsoluteUri(this IHttpRequest httpRequest) - { - if (httpRequest == null) - throw new ArgumentNullException(nameof(httpRequest)); - - Uri requestUri = httpRequest.Uri; - if (requestUri.IsAbsoluteUri) - return requestUri; - - throw new InvalidOperationException("The HTTP request does not have an absolute URI."); - } - - /// - /// Create a copy of the request with the specified base URI. - /// - /// - /// The request. - /// - /// - /// The request base URI (must be absolute). - /// - /// - /// The new . - /// - /// - /// The request already has an absolute URI. - /// - public static HttpRequest WithBaseUri(this HttpRequest request, Uri baseUri) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (baseUri == null) - throw new ArgumentNullException(nameof(baseUri)); - - if (!baseUri.IsAbsoluteUri) - throw new ArgumentException("The supplied base URI is not an absolute URI.", nameof(baseUri)); - - if (request.Uri.IsAbsoluteUri) - throw new InvalidOperationException("The request already has an absolute URI."); - - return request.Clone(properties => - { - properties.SetUri( - baseUri.AppendRelativeUri(request.Uri) - ); - }); - } - - /// - /// Create a copy of the request with the specified request URI. - /// - /// - /// The request. - /// - /// - /// The new request URI. - /// - /// - /// The new . - /// - public static HttpRequest WithUri(this HttpRequest request, Uri requestUri) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); - - return request.Clone(properties => - { - properties.SetUri(requestUri); - }); - } - - /// - /// Create a copy of the request with the specified request URI appended to its existing URI. - /// - /// - /// The request. - /// - /// - /// The relative request URI. - /// - /// - /// The new . - /// - public static HttpRequest WithRelativeUri(this HttpRequest request, string relativeUri) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(relativeUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'relativeUri'.", nameof(relativeUri)); - - return request.WithRelativeUri( - new Uri(relativeUri, UriKind.Relative) - ); - } - - /// - /// Create a copy of the request with the specified request URI appended to its existing URI. - /// - /// - /// The request. - /// - /// - /// The relative request URI. - /// - /// - /// The new . - /// - public static HttpRequest WithRelativeUri(this HttpRequest request, Uri relativeUri) - { - if (relativeUri == null) - throw new ArgumentNullException(nameof(relativeUri)); - - if (relativeUri.IsAbsoluteUri) - throw new ArgumentException("The specified URI is not a relative URI.", nameof(relativeUri)); - - return request.Clone(properties => - { - properties.SetUri( - request.Uri.AppendRelativeUri(relativeUri) - ); - }); - } - } + /// + /// Ensure that the has an absolute URI. + /// + /// + /// The request's absolute URI. + /// + /// + /// The request has a relative URI. + /// + public static bool HasAbsoluteUri(this IHttpRequest httpRequest) + { + if (httpRequest == null) + throw new ArgumentNullException(nameof(httpRequest)); + + return httpRequest.Uri.IsAbsoluteUri; + } + + /// + /// Ensure that the has an absolute URI. + /// + /// + /// The request's absolute URI. + /// + /// + /// The request has a relative URI. + /// + public static Uri EnsureAbsoluteUri(this IHttpRequest httpRequest) + { + if (httpRequest == null) + throw new ArgumentNullException(nameof(httpRequest)); + + Uri requestUri = httpRequest.Uri; + if (requestUri.IsAbsoluteUri) + return requestUri; + + throw new InvalidOperationException("The HTTP request does not have an absolute URI."); + } + + /// + /// Create a copy of the request with the specified base URI. + /// + /// + /// The request. + /// + /// + /// The request base URI (must be absolute). + /// + /// + /// The new . + /// + /// + /// The request already has an absolute URI. + /// + public static HttpRequest WithBaseUri(this HttpRequest request, Uri baseUri) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (baseUri == null) + throw new ArgumentNullException(nameof(baseUri)); + + if (!baseUri.IsAbsoluteUri) + throw new ArgumentException("The supplied base URI is not an absolute URI.", nameof(baseUri)); + + if (request.Uri.IsAbsoluteUri) + throw new InvalidOperationException("The request already has an absolute URI."); + + return request.Clone(properties => + { + properties.SetUri( + baseUri.AppendRelativeUri(request.Uri) + ); + }); + } + + /// + /// Create a copy of the request with the specified request URI. + /// + /// + /// The request. + /// + /// + /// The new request URI. + /// + /// + /// The new . + /// + public static HttpRequest WithUri(this HttpRequest request, Uri requestUri) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); + + return request.Clone(properties => + { + properties.SetUri(requestUri); + }); + } + + /// + /// Create a copy of the request with the specified request URI appended to its existing URI. + /// + /// + /// The request. + /// + /// + /// The relative request URI. + /// + /// + /// The new . + /// + public static HttpRequest WithRelativeUri(this HttpRequest request, string relativeUri) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(relativeUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'relativeUri'.", nameof(relativeUri)); + + return request.WithRelativeUri( + new Uri(relativeUri, UriKind.Relative) + ); + } + + /// + /// Create a copy of the request with the specified request URI appended to its existing URI. + /// + /// + /// The request. + /// + /// + /// The relative request URI. + /// + /// + /// The new . + /// + public static HttpRequest WithRelativeUri(this HttpRequest request, Uri relativeUri) + { + if (relativeUri == null) + throw new ArgumentNullException(nameof(relativeUri)); + + if (relativeUri.IsAbsoluteUri) + throw new ArgumentException("The specified URI is not a relative URI.", nameof(relativeUri)); + + return request.Clone(properties => + { + properties.SetUri( + request.Uri.AppendRelativeUri(relativeUri) + ); + }); + } + } } diff --git a/src/HTTPlease.Core/RequestExtensions.ResponseActions.cs b/src/HTTPlease.Core/RequestExtensions.ResponseActions.cs index 667930e..dfce4b7 100644 --- a/src/HTTPlease.Core/RequestExtensions.ResponseActions.cs +++ b/src/HTTPlease.Core/RequestExtensions.ResponseActions.cs @@ -3,130 +3,130 @@ namespace HTTPlease { - using Core; - - /// - /// / extension methods for response-processing actions. - /// - public static partial class RequestExtensions + using Core; + + /// + /// / extension methods for response-processing actions. + /// + public static partial class RequestExtensions { - /// - /// Create a copy of the request with the specified response-processing action. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified response-processing action. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseAction == null) - throw new ArgumentNullException(nameof(responseAction)); + if (responseAction == null) + throw new ArgumentNullException(nameof(responseAction)); - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add( - (message, context) => responseAction(message) - ); - }); - } - - /// - /// Create a copy of the request with the specified response-processing action. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add( + (message, context) => responseAction(message) + ); + }); + } + + /// + /// Create a copy of the request with the specified response-processing action. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseAction == null) - throw new ArgumentNullException(nameof(responseAction)); + if (responseAction == null) + throw new ArgumentNullException(nameof(responseAction)); - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add(responseAction); - }); - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add(responseAction); + }); + } - /// - /// Create a copy of the request with the specified response-processing actions. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified response-processing actions. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseActions == null) - throw new ArgumentNullException(nameof(responseActions)); + if (responseActions == null) + throw new ArgumentNullException(nameof(responseActions)); - if (responseActions.Length == 0) - return request; + if (responseActions.Length == 0) + return request; - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange( - responseActions.Select(responseAction => - { - ResponseAction responseActionWithContext = (message, context) => responseAction(message); - - return responseActionWithContext; - }) - ); - }); - } - - /// - /// Create a copy of the request with the specified response-processing actions. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange( + responseActions.Select(responseAction => + { + ResponseAction responseActionWithContext = (message, context) => responseAction(message); + + return responseActionWithContext; + }) + ); + }); + } + + /// + /// Create a copy of the request with the specified response-processing actions. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseActions == null) - throw new ArgumentNullException(nameof(responseActions)); + if (responseActions == null) + throw new ArgumentNullException(nameof(responseActions)); - if (responseActions.Length == 0) - return request; + if (responseActions.Length == 0) + return request; - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange(responseActions); - }); - } - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange(responseActions); + }); + } + } } diff --git a/src/HTTPlease.Core/RequestExtensions.TemplateParameters.cs b/src/HTTPlease.Core/RequestExtensions.TemplateParameters.cs index 2190ce0..3d7c624 100644 --- a/src/HTTPlease.Core/RequestExtensions.TemplateParameters.cs +++ b/src/HTTPlease.Core/RequestExtensions.TemplateParameters.cs @@ -4,245 +4,245 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// / extension methods for template parameters. - /// - public static partial class RequestExtensions + /// + /// / extension methods for template parameters. + /// + public static partial class RequestExtensions { - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter data-type. - /// - /// - /// The parameter name. - /// - /// - /// The parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, TParameter value) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - return request.WithTemplateParameterFromProvider(name, - ValueProvider.FromConstantValue(value?.ToString()) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter data-type. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that returns the parameter value (cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithTemplateParameterFromProvider(name, - ValueProvider.FromFunction(getValue).Convert().ValueToString() - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter data-type. - /// - /// - /// The parameter name. - /// - /// - /// A value provider that, given the current context, returns the parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.SetItem( - key: name, - value: valueProvider.Convert().ValueToString() - ); - }); - } - - /// - /// Create a copy of the request, but with template parameters from the specified object's properties. - /// - /// - /// The type of object whose properties will form the parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The object whose properties will form the parameters. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameters(this HttpRequest request, TParameters parameters) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - return request.WithTemplateParametersFromProviders( - CreateDeferredParameters(parameters) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A sequence of 0 or more key / value pairs representing the template parameters (values cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParametersFromProviders(this HttpRequest request, IEnumerable>> templateParameters) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (templateParameters == null) - throw new ArgumentNullException(nameof(templateParameters)); - - bool modified = false; - ImmutableDictionary>.Builder templateParametersBuilder = request.TemplateParameters.ToBuilder(); - foreach (KeyValuePair> templateParameter in templateParameters) - { - if (templateParameter.Value == null) - { - throw new ArgumentException( - String.Format( - "Template parameter '{0}' has a null getter; this is not supported.", - templateParameter.Key - ), - nameof(templateParameters) - ); - } - - templateParametersBuilder[templateParameter.Key] = templateParameter.Value; - modified = true; - } - - if (!modified) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = templateParametersBuilder.ToImmutable(); - }); - } - - /// - /// Create a copy of the request builder without the specified request URI template parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// The new . - /// - public static HttpRequest WithoutTemplateParameter(this HttpRequest request, string name) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (!request.TemplateParameters.ContainsKey(name)) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.Remove(name); - }); - } - - /// - /// Create a copy of the request builder without the specified request URI template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter names. - /// - /// - /// The new . - /// - public static HttpRequest WithoutTemplateParameters(this HttpRequest request, IEnumerable names) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (names == null) - throw new ArgumentNullException(nameof(names)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.RemoveRange(names); - }); - } - } + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter data-type. + /// + /// + /// The parameter name. + /// + /// + /// The parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, TParameter value) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + return request.WithTemplateParameterFromProvider(name, + ValueProvider.FromConstantValue(value?.ToString()) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter data-type. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that returns the parameter value (cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithTemplateParameterFromProvider(name, + ValueProvider.FromFunction(getValue).Convert().ValueToString() + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter data-type. + /// + /// + /// The parameter name. + /// + /// + /// A value provider that, given the current context, returns the parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.SetItem( + key: name, + value: valueProvider.Convert().ValueToString() + ); + }); + } + + /// + /// Create a copy of the request, but with template parameters from the specified object's properties. + /// + /// + /// The type of object whose properties will form the parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The object whose properties will form the parameters. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameters(this HttpRequest request, TParameters parameters) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + return request.WithTemplateParametersFromProviders( + CreateDeferredParameters(parameters) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A sequence of 0 or more key / value pairs representing the template parameters (values cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParametersFromProviders(this HttpRequest request, IEnumerable>> templateParameters) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (templateParameters == null) + throw new ArgumentNullException(nameof(templateParameters)); + + bool modified = false; + ImmutableDictionary>.Builder templateParametersBuilder = request.TemplateParameters.ToBuilder(); + foreach (KeyValuePair> templateParameter in templateParameters) + { + if (templateParameter.Value == null) + { + throw new ArgumentException( + String.Format( + "Template parameter '{0}' has a null getter; this is not supported.", + templateParameter.Key + ), + nameof(templateParameters) + ); + } + + templateParametersBuilder[templateParameter.Key] = templateParameter.Value; + modified = true; + } + + if (!modified) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = templateParametersBuilder.ToImmutable(); + }); + } + + /// + /// Create a copy of the request builder without the specified request URI template parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// The new . + /// + public static HttpRequest WithoutTemplateParameter(this HttpRequest request, string name) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (!request.TemplateParameters.ContainsKey(name)) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.Remove(name); + }); + } + + /// + /// Create a copy of the request builder without the specified request URI template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter names. + /// + /// + /// The new . + /// + public static HttpRequest WithoutTemplateParameters(this HttpRequest request, IEnumerable names) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (names == null) + throw new ArgumentNullException(nameof(names)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.RemoveRange(names); + }); + } + } } diff --git a/src/HTTPlease.Core/RequestHeaderExtensions.cs b/src/HTTPlease.Core/RequestHeaderExtensions.cs index 4c401b0..9fbf0db 100644 --- a/src/HTTPlease.Core/RequestHeaderExtensions.cs +++ b/src/HTTPlease.Core/RequestHeaderExtensions.cs @@ -5,40 +5,40 @@ namespace HTTPlease { - /// - /// Extension method for - /// - public static class RequestHeaderExtensions - { - /// - /// Retrieve the value of an optional HTTP request header. - /// - /// - /// The HTTP request headers to examine. - /// - /// - /// The name of the target header. - /// - /// - /// The header value, or null if the header is not present (or an string if the header is present but has no value). - /// - public static string GetOptionalHeaderValue(this HttpRequestHeaders requestHeaders, string headerName) - { - if (requestHeaders == null) - throw new ArgumentNullException(nameof(requestHeaders)); + /// + /// Extension method for + /// + public static class RequestHeaderExtensions + { + /// + /// Retrieve the value of an optional HTTP request header. + /// + /// + /// The HTTP request headers to examine. + /// + /// + /// The name of the target header. + /// + /// + /// The header value, or null if the header is not present (or an string if the header is present but has no value). + /// + public static string GetOptionalHeaderValue(this HttpRequestHeaders requestHeaders, string headerName) + { + if (requestHeaders == null) + throw new ArgumentNullException(nameof(requestHeaders)); - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'headerName'.", nameof(headerName)); + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'headerName'.", nameof(headerName)); - IEnumerable headerValues; - if (!requestHeaders.TryGetValues(headerName, out headerValues)) - return null; + IEnumerable headerValues; + if (!requestHeaders.TryGetValues(headerName, out headerValues)) + return null; - return - headerValues.DefaultIfEmpty( - String.Empty - ) - .FirstOrDefault(); - } - } + return + headerValues.DefaultIfEmpty( + String.Empty + ) + .FirstOrDefault(); + } + } } diff --git a/src/HTTPlease.Core/TypedClientExtensions.cs b/src/HTTPlease.Core/TypedClientExtensions.cs index a93a36a..c1d7cfc 100644 --- a/src/HTTPlease.Core/TypedClientExtensions.cs +++ b/src/HTTPlease.Core/TypedClientExtensions.cs @@ -5,288 +5,288 @@ namespace HTTPlease { - using System.Collections.Generic; - using Core; + using System.Collections.Generic; + using Core; - /// - /// Invocation-related extension methods for s that use an . - /// - public static class TypedClientExtensions + /// + /// Invocation-related extension methods for s that use an . + /// + public static class TypedClientExtensions { - #region Invoke + #region Invoke - /// - /// Asynchronously execute a request as an HTTP HEAD. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task HeadAsync(this HttpClient httpClient, HttpRequest request, TContext context, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP HEAD. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task HeadAsync(this HttpClient httpClient, HttpRequest request, TContext context, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Head, context, cancellationToken: cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Head, context, cancellationToken: cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP GET. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task GetAsync(this HttpClient httpClient, HttpRequest request, TContext context, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP GET. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task GetAsync(this HttpClient httpClient, HttpRequest request, TContext context, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Get, context, cancellationToken: cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Get, context, cancellationToken: cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP POST. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// Optional representing the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PostAsync(this HttpClient httpClient, HttpRequest request, TContext context, HttpContent postBody = null, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP POST. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// Optional representing the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PostAsync(this HttpClient httpClient, HttpRequest request, TContext context, HttpContent postBody = null, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Post, context, postBody, cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Post, context, postBody, cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP PUT. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// representing the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PutAsync(this HttpClient httpClient, HttpRequest request, TContext context, HttpContent putBody, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); + /// + /// Asynchronously execute a request as an HTTP PUT. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// representing the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PutAsync(this HttpClient httpClient, HttpRequest request, TContext context, HttpContent putBody, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); - if (request == null) - throw new ArgumentNullException(nameof(request)); + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (putBody == null) - throw new ArgumentNullException(nameof(putBody)); + if (putBody == null) + throw new ArgumentNullException(nameof(putBody)); - return await httpClient.SendAsync(request, HttpMethod.Put, context, putBody, cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Put, context, putBody, cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP PATCH. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// representing the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PatchAsync(this HttpClient httpClient, HttpRequest request, TContext context, HttpContent patchBody, CancellationToken cancellationToken = default) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously execute a request as an HTTP PATCH. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// representing the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PatchAsync(this HttpClient httpClient, HttpRequest request, TContext context, HttpContent patchBody, CancellationToken cancellationToken = default) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (patchBody == null) - throw new ArgumentNullException(nameof(patchBody)); + if (patchBody == null) + throw new ArgumentNullException(nameof(patchBody)); - return await httpClient.SendAsync(request, OtherHttpMethods.Patch, context, patchBody, cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, OtherHttpMethods.Patch, context, patchBody, cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute a request as an HTTP DELETE. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task DeleteAsync(this HttpClient httpClient, HttpRequest request, TContext context, CancellationToken cancellationToken = default) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously execute a request as an HTTP DELETE. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task DeleteAsync(this HttpClient httpClient, HttpRequest request, TContext context, CancellationToken cancellationToken = default) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await httpClient.SendAsync(request, HttpMethod.Delete, context, cancellationToken: cancellationToken).ConfigureAwait(false); - } + return await httpClient.SendAsync(request, HttpMethod.Delete, context, cancellationToken: cancellationToken).ConfigureAwait(false); + } - /// - /// Asynchronously execute the request using the specified HTTP method. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An representing the method to use. - /// - /// - /// The instance used as a context for resolving deferred parameters. - /// - /// - /// Optional representing the request body (if any). - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task SendAsync(this HttpClient httpClient, HttpRequest request, HttpMethod method, TContext context, HttpContent body = null, CancellationToken cancellationToken = default) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously execute the request using the specified HTTP method. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An representing the method to use. + /// + /// + /// The instance used as a context for resolving deferred parameters. + /// + /// + /// Optional representing the request body (if any). + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task SendAsync(this HttpClient httpClient, HttpRequest request, HttpMethod method, TContext context, HttpContent body = null, CancellationToken cancellationToken = default) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, context, body, httpClient.BaseAddress)) - { - HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); - try - { - request.ExecuteResponseActions(responseMessage, context); - } - catch - { - using (responseMessage) - { - throw; - } - } + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, context, body, httpClient.BaseAddress)) + { + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + try + { + request.ExecuteResponseActions(responseMessage, context); + } + catch + { + using (responseMessage) + { + throw; + } + } - return responseMessage; - } - } + return responseMessage; + } + } - #endregion // Invoke + #endregion // Invoke - #region Helpers + #region Helpers - /// - /// Execute the request's configured response actions (if any) against the specified response message. - /// - /// - /// The . - /// - /// - /// The HTTP response message. - /// + /// + /// Execute the request's configured response actions (if any) against the specified response message. + /// + /// + /// The . + /// + /// + /// The HTTP response message. + /// /// - /// The used as a context for resolving deferred values. + /// The used as a context for resolving deferred values. /// - static void ExecuteResponseActions(this HttpRequest request, HttpResponseMessage responseMessage, TContext context) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + static void ExecuteResponseActions(this HttpRequest request, HttpResponseMessage responseMessage, TContext context) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); - List responseActionExceptions = new List(); - foreach (ResponseAction responseAction in request.ResponseActions) - { - try - { - responseAction(responseMessage, context); - } - catch (Exception eResponseAction) - { - responseActionExceptions.Add(eResponseAction); - } - } + List responseActionExceptions = new List(); + foreach (ResponseAction responseAction in request.ResponseActions) + { + try + { + responseAction(responseMessage, context); + } + catch (Exception eResponseAction) + { + responseActionExceptions.Add(eResponseAction); + } + } - if (responseActionExceptions.Count > 0) - throw new AggregateException("One or more errors occurred while processing the response message.", responseActionExceptions); - } + if (responseActionExceptions.Count > 0) + throw new AggregateException("One or more errors occurred while processing the response message.", responseActionExceptions); + } - #endregion // Helpers - } + #endregion // Helpers + } } diff --git a/src/HTTPlease.Core/TypedFactoryExtensions.cs b/src/HTTPlease.Core/TypedFactoryExtensions.cs index eeba330..c829636 100644 --- a/src/HTTPlease.Core/TypedFactoryExtensions.cs +++ b/src/HTTPlease.Core/TypedFactoryExtensions.cs @@ -2,37 +2,37 @@ namespace HTTPlease { - /// - /// Extension methods for . - /// - public static class TypedFactoryExtensions + /// + /// Extension methods for . + /// + public static class TypedFactoryExtensions { - /// - /// Create a new HTTP request with the specified request URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Create(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request with the specified request URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Create(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return requestFactory.Create( - new Uri(requestUri, UriKind.RelativeOrAbsolute) - ); - } - } + return requestFactory.Create( + new Uri(requestUri, UriKind.RelativeOrAbsolute) + ); + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.Headers.cs b/src/HTTPlease.Core/TypedRequestExtensions.Headers.cs index 0d6d01c..f5493d2 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.Headers.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.Headers.cs @@ -3,398 +3,398 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// / extension methods for HTTP headers. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for HTTP headers. + /// + public static partial class TypedRequestExtensions { - /// - /// Create a copy of the request that adds a header to each request. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The header value data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// The header value. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeader(this HttpRequest request, string headerName, TValue headerValue, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); - - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - return request.WithHeaderFromProvider(headerName, - ValueProvider.FromConstantValue(headerValue).Convert().ValueToString(), - ensureQuoted - ); - } - - /// - /// Create a copy of the request that adds a header with its value obtained from the specified delegate. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The type of header value to add. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// A delegate that returns the header value for each request. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeader(this HttpRequest request, string headerName, Func getValue, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeaderFromProvider(headerName, - ValueProvider.FromFunction(getValue).Convert().ValueToString(), - ensureQuoted - ); - } - - /// - /// Create a copy of the request that adds a header with its value obtained from the specified delegate. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The type of header value to add. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// A delegate that extracts the header value from the context for each request. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeader(this HttpRequest request, string headerName, Func getValue, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeaderFromProvider(headerName, - ValueProvider.FromSelector(getValue).Convert().ValueToString(), - ensureQuoted - ); - } - - /// - /// Create a copy of the request, but with the specified media type added to the "Accept" header. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The media-type name. - /// - /// - /// An optional media-type quality. - /// - /// - /// The new . - /// - public static HttpRequest AcceptMediaType(this HttpRequest request, string mediaType, double? quality = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(mediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); - - MediaTypeWithQualityHeaderValue mediaTypeHeader = - quality.HasValue ? - new MediaTypeWithQualityHeaderValue(mediaType, quality.Value) - : - new MediaTypeWithQualityHeaderValue(mediaType); - - return request.WithRequestAction(requestMessage => - { - requestMessage.Headers.Accept.Add(mediaTypeHeader); - }); - } - - /// - /// Create a copy of the request, but with no media types in the "Accept" header. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The new . - /// - public static HttpRequest AcceptNoMediaTypes(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - return request.WithRequestAction(requestMessage => - { - requestMessage.Headers.Accept.Clear(); - }); - } - - /// - /// Create a copy of the request that adds an "If-Match" header to each request. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The header value. - /// - /// - /// The new . - /// - public static HttpRequest WithIfMatchHeader(this HttpRequest request, string headerValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - return request.WithHeader("If-Match", () => headerValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-Match" header with its value obtained from the specified delegate. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that extracts the header value from the context for each request. - /// - /// - /// The new . - /// - public static HttpRequest WithIfMatchHeader(this HttpRequest request, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeader("If-Match", getValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-Match" header with its value obtained from the specified delegate. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that returns the header value for each request. - /// - /// - /// The new . - /// - public static HttpRequest WithIfMatchHeader(this HttpRequest request, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeader("If-Match", getValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-None-Match" header to each request. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The header value. - /// - /// - /// The new . - /// - public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, string headerValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - return request.WithHeader("If-None-Match", () => headerValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-None-Match" header with its value obtained from the specified delegate. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that extracts the header value from the context for each request. - /// - /// - /// The new . - /// - public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeader("If-None-Match", getValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds an "If-None-Match" header with its value obtained from the specified delegate. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that returns the header value for each request. - /// - /// - /// The new . - /// - public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithHeader("If-None-Match", getValue, ensureQuoted: true); - } - - /// - /// Create a copy of the request that adds a header to each request. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The header name. - /// - /// - /// The header value provider. - /// - /// - /// Ensure that the header value is quoted? - /// - /// - /// The new . - /// - public static HttpRequest WithHeaderFromProvider(this HttpRequest request, string headerName, IValueProvider valueProvider, bool ensureQuoted = false) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(headerName)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); - - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); - - return request.WithRequestAction((requestMessage, context) => - { - requestMessage.Headers.Remove(headerName); - - string headerValue = valueProvider.Get(context); - if (headerValue == null) - return; - - if (ensureQuoted) - headerValue = EnsureQuoted(headerValue); - - requestMessage.Headers.Add(headerName, headerValue); - }); - } - } + /// + /// Create a copy of the request that adds a header to each request. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The header value data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// The header value. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeader(this HttpRequest request, string headerName, TValue headerValue, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); + + if (headerValue == null) + throw new ArgumentNullException(nameof(headerValue)); + + return request.WithHeaderFromProvider(headerName, + ValueProvider.FromConstantValue(headerValue).Convert().ValueToString(), + ensureQuoted + ); + } + + /// + /// Create a copy of the request that adds a header with its value obtained from the specified delegate. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The type of header value to add. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// A delegate that returns the header value for each request. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeader(this HttpRequest request, string headerName, Func getValue, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeaderFromProvider(headerName, + ValueProvider.FromFunction(getValue).Convert().ValueToString(), + ensureQuoted + ); + } + + /// + /// Create a copy of the request that adds a header with its value obtained from the specified delegate. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The type of header value to add. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// A delegate that extracts the header value from the context for each request. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeader(this HttpRequest request, string headerName, Func getValue, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeaderFromProvider(headerName, + ValueProvider.FromSelector(getValue).Convert().ValueToString(), + ensureQuoted + ); + } + + /// + /// Create a copy of the request, but with the specified media type added to the "Accept" header. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The media-type name. + /// + /// + /// An optional media-type quality. + /// + /// + /// The new . + /// + public static HttpRequest AcceptMediaType(this HttpRequest request, string mediaType, double? quality = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(mediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); + + MediaTypeWithQualityHeaderValue mediaTypeHeader = + quality.HasValue ? + new MediaTypeWithQualityHeaderValue(mediaType, quality.Value) + : + new MediaTypeWithQualityHeaderValue(mediaType); + + return request.WithRequestAction(requestMessage => + { + requestMessage.Headers.Accept.Add(mediaTypeHeader); + }); + } + + /// + /// Create a copy of the request, but with no media types in the "Accept" header. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The new . + /// + public static HttpRequest AcceptNoMediaTypes(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + return request.WithRequestAction(requestMessage => + { + requestMessage.Headers.Accept.Clear(); + }); + } + + /// + /// Create a copy of the request that adds an "If-Match" header to each request. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The header value. + /// + /// + /// The new . + /// + public static HttpRequest WithIfMatchHeader(this HttpRequest request, string headerValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (headerValue == null) + throw new ArgumentNullException(nameof(headerValue)); + + return request.WithHeader("If-Match", () => headerValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-Match" header with its value obtained from the specified delegate. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that extracts the header value from the context for each request. + /// + /// + /// The new . + /// + public static HttpRequest WithIfMatchHeader(this HttpRequest request, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeader("If-Match", getValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-Match" header with its value obtained from the specified delegate. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that returns the header value for each request. + /// + /// + /// The new . + /// + public static HttpRequest WithIfMatchHeader(this HttpRequest request, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeader("If-Match", getValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-None-Match" header to each request. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The header value. + /// + /// + /// The new . + /// + public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, string headerValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (headerValue == null) + throw new ArgumentNullException(nameof(headerValue)); + + return request.WithHeader("If-None-Match", () => headerValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-None-Match" header with its value obtained from the specified delegate. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that extracts the header value from the context for each request. + /// + /// + /// The new . + /// + public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeader("If-None-Match", getValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds an "If-None-Match" header with its value obtained from the specified delegate. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that returns the header value for each request. + /// + /// + /// The new . + /// + public static HttpRequest WithIfNoneMatchHeader(this HttpRequest request, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithHeader("If-None-Match", getValue, ensureQuoted: true); + } + + /// + /// Create a copy of the request that adds a header to each request. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The header name. + /// + /// + /// The header value provider. + /// + /// + /// Ensure that the header value is quoted? + /// + /// + /// The new . + /// + public static HttpRequest WithHeaderFromProvider(this HttpRequest request, string headerName, IValueProvider valueProvider, bool ensureQuoted = false) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(headerName)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(headerName)); + + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + return request.WithRequestAction((requestMessage, context) => + { + requestMessage.Headers.Remove(headerName); + + string headerValue = valueProvider.Get(context); + if (headerValue == null) + return; + + if (ensureQuoted) + headerValue = EnsureQuoted(headerValue); + + requestMessage.Headers.Add(headerName, headerValue); + }); + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.Helpers.cs b/src/HTTPlease.Core/TypedRequestExtensions.Helpers.cs index 2896919..4dd8a5a 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.Helpers.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.Helpers.cs @@ -5,106 +5,106 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// Helper methods for / extensions. - /// - public static partial class TypedRequestExtensions + /// + /// Helper methods for / extensions. + /// + public static partial class TypedRequestExtensions { - /// - /// Configure the request URI (and template status) in the request properties. - /// - /// - /// The request properties to modify. - /// - /// - /// The request URI. - /// - static void SetUri(this IDictionary requestProperties, Uri requestUri) - { - if (requestProperties == null) - throw new ArgumentNullException(nameof(requestProperties)); + /// + /// Configure the request URI (and template status) in the request properties. + /// + /// + /// The request properties to modify. + /// + /// + /// The request URI. + /// + static void SetUri(this IDictionary requestProperties, Uri requestUri) + { + if (requestProperties == null) + throw new ArgumentNullException(nameof(requestProperties)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - requestProperties[nameof(IHttpRequest.Uri)] = requestUri; - requestProperties[nameof(IHttpRequest.IsUriTemplate)] = UriTemplate.IsTemplate(requestUri); - } + requestProperties[nameof(IHttpRequest.Uri)] = requestUri; + requestProperties[nameof(IHttpRequest.IsUriTemplate)] = UriTemplate.IsTemplate(requestUri); + } - /// - /// Ensure that the specified string is surrounted by quotes. - /// - /// - /// The string to examine. - /// - /// - /// The string, with quotes prepended / appended as required. - /// - /// - /// Some HTTP headers (such as If-Match) require their values to be quoted. - /// - static string EnsureQuoted(string str) - { - if (str == null) - throw new ArgumentNullException(nameof(str)); + /// + /// Ensure that the specified string is surrounted by quotes. + /// + /// + /// The string to examine. + /// + /// + /// The string, with quotes prepended / appended as required. + /// + /// + /// Some HTTP headers (such as If-Match) require their values to be quoted. + /// + static string EnsureQuoted(string str) + { + if (str == null) + throw new ArgumentNullException(nameof(str)); - if (str.Length == 0) - return "\"\""; + if (str.Length == 0) + return "\"\""; - StringBuilder quotedStringBuilder = new StringBuilder(str); + StringBuilder quotedStringBuilder = new StringBuilder(str); - if (quotedStringBuilder[0] != '\"') - quotedStringBuilder.Insert(0, '\"'); + if (quotedStringBuilder[0] != '\"') + quotedStringBuilder.Insert(0, '\"'); - if (quotedStringBuilder[quotedStringBuilder.Length - 1] != '\"') - quotedStringBuilder.Append('\"'); + if (quotedStringBuilder[quotedStringBuilder.Length - 1] != '\"') + quotedStringBuilder.Append('\"'); - return quotedStringBuilder.ToString(); - } + return quotedStringBuilder.ToString(); + } - /// - /// Convert the specified object's properties to deferred parameters. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The type of object whose properties will form the parameters. - /// - /// - /// The object whose properties will form the parameters. - /// - /// - /// A sequence of key / value pairs representing the parameters. - /// - static IEnumerable>> CreateDeferredParameters(TParameters parameters) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); + /// + /// Convert the specified object's properties to deferred parameters. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The type of object whose properties will form the parameters. + /// + /// + /// The object whose properties will form the parameters. + /// + /// + /// A sequence of key / value pairs representing the parameters. + /// + static IEnumerable>> CreateDeferredParameters(TParameters parameters) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); - // TODO: Refactor PropertyInfo retrieval logic (move it out to an extension method). + // TODO: Refactor PropertyInfo retrieval logic (move it out to an extension method). - // Yes yes yes, reflection might be "slow", but it's still blazingly fast compared to making a request over the network. - foreach (PropertyInfo property in typeof(TParameters).GetTypeInfo().DeclaredProperties) - { - // Ignore write-only properties. - if (!property.CanRead) - continue; - - // Public instance properties only. - if (!property.GetMethod.IsPublic || property.GetMethod.IsStatic) - continue; + // Yes yes yes, reflection might be "slow", but it's still blazingly fast compared to making a request over the network. + foreach (PropertyInfo property in typeof(TParameters).GetTypeInfo().DeclaredProperties) + { + // Ignore write-only properties. + if (!property.CanRead) + continue; + + // Public instance properties only. + if (!property.GetMethod.IsPublic || property.GetMethod.IsStatic) + continue; - yield return new KeyValuePair>( - property.Name, - ValueProvider.FromSelector( - context => property.GetValue(parameters) - ) - .Convert().ValueToString() - ); - } - } - } + yield return new KeyValuePair>( + property.Name, + ValueProvider.FromSelector( + context => property.GetValue(parameters) + ) + .Convert().ValueToString() + ); + } + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.Messages.cs b/src/HTTPlease.Core/TypedRequestExtensions.Messages.cs index 2938876..fc84afd 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.Messages.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.Messages.cs @@ -1,10 +1,10 @@ namespace HTTPlease { - /// - /// / extension methods for building request messages. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for building request messages. + /// + public static partial class TypedRequestExtensions { - // TODO: Implement abstraction for content formatters then add BuildRequestMessage extension method for HttpRequest that adds body content. - } + // TODO: Implement abstraction for content formatters then add BuildRequestMessage extension method for HttpRequest that adds body content. + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.QueryParameters.cs b/src/HTTPlease.Core/TypedRequestExtensions.QueryParameters.cs index 99be0ec..af50957 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.QueryParameters.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.QueryParameters.cs @@ -4,255 +4,255 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// / extension methods for query parameters. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for query parameters. + /// + public static partial class TypedRequestExtensions { - /// - /// Create a copy of the request builder with the specified request URI query parameter. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The parameter data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// The parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameter(this HttpRequest request, string name, TValue value) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request builder with the specified request URI query parameter. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The parameter data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// The parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameter(this HttpRequest request, string name, TValue value) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - return request.WithQueryParameterFromProvider(name, - ValueProvider.FromConstantValue(value?.ToString()) - ); - } + return request.WithQueryParameterFromProvider(name, + ValueProvider.FromConstantValue(value?.ToString()) + ); + } - /// - /// Create a copy of the request builder with the specified request URI query parameter. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that returns the parameter value (cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameter(this HttpRequest request, string name, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request builder with the specified request URI query parameter. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that returns the parameter value (cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameter(this HttpRequest request, string name, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); - return request.WithQueryParameterFromProvider( - name, - ValueProvider.FromFunction(getValue) - ); - } + return request.WithQueryParameterFromProvider( + name, + ValueProvider.FromFunction(getValue) + ); + } - /// - /// Create a copy of the request builder with the specified request URI query parameter. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that, given the current context, returns the parameter value (cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request builder with the specified request URI query parameter. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that, given the current context, returns the parameter value (cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.SetItem( - key: name, - value: valueProvider.Convert().ValueToString() - ); - }); - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.SetItem( + key: name, + value: valueProvider.Convert().ValueToString() + ); + }); + } - /// - /// Create a copy of the request, but with query parameters from the specified object's properties. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The type of object whose properties will form the parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The object whose properties will form the parameters. - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParametersFrom(HttpRequest request, TParameters parameters) - { - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); + /// + /// Create a copy of the request, but with query parameters from the specified object's properties. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The type of object whose properties will form the parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The object whose properties will form the parameters. + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParametersFrom(HttpRequest request, TParameters parameters) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); - return request.WithQueryParametersFromProviders( - CreateDeferredParameters(parameters) - ); - } + return request.WithQueryParametersFromProviders( + CreateDeferredParameters(parameters) + ); + } - /// - /// Create a copy of the request builder with the specified request URI query parameters. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A sequence of 0 or more key / value pairs representing the query parameters (values cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithQueryParametersFromProviders(this HttpRequest request, IEnumerable>> queryParameters) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request builder with the specified request URI query parameters. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A sequence of 0 or more key / value pairs representing the query parameters (values cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithQueryParametersFromProviders(this HttpRequest request, IEnumerable>> queryParameters) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (queryParameters == null) - throw new ArgumentNullException(nameof(queryParameters)); + if (queryParameters == null) + throw new ArgumentNullException(nameof(queryParameters)); - bool modified = false; - ImmutableDictionary>.Builder queryParametersBuilder = request.QueryParameters.ToBuilder(); - foreach (KeyValuePair> queryParameter in queryParameters) - { - if (queryParameter.Value == null) - { - throw new ArgumentException( - String.Format( - "Query parameter '{0}' has a null getter; this is not supported.", - queryParameter.Key - ), - nameof(queryParameters) - ); - } + bool modified = false; + ImmutableDictionary>.Builder queryParametersBuilder = request.QueryParameters.ToBuilder(); + foreach (KeyValuePair> queryParameter in queryParameters) + { + if (queryParameter.Value == null) + { + throw new ArgumentException( + String.Format( + "Query parameter '{0}' has a null getter; this is not supported.", + queryParameter.Key + ), + nameof(queryParameters) + ); + } - queryParametersBuilder[queryParameter.Key] = queryParameter.Value; - modified = true; - } + queryParametersBuilder[queryParameter.Key] = queryParameter.Value; + modified = true; + } - if (!modified) - return request; + if (!modified) + return request; - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = queryParametersBuilder.ToImmutable(); - }); - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = queryParametersBuilder.ToImmutable(); + }); + } - /// - /// Create a copy of the request builder without the specified request URI query parameter. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// The new . - /// - public static HttpRequest WithoutQueryParameter(this HttpRequest request, string name) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request builder without the specified request URI query parameter. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// The new . + /// + public static HttpRequest WithoutQueryParameter(this HttpRequest request, string name) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - if (!request.QueryParameters.ContainsKey(name)) - return request; + if (!request.QueryParameters.ContainsKey(name)) + return request; - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.Remove(name); - }); - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.Remove(name); + }); + } - /// - /// Create a copy of the request builder without the specified request URI query parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter names. - /// - /// - /// The new . - /// - public static HttpRequest WithoutQueryParameters(this HttpRequest request, IEnumerable names) - { - if (names == null) - throw new ArgumentNullException(nameof(names)); + /// + /// Create a copy of the request builder without the specified request URI query parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter names. + /// + /// + /// The new . + /// + public static HttpRequest WithoutQueryParameters(this HttpRequest request, IEnumerable names) + { + if (names == null) + throw new ArgumentNullException(nameof(names)); - return request.Clone(properties => - { - properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.RemoveRange(names); - }); - } - } + return request.Clone(properties => + { + properties[nameof(HttpRequest.QueryParameters)] = request.QueryParameters.RemoveRange(names); + }); + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.RequestActions.cs b/src/HTTPlease.Core/TypedRequestExtensions.RequestActions.cs index d178002..66b5d47 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.RequestActions.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.RequestActions.cs @@ -3,142 +3,142 @@ namespace HTTPlease { - using Core; + using Core; - /// - /// / extension methods for request-configuration actions. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for request-configuration actions. + /// + public static partial class TypedRequestExtensions { - /// - /// Create a copy of the request with the specified request-configuration action. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (requestAction == null) - throw new ArgumentNullException(nameof(requestAction)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add( - (message, context) => requestAction(message) - ); - }); - } - - /// - /// Create a copy of the request with the specified request-configuration action. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (requestAction == null) - throw new ArgumentNullException(nameof(requestAction)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add(requestAction); - }); - } - - /// - /// Create a copy of the request with the specified request-configuration actions. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (requestActions == null) - throw new ArgumentNullException(nameof(requestActions)); - - if (requestActions.Length == 0) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange( - requestActions.Select(requestAction => - { - RequestAction requestActionWithContext = (message, context) => requestAction(message); - - return requestActionWithContext; - }) - ); - }); - } - - /// - /// Create a copy of the request with the specified request-configuration actions. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures outgoing request messages. - /// - /// - /// The new . - /// - public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (requestActions == null) - throw new ArgumentNullException(nameof(requestActions)); - - if (requestActions.Length == 0) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange(requestActions); - }); - } - } + /// + /// Create a copy of the request with the specified request-configuration action. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (requestAction == null) + throw new ArgumentNullException(nameof(requestAction)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add( + (message, context) => requestAction(message) + ); + }); + } + + /// + /// Create a copy of the request with the specified request-configuration action. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, RequestAction requestAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (requestAction == null) + throw new ArgumentNullException(nameof(requestAction)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.Add(requestAction); + }); + } + + /// + /// Create a copy of the request with the specified request-configuration actions. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (requestActions == null) + throw new ArgumentNullException(nameof(requestActions)); + + if (requestActions.Length == 0) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange( + requestActions.Select(requestAction => + { + RequestAction requestActionWithContext = (message, context) => requestAction(message); + + return requestActionWithContext; + }) + ); + }); + } + + /// + /// Create a copy of the request with the specified request-configuration actions. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures outgoing request messages. + /// + /// + /// The new . + /// + public static HttpRequest WithRequestAction(this HttpRequest request, params RequestAction[] requestActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (requestActions == null) + throw new ArgumentNullException(nameof(requestActions)); + + if (requestActions.Length == 0) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.RequestActions)] = request.RequestActions.AddRange(requestActions); + }); + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.RequestUri.cs b/src/HTTPlease.Core/TypedRequestExtensions.RequestUri.cs index 93587fe..34ddb05 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.RequestUri.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.RequestUri.cs @@ -2,141 +2,141 @@ namespace HTTPlease { - using Core.Utilities; + using Core.Utilities; - /// - /// / extension methods for request URIs. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for request URIs. + /// + public static partial class TypedRequestExtensions { - /// - /// Create a copy of the request with the specified base URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The request. - /// - /// - /// The request base URI (must be absolute). - /// - /// - /// The new . - /// - /// - /// The request already has an absolute URI. - /// - public static HttpRequest WithBaseUri(this HttpRequest request, Uri baseUri) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified base URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The request. + /// + /// + /// The request base URI (must be absolute). + /// + /// + /// The new . + /// + /// + /// The request already has an absolute URI. + /// + public static HttpRequest WithBaseUri(this HttpRequest request, Uri baseUri) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (baseUri == null) - throw new ArgumentNullException(nameof(baseUri)); + if (baseUri == null) + throw new ArgumentNullException(nameof(baseUri)); - if (!baseUri.IsAbsoluteUri) - throw new ArgumentException("The supplied base URI is not an absolute URI.", nameof(baseUri)); + if (!baseUri.IsAbsoluteUri) + throw new ArgumentException("The supplied base URI is not an absolute URI.", nameof(baseUri)); - if (request.Uri.IsAbsoluteUri) - throw new InvalidOperationException("The request already has an absolute URI."); + if (request.Uri.IsAbsoluteUri) + throw new InvalidOperationException("The request already has an absolute URI."); - return request.Clone(properties => - { - properties.SetUri( - baseUri.AppendRelativeUri(request.Uri) - ); - }); - } + return request.Clone(properties => + { + properties.SetUri( + baseUri.AppendRelativeUri(request.Uri) + ); + }); + } - /// - /// Create a copy of the request with the specified request URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The request. - /// - /// - /// The new request URI. - /// - /// Must be an absolute URI (otherwise, use ). - /// - /// - /// The new . - /// - public static HttpRequest WithUri(this HttpRequest request, Uri requestUri) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified request URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The request. + /// + /// + /// The new request URI. + /// + /// Must be an absolute URI (otherwise, use ). + /// + /// + /// The new . + /// + public static HttpRequest WithUri(this HttpRequest request, Uri requestUri) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return request.Clone(properties => - { - properties.SetUri(requestUri); - }); - } + return request.Clone(properties => + { + properties.SetUri(requestUri); + }); + } - /// - /// Create a copy of the request with the specified request URI appended to its existing URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The request. - /// - /// - /// The relative request URI. - /// - /// - /// The new . - /// - public static HttpRequest WithRelativeUri(this HttpRequest request, string relativeUri) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the request with the specified request URI appended to its existing URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The request. + /// + /// + /// The relative request URI. + /// + /// + /// The new . + /// + public static HttpRequest WithRelativeUri(this HttpRequest request, string relativeUri) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (String.IsNullOrWhiteSpace(relativeUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'relativeUri'.", nameof(relativeUri)); + if (String.IsNullOrWhiteSpace(relativeUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'relativeUri'.", nameof(relativeUri)); - return request.WithRelativeUri( - new Uri(relativeUri, UriKind.Relative) - ); - } + return request.WithRelativeUri( + new Uri(relativeUri, UriKind.Relative) + ); + } - /// - /// Create a copy of the request with the specified request URI appended to its existing URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The request. - /// - /// - /// The relative request URI. - /// - /// - /// The new . - /// - public static HttpRequest WithRelativeUri(this HttpRequest request, Uri relativeUri) - { - if (relativeUri == null) - throw new ArgumentNullException(nameof(relativeUri)); + /// + /// Create a copy of the request with the specified request URI appended to its existing URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The request. + /// + /// + /// The relative request URI. + /// + /// + /// The new . + /// + public static HttpRequest WithRelativeUri(this HttpRequest request, Uri relativeUri) + { + if (relativeUri == null) + throw new ArgumentNullException(nameof(relativeUri)); - if (relativeUri.IsAbsoluteUri) - throw new ArgumentException("The specified URI is not a relative URI.", nameof(relativeUri)); + if (relativeUri.IsAbsoluteUri) + throw new ArgumentException("The specified URI is not a relative URI.", nameof(relativeUri)); - return request.Clone(properties => - { - properties.SetUri( - request.Uri.AppendRelativeUri(relativeUri) - ); - }); - } - } + return request.Clone(properties => + { + properties.SetUri( + request.Uri.AppendRelativeUri(relativeUri) + ); + }); + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.ResponseActions.cs b/src/HTTPlease.Core/TypedRequestExtensions.ResponseActions.cs index 53d0633..a675939 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.ResponseActions.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.ResponseActions.cs @@ -3,142 +3,142 @@ namespace HTTPlease { - using Core; + using Core; - /// - /// / extension methods for response-processing actions. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for response-processing actions. + /// + public static partial class TypedRequestExtensions { - /// - /// Create a copy of the request with the specified response-processing action. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (responseAction == null) - throw new ArgumentNullException(nameof(responseAction)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add( - (message, context) => responseAction(message) - ); - }); - } - - /// - /// Create a copy of the request with the specified response-processing action. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (responseAction == null) - throw new ArgumentNullException(nameof(responseAction)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add(responseAction); - }); - } - - /// - /// Create a copy of the request with the specified response-processing actions. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (responseActions == null) - throw new ArgumentNullException(nameof(responseActions)); - - if (responseActions.Length == 0) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange( - responseActions.Select(responseAction => - { - ResponseAction responseActionWithContext = (message, context) => responseAction(message); - - return responseActionWithContext; - }) - ); - }); - } - - /// - /// Create a copy of the request with the specified response-processing actions. - /// - /// - /// The type of object used by the request when resolving deferred template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A delegate that configures incoming response messages. - /// - /// - /// The new . - /// - public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (responseActions == null) - throw new ArgumentNullException(nameof(responseActions)); - - if (responseActions.Length == 0) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange(responseActions); - }); - } - } + /// + /// Create a copy of the request with the specified response-processing action. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (responseAction == null) + throw new ArgumentNullException(nameof(responseAction)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add( + (message, context) => responseAction(message) + ); + }); + } + + /// + /// Create a copy of the request with the specified response-processing action. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, ResponseAction responseAction) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (responseAction == null) + throw new ArgumentNullException(nameof(responseAction)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.Add(responseAction); + }); + } + + /// + /// Create a copy of the request with the specified response-processing actions. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (responseActions == null) + throw new ArgumentNullException(nameof(responseActions)); + + if (responseActions.Length == 0) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange( + responseActions.Select(responseAction => + { + ResponseAction responseActionWithContext = (message, context) => responseAction(message); + + return responseActionWithContext; + }) + ); + }); + } + + /// + /// Create a copy of the request with the specified response-processing actions. + /// + /// + /// The type of object used by the request when resolving deferred template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A delegate that configures incoming response messages. + /// + /// + /// The new . + /// + public static HttpRequest WithResponseAction(this HttpRequest request, params ResponseAction[] responseActions) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (responseActions == null) + throw new ArgumentNullException(nameof(responseActions)); + + if (responseActions.Length == 0) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.ResponseActions)] = request.ResponseActions.AddRange(responseActions); + }); + } + } } diff --git a/src/HTTPlease.Core/TypedRequestExtensions.TemplateParameters.cs b/src/HTTPlease.Core/TypedRequestExtensions.TemplateParameters.cs index 6ccf695..8f9c151 100644 --- a/src/HTTPlease.Core/TypedRequestExtensions.TemplateParameters.cs +++ b/src/HTTPlease.Core/TypedRequestExtensions.TemplateParameters.cs @@ -4,294 +4,294 @@ namespace HTTPlease { - using Core.ValueProviders; + using Core.ValueProviders; - /// - /// / extension methods for template parameters. - /// - public static partial class TypedRequestExtensions + /// + /// / extension methods for template parameters. + /// + public static partial class TypedRequestExtensions { - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The parameter data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// The parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, TValue value) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - return request.WithTemplateParameterFromProvider(name, - ValueProvider.FromConstantValue(value?.ToString()) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The parameter data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that returns the parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithTemplateParameterFromProvider(name, - ValueProvider.FromFunction(getValue).Convert().ValueToString() - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The parameter data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// Delegate that returns the parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, Func getValue) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (getValue == null) - throw new ArgumentNullException(nameof(getValue)); - - return request.WithTemplateParameterFromProvider(name, - ValueProvider.FromSelector(getValue).Convert().ValueToString() - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameter. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The parameter data-type. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// A value provider that, given the current context, returns the parameter value. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (valueProvider == null) - throw new ArgumentNullException(nameof(valueProvider)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.SetItem( - key: name, - value: valueProvider.Convert().ValueToString() - ); - }); - } - - /// - /// Create a copy of the request, but with template parameters from the specified object's properties. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The type of object whose properties will form the parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The object whose properties will form the parameters. - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParameters(HttpRequest request, TParameters parameters) - { - if (ReferenceEquals(parameters, null)) - throw new ArgumentNullException(nameof(parameters)); - - if (parameters == null) - throw new ArgumentNullException(nameof(parameters)); - - return request.WithTemplateParametersFromProviders( - CreateDeferredParameters(parameters) - ); - } - - /// - /// Create a copy of the request builder with the specified request URI template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// A sequence of 0 or more key / value pairs representing the template parameters (values cannot be null). - /// - /// - /// The new . - /// - public static HttpRequest WithTemplateParametersFromProviders(this HttpRequest request, IEnumerable>> templateParameters) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (templateParameters == null) - throw new ArgumentNullException(nameof(templateParameters)); - - bool modified = false; - ImmutableDictionary>.Builder templateParametersBuilder = request.TemplateParameters.ToBuilder(); - foreach (KeyValuePair> templateParameter in templateParameters) - { - if (templateParameter.Value == null) - { - throw new ArgumentException( - String.Format( - "Template parameter '{0}' has a null getter; this is not supported.", - templateParameter.Key - ), - nameof(templateParameters) - ); - } - - templateParametersBuilder[templateParameter.Key] = templateParameter.Value; - modified = true; - } - - if (!modified) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = templateParametersBuilder.ToImmutable(); - }); - } - - /// - /// Create a copy of the request builder without the specified request URI template parameter. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter name. - /// - /// - /// The new . - /// - public static HttpRequest WithoutTemplateParameter(this HttpRequest request, string name) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (String.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); - - if (!request.TemplateParameters.ContainsKey(name)) - return request; - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.Remove(name); - }); - } - - /// - /// Create a copy of the request builder without the specified request URI template parameters. - /// - /// - /// The HTTP request. - /// - /// - /// The parameter names. - /// - /// - /// The new . - /// - public static HttpRequest WithoutTemplateParameters(this HttpRequest request, IEnumerable names) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (names == null) - throw new ArgumentNullException(nameof(names)); - - return request.Clone(properties => - { - properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.RemoveRange(names); - }); - } - } + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The parameter data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// The parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, TValue value) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + return request.WithTemplateParameterFromProvider(name, + ValueProvider.FromConstantValue(value?.ToString()) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The parameter data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that returns the parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithTemplateParameterFromProvider(name, + ValueProvider.FromFunction(getValue).Convert().ValueToString() + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The parameter data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// Delegate that returns the parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameter(this HttpRequest request, string name, Func getValue) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (getValue == null) + throw new ArgumentNullException(nameof(getValue)); + + return request.WithTemplateParameterFromProvider(name, + ValueProvider.FromSelector(getValue).Convert().ValueToString() + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameter. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The parameter data-type. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// A value provider that, given the current context, returns the parameter value. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameterFromProvider(this HttpRequest request, string name, IValueProvider valueProvider) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (valueProvider == null) + throw new ArgumentNullException(nameof(valueProvider)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.SetItem( + key: name, + value: valueProvider.Convert().ValueToString() + ); + }); + } + + /// + /// Create a copy of the request, but with template parameters from the specified object's properties. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The type of object whose properties will form the parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The object whose properties will form the parameters. + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParameters(HttpRequest request, TParameters parameters) + { + if (ReferenceEquals(parameters, null)) + throw new ArgumentNullException(nameof(parameters)); + + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + + return request.WithTemplateParametersFromProviders( + CreateDeferredParameters(parameters) + ); + } + + /// + /// Create a copy of the request builder with the specified request URI template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// A sequence of 0 or more key / value pairs representing the template parameters (values cannot be null). + /// + /// + /// The new . + /// + public static HttpRequest WithTemplateParametersFromProviders(this HttpRequest request, IEnumerable>> templateParameters) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (templateParameters == null) + throw new ArgumentNullException(nameof(templateParameters)); + + bool modified = false; + ImmutableDictionary>.Builder templateParametersBuilder = request.TemplateParameters.ToBuilder(); + foreach (KeyValuePair> templateParameter in templateParameters) + { + if (templateParameter.Value == null) + { + throw new ArgumentException( + String.Format( + "Template parameter '{0}' has a null getter; this is not supported.", + templateParameter.Key + ), + nameof(templateParameters) + ); + } + + templateParametersBuilder[templateParameter.Key] = templateParameter.Value; + modified = true; + } + + if (!modified) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = templateParametersBuilder.ToImmutable(); + }); + } + + /// + /// Create a copy of the request builder without the specified request URI template parameter. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter name. + /// + /// + /// The new . + /// + public static HttpRequest WithoutTemplateParameter(this HttpRequest request, string name) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (String.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'name'.", nameof(name)); + + if (!request.TemplateParameters.ContainsKey(name)) + return request; + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.Remove(name); + }); + } + + /// + /// Create a copy of the request builder without the specified request URI template parameters. + /// + /// + /// The HTTP request. + /// + /// + /// The parameter names. + /// + /// + /// The new . + /// + public static HttpRequest WithoutTemplateParameters(this HttpRequest request, IEnumerable names) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (names == null) + throw new ArgumentNullException(nameof(names)); + + return request.Clone(properties => + { + properties[nameof(HttpRequest.TemplateParameters)] = request.TemplateParameters.RemoveRange(names); + }); + } + } } diff --git a/src/HTTPlease.Core/UriTemplate.cs b/src/HTTPlease.Core/UriTemplate.cs index 5f66c17..a3611dd 100644 --- a/src/HTTPlease.Core/UriTemplate.cs +++ b/src/HTTPlease.Core/UriTemplate.cs @@ -5,171 +5,171 @@ namespace HTTPlease { - using Core.Templates; - - /// - /// Populates parameterised URI templates. - /// - public sealed class UriTemplate - { - /// - /// The URI template. - /// - readonly string _template; - - /// - /// The template's URI segments. - /// - readonly IReadOnlyList _uriSegments; - - /// - /// The template's URI segments. - /// - readonly IReadOnlyList _querySegments; - - /// - /// Create a new URI template. - /// - /// - /// The template. - /// - public UriTemplate(string template) - { - if (String.IsNullOrWhiteSpace(template)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'template'.", nameof(template)); - - _template = template; - - IReadOnlyList templateSegments = TemplateSegment.Parse(_template); - _uriSegments = templateSegments.OfType().ToArray(); - if (_uriSegments.Count == 0) - throw new UriTemplateException("Invalid URI template (contains no path segments)."); - - _querySegments = templateSegments.OfType().ToArray(); - } - - /// - /// Build a URI from the template. - /// - /// - /// A dictionary containing the template parameters. - /// - /// - /// The generated URI. - /// - public Uri Populate(IDictionary templateParameters) - { - return Populate(null, templateParameters); - } - - /// - /// Build a URI from the template. - /// - /// - /// The base URI, or null to generate a relative URI. - /// - /// - /// A dictionary containing the template parameters. - /// - /// - /// The generated URI. - /// - public Uri Populate(Uri baseUri, IDictionary templateParameters) - { - if (baseUri != null && !baseUri.IsAbsoluteUri) - throw new UriTemplateException("Base URI '{0}' is not an absolute URI.", baseUri); - - if (templateParameters == null) - throw new ArgumentNullException(nameof(templateParameters)); - - TemplateEvaluationContext evaluationContext = new TemplateEvaluationContext(templateParameters); - StringBuilder uriBuilder = new StringBuilder(); - if (baseUri != null) - { - uriBuilder.Append( - baseUri.GetComponents(UriComponents.Scheme | UriComponents.StrongAuthority, UriFormat.UriEscaped) - ); - } - - if (_uriSegments.Count > 0) - { - foreach (UriSegment uriSegment in _uriSegments) - { - string segmentValue = uriSegment.GetValue(evaluationContext); - if (segmentValue == null) - continue; - - uriBuilder.Append( - Uri.EscapeUriString(segmentValue) - ); - if (uriSegment.IsDirectory) - uriBuilder.Append('/'); - } - } - else - uriBuilder.Append('/'); - - bool isFirstParameterWithValue = true; - foreach (QuerySegment segment in _querySegments) - { - string queryParameterValue = segment.GetValue(evaluationContext); - if (queryParameterValue == null) - continue; - - // Different prefix for first parameter that has a value. - if (isFirstParameterWithValue) - { - uriBuilder.Append('?'); - - isFirstParameterWithValue = false; - } - else - uriBuilder.Append('&'); - - uriBuilder.AppendFormat( - "{0}={1}", - Uri.EscapeDataString(segment.QueryParameterName), - Uri.EscapeDataString(queryParameterValue) - ); - } - - return new Uri(uriBuilder.ToString(), UriKind.RelativeOrAbsolute); - } - - /// - /// Does the specified URI represent a template? - /// - /// - /// The URI. - /// - /// - /// true, if any of the URI's components are parameterised (i.e. have non-constant values); otherwise, false. - /// - public static bool IsTemplate(Uri uri) - { - if (uri == null) - throw new ArgumentNullException(nameof(uri)); - - return IsTemplate(uri.ToString()); - } - - /// - /// Does the specified URI represent a template? - /// - /// - /// The URI. - /// - /// - /// true, if any of the URI's components are parameterised (i.e. have non-constant values); otherwise, false. - /// - public static bool IsTemplate(string uri) - { - if (uri == null) - throw new ArgumentNullException(nameof(uri)); - - IReadOnlyList templateSegments = TemplateSegment.Parse(uri); - - return templateSegments.Any(segment => segment.IsParameterized); - } - } + using Core.Templates; + + /// + /// Populates parameterised URI templates. + /// + public sealed class UriTemplate + { + /// + /// The URI template. + /// + readonly string _template; + + /// + /// The template's URI segments. + /// + readonly IReadOnlyList _uriSegments; + + /// + /// The template's URI segments. + /// + readonly IReadOnlyList _querySegments; + + /// + /// Create a new URI template. + /// + /// + /// The template. + /// + public UriTemplate(string template) + { + if (String.IsNullOrWhiteSpace(template)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'template'.", nameof(template)); + + _template = template; + + IReadOnlyList templateSegments = TemplateSegment.Parse(_template); + _uriSegments = templateSegments.OfType().ToArray(); + if (_uriSegments.Count == 0) + throw new UriTemplateException("Invalid URI template (contains no path segments)."); + + _querySegments = templateSegments.OfType().ToArray(); + } + + /// + /// Build a URI from the template. + /// + /// + /// A dictionary containing the template parameters. + /// + /// + /// The generated URI. + /// + public Uri Populate(IDictionary templateParameters) + { + return Populate(null, templateParameters); + } + + /// + /// Build a URI from the template. + /// + /// + /// The base URI, or null to generate a relative URI. + /// + /// + /// A dictionary containing the template parameters. + /// + /// + /// The generated URI. + /// + public Uri Populate(Uri baseUri, IDictionary templateParameters) + { + if (baseUri != null && !baseUri.IsAbsoluteUri) + throw new UriTemplateException("Base URI '{0}' is not an absolute URI.", baseUri); + + if (templateParameters == null) + throw new ArgumentNullException(nameof(templateParameters)); + + TemplateEvaluationContext evaluationContext = new TemplateEvaluationContext(templateParameters); + StringBuilder uriBuilder = new StringBuilder(); + if (baseUri != null) + { + uriBuilder.Append( + baseUri.GetComponents(UriComponents.Scheme | UriComponents.StrongAuthority, UriFormat.UriEscaped) + ); + } + + if (_uriSegments.Count > 0) + { + foreach (UriSegment uriSegment in _uriSegments) + { + string segmentValue = uriSegment.GetValue(evaluationContext); + if (segmentValue == null) + continue; + + uriBuilder.Append( + Uri.EscapeUriString(segmentValue) + ); + if (uriSegment.IsDirectory) + uriBuilder.Append('/'); + } + } + else + uriBuilder.Append('/'); + + bool isFirstParameterWithValue = true; + foreach (QuerySegment segment in _querySegments) + { + string queryParameterValue = segment.GetValue(evaluationContext); + if (queryParameterValue == null) + continue; + + // Different prefix for first parameter that has a value. + if (isFirstParameterWithValue) + { + uriBuilder.Append('?'); + + isFirstParameterWithValue = false; + } + else + uriBuilder.Append('&'); + + uriBuilder.AppendFormat( + "{0}={1}", + Uri.EscapeDataString(segment.QueryParameterName), + Uri.EscapeDataString(queryParameterValue) + ); + } + + return new Uri(uriBuilder.ToString(), UriKind.RelativeOrAbsolute); + } + + /// + /// Does the specified URI represent a template? + /// + /// + /// The URI. + /// + /// + /// true, if any of the URI's components are parameterised (i.e. have non-constant values); otherwise, false. + /// + public static bool IsTemplate(Uri uri) + { + if (uri == null) + throw new ArgumentNullException(nameof(uri)); + + return IsTemplate(uri.ToString()); + } + + /// + /// Does the specified URI represent a template? + /// + /// + /// The URI. + /// + /// + /// true, if any of the URI's components are parameterised (i.e. have non-constant values); otherwise, false. + /// + public static bool IsTemplate(string uri) + { + if (uri == null) + throw new ArgumentNullException(nameof(uri)); + + IReadOnlyList templateSegments = TemplateSegment.Parse(uri); + + return templateSegments.Any(segment => segment.IsParameterized); + } + } } diff --git a/src/HTTPlease.Core/UriTemplateException.cs b/src/HTTPlease.Core/UriTemplateException.cs index bdaebb2..a22ec92 100644 --- a/src/HTTPlease.Core/UriTemplateException.cs +++ b/src/HTTPlease.Core/UriTemplateException.cs @@ -2,41 +2,41 @@ namespace HTTPlease { - /// - /// Exception raised when a is invalid or is missing required information. - /// - public class UriTemplateException - : Exception - { - /// - /// Create a new . - /// - /// - /// The exception message or message-format specifier. - /// - /// - /// Optional message format arguments. - /// - public UriTemplateException(string messageOrFormat, params object[] formatArguments) - : base(String.Format(messageOrFormat, formatArguments)) - { - } + /// + /// Exception raised when a is invalid or is missing required information. + /// + public class UriTemplateException + : Exception + { + /// + /// Create a new . + /// + /// + /// The exception message or message-format specifier. + /// + /// + /// Optional message format arguments. + /// + public UriTemplateException(string messageOrFormat, params object[] formatArguments) + : base(String.Format(messageOrFormat, formatArguments)) + { + } - /// - /// Create a new . - /// - /// - /// The exception that caused this exception to be raised. - /// - /// - /// The exception message or message-format specifier. - /// - /// - /// Optional message format arguments. - /// - public UriTemplateException(Exception innerException, string messageOrFormat, params object[] formatArguments) - : base(String.Format(messageOrFormat, formatArguments), innerException) - { - } - } + /// + /// Create a new . + /// + /// + /// The exception that caused this exception to be raised. + /// + /// + /// The exception message or message-format specifier. + /// + /// + /// Optional message format arguments. + /// + public UriTemplateException(Exception innerException, string messageOrFormat, params object[] formatArguments) + : base(String.Format(messageOrFormat, formatArguments), innerException) + { + } + } } diff --git a/src/HTTPlease.Diagnostics/ClientBuilderExtensions.cs b/src/HTTPlease.Diagnostics/ClientBuilderExtensions.cs index dfa91fb..93f11ae 100644 --- a/src/HTTPlease.Diagnostics/ClientBuilderExtensions.cs +++ b/src/HTTPlease.Diagnostics/ClientBuilderExtensions.cs @@ -3,68 +3,68 @@ namespace HTTPlease.Diagnostics { - using MessageHandlers; - - /// - /// Extension methods for the HTTP client builder. - /// - public static class ClientBuilderExtensions - { - /// - /// Create a copy of the HTTP client builder whose clients will log requests and responses to the specified logger. - /// - /// - /// The HTTP client builder. - /// - /// - /// The logger used to log the event. - /// - /// - /// A value indicating which components of each request message should be logged. - /// - /// - /// A value indicating which components of each response message should be logged. - /// - /// - /// The new . - /// - /// - /// This overload is for convenience only; for the purposes of reliability you should resolve the logger when you are creating the client (it's not good practice to share the same instance of a logger between multiple clients). - /// - public static ClientBuilder WithLogging(this ClientBuilder clientBuilder, ILogger logger, LogMessageComponents requestComponents = LogMessageComponents.Basic, LogMessageComponents responseComponents = LogMessageComponents.Basic) - { - return clientBuilder.WithLogging(() => logger, requestComponents, responseComponents); - } - - /// - /// Create a copy of the HTTP client builder whose clients will log requests and responses to the specified logger. - /// - /// - /// The HTTP client builder. - /// - /// - /// A delegate that produces the logger for each client. - /// - /// - /// A value indicating which components of each request message should be logged. - /// - /// - /// A value indicating which components of each response message should be logged. - /// - /// - /// The new . - /// - /// - /// Each call to should return a new instance of the logger (it's not good practice to share the same instance of a logger between multiple clients). - /// - public static ClientBuilder WithLogging(this ClientBuilder clientBuilder, Func loggerFactory, LogMessageComponents requestComponents = LogMessageComponents.Basic, LogMessageComponents responseComponents = LogMessageComponents.Basic) - { - return clientBuilder.AddHandler(() => - { - ILogger logger = loggerFactory(); - - return new LoggingMessageHandler(logger, requestComponents, responseComponents); - }); - } - } + using MessageHandlers; + + /// + /// Extension methods for the HTTP client builder. + /// + public static class ClientBuilderExtensions + { + /// + /// Create a copy of the HTTP client builder whose clients will log requests and responses to the specified logger. + /// + /// + /// The HTTP client builder. + /// + /// + /// The logger used to log the event. + /// + /// + /// A value indicating which components of each request message should be logged. + /// + /// + /// A value indicating which components of each response message should be logged. + /// + /// + /// The new . + /// + /// + /// This overload is for convenience only; for the purposes of reliability you should resolve the logger when you are creating the client (it's not good practice to share the same instance of a logger between multiple clients). + /// + public static ClientBuilder WithLogging(this ClientBuilder clientBuilder, ILogger logger, LogMessageComponents requestComponents = LogMessageComponents.Basic, LogMessageComponents responseComponents = LogMessageComponents.Basic) + { + return clientBuilder.WithLogging(() => logger, requestComponents, responseComponents); + } + + /// + /// Create a copy of the HTTP client builder whose clients will log requests and responses to the specified logger. + /// + /// + /// The HTTP client builder. + /// + /// + /// A delegate that produces the logger for each client. + /// + /// + /// A value indicating which components of each request message should be logged. + /// + /// + /// A value indicating which components of each response message should be logged. + /// + /// + /// The new . + /// + /// + /// Each call to should return a new instance of the logger (it's not good practice to share the same instance of a logger between multiple clients). + /// + public static ClientBuilder WithLogging(this ClientBuilder clientBuilder, Func loggerFactory, LogMessageComponents requestComponents = LogMessageComponents.Basic, LogMessageComponents responseComponents = LogMessageComponents.Basic) + { + return clientBuilder.AddHandler(() => + { + ILogger logger = loggerFactory(); + + return new LoggingMessageHandler(logger, requestComponents, responseComponents); + }); + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Diagnostics/LogEventIds.cs b/src/HTTPlease.Diagnostics/LogEventIds.cs index edf807f..ca481ae 100644 --- a/src/HTTPlease.Diagnostics/LogEventIds.cs +++ b/src/HTTPlease.Diagnostics/LogEventIds.cs @@ -3,38 +3,38 @@ namespace HTTPlease.Diagnostics { /// - /// The Ids of well-known log events raised by HTTPlease diagnostics. + /// The Ids of well-known log events raised by HTTPlease diagnostics. /// public static class LogEventIds - { - /// - /// An outgoing HTTP request is being performed. - /// - public static readonly EventId BeginRequest = new EventId(100, nameof(BeginRequest)); + { + /// + /// An outgoing HTTP request is being performed. + /// + public static readonly EventId BeginRequest = new EventId(100, nameof(BeginRequest)); - /// - /// The body of an outgoing HTTP request. - /// - public static readonly EventId RequestBody = new EventId(101, nameof(RequestBody)); + /// + /// The body of an outgoing HTTP request. + /// + public static readonly EventId RequestBody = new EventId(101, nameof(RequestBody)); - /// - /// The body of an incoming HTTP response. - /// - public static readonly EventId ResponseBody = new EventId(102, nameof(ResponseBody)); + /// + /// The body of an incoming HTTP response. + /// + public static readonly EventId ResponseBody = new EventId(102, nameof(ResponseBody)); - /// - /// The body of an incoming HTTP response is streamed. - /// - public static readonly EventId StreamedResponse = new EventId(103, nameof(StreamedResponse)); - - /// - /// An incoming HTTP response has been received. - /// - public static readonly EventId EndRequest = new EventId(110, nameof(EndRequest)); - - /// - /// An exception occurred while performing an HTTP request. - /// - public static readonly EventId RequestError = new EventId(115, nameof(RequestError)); - } + /// + /// The body of an incoming HTTP response is streamed. + /// + public static readonly EventId StreamedResponse = new EventId(103, nameof(StreamedResponse)); + + /// + /// An incoming HTTP response has been received. + /// + public static readonly EventId EndRequest = new EventId(110, nameof(EndRequest)); + + /// + /// An exception occurred while performing an HTTP request. + /// + public static readonly EventId RequestError = new EventId(115, nameof(RequestError)); + } } diff --git a/src/HTTPlease.Diagnostics/LogMessageComponents.cs b/src/HTTPlease.Diagnostics/LogMessageComponents.cs index f7584c7..79a9a1b 100644 --- a/src/HTTPlease.Diagnostics/LogMessageComponents.cs +++ b/src/HTTPlease.Diagnostics/LogMessageComponents.cs @@ -2,35 +2,35 @@ namespace HTTPlease.Diagnostics { - /// - /// Components of an HTTP message that should be logged. - /// - [Flags] - public enum LogMessageComponents - { - /// - /// No message components should be logged. - /// - None = 0, + /// + /// Components of an HTTP message that should be logged. + /// + [Flags] + public enum LogMessageComponents + { + /// + /// No message components should be logged. + /// + None = 0, - /// - /// Basic message components (e.g. method and request URI) should be logged. - /// - Basic = 1, + /// + /// Basic message components (e.g. method and request URI) should be logged. + /// + Basic = 1, - /// - /// Message body should be logged. - /// - Body = 2, + /// + /// Message body should be logged. + /// + Body = 2, - /// - /// Message headers should be logged. - /// - Headers = 4, + /// + /// Message headers should be logged. + /// + Headers = 4, - /// - /// All message components should be logged. - /// - All = Basic | Body | Headers - } + /// + /// All message components should be logged. + /// + All = Basic | Body | Headers + } } \ No newline at end of file diff --git a/src/HTTPlease.Diagnostics/LoggerExtensions.cs b/src/HTTPlease.Diagnostics/LoggerExtensions.cs index 7abf8d4..5d5f4c7 100644 --- a/src/HTTPlease.Diagnostics/LoggerExtensions.cs +++ b/src/HTTPlease.Diagnostics/LoggerExtensions.cs @@ -7,185 +7,185 @@ namespace HTTPlease.Diagnostics { /// - /// Extension methods for used to log messages about requests and responses. + /// Extension methods for used to log messages about requests and responses. /// public static class LoggerExtensions - { - /// - /// Log an event representing the start of an HTTP request. - /// - /// - /// The logger used to log the event. - /// - /// - /// An representing the request. - /// - public static void BeginRequest(this ILogger logger, HttpRequestMessage request) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - logger.LogDebug(LogEventIds.BeginRequest, "Performing {Method} request to '{RequestUri}'.", - request.Method?.Method, - request.RequestUri - ); - } - - /// - /// Asynchronously log an event representing the body of an HTTP request. - /// - /// - /// The logger used to log the event. - /// - /// - /// An representing the request. - /// - /// - /// A representing the asynchronous operation. - /// - public static async Task RequestBody(this ILogger logger, HttpRequestMessage request) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (request.Content == null) - throw new InvalidOperationException("HttpRequestMessage.Content is null."); - - if (!logger.IsEnabled(LogLevel.Debug)) - return; // Don't bother capturing request body if we won't be able to log it. - - string requestBody = await request.Content.ReadAsStringAsync(); - - logger.LogDebug(LogEventIds.RequestBody, "Send request body for {Method} request to '{RequestUri}':\n{RequestBody}", - request.Method?.Method, - request.RequestUri, - requestBody - ); - } - - /// - /// Asynchronously log an event representing the body of an HTTP response. - /// - /// - /// The logger used to log the event. - /// - /// - /// An representing the response. - /// - /// - /// A representing the asynchronous operation. - /// - public static async Task ResponseBody(this ILogger logger, HttpResponseMessage response) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (response.RequestMessage == null) - throw new InvalidOperationException("HttpResponseMessage.RequestMessage is null."); // Can't examine original request so we don't know if the response is streamed. - - if (response.Content == null) - throw new InvalidOperationException("HttpResponseMessage.Content is null."); - - if (!logger.IsEnabled(LogLevel.Debug)) - return; // Don't bother capturing response body if we won't be able to log it. - - string responseBody = await response.Content.ReadAsStringAsync(); - - logger.LogDebug(LogEventIds.ResponseBody, "Receive response body for {Method} request to '{RequestUri}' ({StatusCode}):\n{Body}", - response.RequestMessage.Method?.Method, - response.RequestMessage.RequestUri, - response.StatusCode, - responseBody - ); - } - - /// - /// Log an event representing the streamed body of an HTTP response. - /// - /// - /// The logger used to log the event. - /// - /// - /// An representing the response. - /// - public static void StreamedResponse(this ILogger logger, HttpResponseMessage response) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - - if (response == null) - throw new ArgumentNullException(nameof(response)); - - logger.LogDebug(LogEventIds.StreamedResponse, "Receive response body for {Method} request to '{RequestUri}' (response is streamed so body cannot be logged).", - response.RequestMessage.Method?.Method, - response.RequestMessage.RequestUri - ); - } - - /// - /// Log an event representing the completion of an HTTP request. - /// - /// - /// The logger used to log the event. - /// - /// - /// An representing the request. - /// - /// - /// An representing the response status code. - /// - public static void EndRequest(this ILogger logger, HttpRequestMessage request, HttpStatusCode statusCode) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - logger.LogDebug(LogEventIds.EndRequest, "Completed {Method} request to '{RequestUri}' ({StatusCode}).", - request.Method?.Method, - request.RequestUri, - statusCode - ); - } - - /// - /// Log an event representing an error encountered while performing an HTTP request. - /// - /// - /// The logger used to log the event. - /// - /// - /// An representing the request. - /// - /// - /// An representing the error. - /// - public static void RequestError(this ILogger logger, HttpRequestMessage request, Exception error) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (error == null) - throw new ArgumentNullException(nameof(error)); - - logger.LogDebug(LogEventIds.RequestError, error, "{Method} request to '{RequestUri} failed: {ErrorMessage}", - request.Method?.Method, - request.RequestUri, - error.Message - ); - } - } + { + /// + /// Log an event representing the start of an HTTP request. + /// + /// + /// The logger used to log the event. + /// + /// + /// An representing the request. + /// + public static void BeginRequest(this ILogger logger, HttpRequestMessage request) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + logger.LogDebug(LogEventIds.BeginRequest, "Performing {Method} request to '{RequestUri}'.", + request.Method?.Method, + request.RequestUri + ); + } + + /// + /// Asynchronously log an event representing the body of an HTTP request. + /// + /// + /// The logger used to log the event. + /// + /// + /// An representing the request. + /// + /// + /// A representing the asynchronous operation. + /// + public static async Task RequestBody(this ILogger logger, HttpRequestMessage request) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (request.Content == null) + throw new InvalidOperationException("HttpRequestMessage.Content is null."); + + if (!logger.IsEnabled(LogLevel.Debug)) + return; // Don't bother capturing request body if we won't be able to log it. + + string requestBody = await request.Content.ReadAsStringAsync(); + + logger.LogDebug(LogEventIds.RequestBody, "Send request body for {Method} request to '{RequestUri}':\n{RequestBody}", + request.Method?.Method, + request.RequestUri, + requestBody + ); + } + + /// + /// Asynchronously log an event representing the body of an HTTP response. + /// + /// + /// The logger used to log the event. + /// + /// + /// An representing the response. + /// + /// + /// A representing the asynchronous operation. + /// + public static async Task ResponseBody(this ILogger logger, HttpResponseMessage response) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (response.RequestMessage == null) + throw new InvalidOperationException("HttpResponseMessage.RequestMessage is null."); // Can't examine original request so we don't know if the response is streamed. + + if (response.Content == null) + throw new InvalidOperationException("HttpResponseMessage.Content is null."); + + if (!logger.IsEnabled(LogLevel.Debug)) + return; // Don't bother capturing response body if we won't be able to log it. + + string responseBody = await response.Content.ReadAsStringAsync(); + + logger.LogDebug(LogEventIds.ResponseBody, "Receive response body for {Method} request to '{RequestUri}' ({StatusCode}):\n{Body}", + response.RequestMessage.Method?.Method, + response.RequestMessage.RequestUri, + response.StatusCode, + responseBody + ); + } + + /// + /// Log an event representing the streamed body of an HTTP response. + /// + /// + /// The logger used to log the event. + /// + /// + /// An representing the response. + /// + public static void StreamedResponse(this ILogger logger, HttpResponseMessage response) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + if (response == null) + throw new ArgumentNullException(nameof(response)); + + logger.LogDebug(LogEventIds.StreamedResponse, "Receive response body for {Method} request to '{RequestUri}' (response is streamed so body cannot be logged).", + response.RequestMessage.Method?.Method, + response.RequestMessage.RequestUri + ); + } + + /// + /// Log an event representing the completion of an HTTP request. + /// + /// + /// The logger used to log the event. + /// + /// + /// An representing the request. + /// + /// + /// An representing the response status code. + /// + public static void EndRequest(this ILogger logger, HttpRequestMessage request, HttpStatusCode statusCode) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + logger.LogDebug(LogEventIds.EndRequest, "Completed {Method} request to '{RequestUri}' ({StatusCode}).", + request.Method?.Method, + request.RequestUri, + statusCode + ); + } + + /// + /// Log an event representing an error encountered while performing an HTTP request. + /// + /// + /// The logger used to log the event. + /// + /// + /// An representing the request. + /// + /// + /// An representing the error. + /// + public static void RequestError(this ILogger logger, HttpRequestMessage request, Exception error) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (error == null) + throw new ArgumentNullException(nameof(error)); + + logger.LogDebug(LogEventIds.RequestError, error, "{Method} request to '{RequestUri} failed: {ErrorMessage}", + request.Method?.Method, + request.RequestUri, + error.Message + ); + } + } } diff --git a/src/HTTPlease.Diagnostics/MessageHandlers/LoggingMessageHandler.cs b/src/HTTPlease.Diagnostics/MessageHandlers/LoggingMessageHandler.cs index f7dcc17..bd5fe62 100644 --- a/src/HTTPlease.Diagnostics/MessageHandlers/LoggingMessageHandler.cs +++ b/src/HTTPlease.Diagnostics/MessageHandlers/LoggingMessageHandler.cs @@ -6,118 +6,118 @@ namespace HTTPlease.Diagnostics.MessageHandlers { - /// - /// Client-side HTTP message handler that logs outgoing requests and incoming responses. - /// - public class LoggingMessageHandler - : DelegatingHandler - { - /// - /// Create a new . - /// - /// - /// The used to log messages about requests and responses. - /// - /// - /// A value indicating which components of each request message should be logged. - /// - /// - /// A value indicating which components of each response message should be logged. - /// - public LoggingMessageHandler(ILogger logger, LogMessageComponents requestComponents = LogMessageComponents.Basic, LogMessageComponents responseComponents = LogMessageComponents.Basic) - { - if (logger == null) - throw new ArgumentNullException(nameof(logger)); + /// + /// Client-side HTTP message handler that logs outgoing requests and incoming responses. + /// + public class LoggingMessageHandler + : DelegatingHandler + { + /// + /// Create a new . + /// + /// + /// The used to log messages about requests and responses. + /// + /// + /// A value indicating which components of each request message should be logged. + /// + /// + /// A value indicating which components of each response message should be logged. + /// + public LoggingMessageHandler(ILogger logger, LogMessageComponents requestComponents = LogMessageComponents.Basic, LogMessageComponents responseComponents = LogMessageComponents.Basic) + { + if (logger == null) + throw new ArgumentNullException(nameof(logger)); - Log = logger; - RequestComponents = requestComponents; - ResponseComponents = responseComponents; - } + Log = logger; + RequestComponents = requestComponents; + ResponseComponents = responseComponents; + } - /// - /// The used to log messages about requests and responses. - /// - protected ILogger Log { get; } + /// + /// The used to log messages about requests and responses. + /// + protected ILogger Log { get; } - /// - /// A value indicating which components of each request message should be logged. - /// - public LogMessageComponents RequestComponents { get; } + /// + /// A value indicating which components of each request message should be logged. + /// + public LogMessageComponents RequestComponents { get; } - /// - /// A value indicating which components of each response message should be logged. - /// - public LogMessageComponents ResponseComponents { get; } + /// + /// A value indicating which components of each response message should be logged. + /// + public LogMessageComponents ResponseComponents { get; } - /// - /// Asynchronously process an outgoing HTTP request message and its incoming response message. - /// - /// - /// The representing the outgoing request. - /// - /// - /// A that can be used to cancel the asynchronous operation. - /// - /// - /// Create a new . - /// - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously process an outgoing HTTP request message and its incoming response message. + /// + /// + /// The representing the outgoing request. + /// + /// + /// A that can be used to cancel the asynchronous operation. + /// + /// + /// Create a new . + /// + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (ShouldLogRequest(LogMessageComponents.Basic)) - Log.BeginRequest(request); + if (ShouldLogRequest(LogMessageComponents.Basic)) + Log.BeginRequest(request); - try - { - if (ShouldLogRequest(LogMessageComponents.Body) && request.Content != null) - await Log.RequestBody(request); + try + { + if (ShouldLogRequest(LogMessageComponents.Body) && request.Content != null) + await Log.RequestBody(request); - HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); - if (ShouldLogResponse(LogMessageComponents.Body) && response.Content != null) - { - if (!response.RequestMessage.IsStreamed()) - await Log.ResponseBody(response); - else - Log.StreamedResponse(response); - } + if (ShouldLogResponse(LogMessageComponents.Body) && response.Content != null) + { + if (!response.RequestMessage.IsStreamed()) + await Log.ResponseBody(response); + else + Log.StreamedResponse(response); + } - if (ShouldLogRequest(LogMessageComponents.Basic)) - Log.EndRequest(request, response.StatusCode); + if (ShouldLogRequest(LogMessageComponents.Basic)) + Log.EndRequest(request, response.StatusCode); - return response; - } - catch (Exception eRequest) - { - // Errors are always logged. - Log.RequestError(request, eRequest); + return response; + } + catch (Exception eRequest) + { + // Errors are always logged. + Log.RequestError(request, eRequest); - throw; - } - } + throw; + } + } - /// - /// Determine whether the specified component of request messages should be logged. - /// - /// - /// A value representing the message component. - /// - /// - /// true, if the message component should be logged; otherwise, false. - /// - protected bool ShouldLogRequest(LogMessageComponents requestComponent) => (RequestComponents & requestComponent) != 0; + /// + /// Determine whether the specified component of request messages should be logged. + /// + /// + /// A value representing the message component. + /// + /// + /// true, if the message component should be logged; otherwise, false. + /// + protected bool ShouldLogRequest(LogMessageComponents requestComponent) => (RequestComponents & requestComponent) != 0; - /// - /// Determine whether the specified component of response messages should be logged. - /// - /// - /// A value representing the message component. - /// - /// - /// true, if the message component should be logged; otherwise, false. - /// - protected bool ShouldLogResponse(LogMessageComponents responseComponent) => (ResponseComponents & responseComponent) != 0; - } + /// + /// Determine whether the specified component of response messages should be logged. + /// + /// + /// A value representing the message component. + /// + /// + /// true, if the message component should be logged; otherwise, false. + /// + protected bool ShouldLogResponse(LogMessageComponents responseComponent) => (ResponseComponents & responseComponent) != 0; + } } diff --git a/src/HTTPlease.Formatters.Json/JsonFormatter.cs b/src/HTTPlease.Formatters.Json/JsonFormatter.cs index a517af3..45dff38 100644 --- a/src/HTTPlease.Formatters.Json/JsonFormatter.cs +++ b/src/HTTPlease.Formatters.Json/JsonFormatter.cs @@ -6,122 +6,122 @@ namespace HTTPlease.Formatters.Json { - /// - /// Content formatter for JSON. - /// - public class JsonFormatter - : IInputFormatter, IOutputFormatter - { - /// - /// Create a new . - /// - public JsonFormatter() - { - } - - /// - /// Settings for the JSON serialiser. - /// - public JsonSerializerSettings SerializerSettings { get; set; } - - /// - /// Content types supported by the formatter. - /// - public ISet SupportedMediaTypes { get; } = new HashSet { WellKnownMediaTypes.Json }; - - /// - /// Determine whether the formatter can deserialise the specified data. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// true, if the formatter can deserialise the data; otherwise, false. - /// - public bool CanRead(InputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return SupportedMediaTypes.Contains(context.MediaType); - } - - /// - /// Determine whether the formatter can serialise the specified data. - /// - /// - /// Contextual information about the data being serialised. - /// - /// - /// true, if the formatter can serialise the data; otherwise, false. - /// - public bool CanWrite(OutputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return SupportedMediaTypes.Contains(context.MediaType); - } - - /// - /// Asynchronously deserialise data from an input stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The input stream from which to read serialised data. - /// - /// - /// The deserialised object. - /// - public Task ReadAsync(InputFormatterContext context, Stream stream) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - using (TextReader reader = context.CreateReader(stream)) - { - JsonSerializer serializer = JsonSerializer.Create(SerializerSettings); - object data = serializer.Deserialize(reader, context.DataType); - - return Task.FromResult(data); - } - } - - /// - /// Asynchronously serialise data to an output stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The output stream to which the serialised data will be written. - /// - /// - /// A representing the asynchronous operation. - /// - public Task WriteAsync(OutputFormatterContext context, Stream stream) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - if (!SupportedMediaTypes.Contains(context.MediaType)) - throw new NotSupportedException($"The {nameof(JsonFormatter)} cannot write content of type '{context.MediaType}'."); - - using (TextWriter writer = context.CreateWriter(stream)) - { - JsonSerializer serializer = JsonSerializer.Create(SerializerSettings); - serializer.Serialize(writer, context.Data, context.DataType); - } - - return Task.CompletedTask; - } - } + /// + /// Content formatter for JSON. + /// + public class JsonFormatter + : IInputFormatter, IOutputFormatter + { + /// + /// Create a new . + /// + public JsonFormatter() + { + } + + /// + /// Settings for the JSON serialiser. + /// + public JsonSerializerSettings SerializerSettings { get; set; } + + /// + /// Content types supported by the formatter. + /// + public ISet SupportedMediaTypes { get; } = new HashSet { WellKnownMediaTypes.Json }; + + /// + /// Determine whether the formatter can deserialise the specified data. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// true, if the formatter can deserialise the data; otherwise, false. + /// + public bool CanRead(InputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return SupportedMediaTypes.Contains(context.MediaType); + } + + /// + /// Determine whether the formatter can serialise the specified data. + /// + /// + /// Contextual information about the data being serialised. + /// + /// + /// true, if the formatter can serialise the data; otherwise, false. + /// + public bool CanWrite(OutputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return SupportedMediaTypes.Contains(context.MediaType); + } + + /// + /// Asynchronously deserialise data from an input stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The input stream from which to read serialised data. + /// + /// + /// The deserialised object. + /// + public Task ReadAsync(InputFormatterContext context, Stream stream) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + using (TextReader reader = context.CreateReader(stream)) + { + JsonSerializer serializer = JsonSerializer.Create(SerializerSettings); + object data = serializer.Deserialize(reader, context.DataType); + + return Task.FromResult(data); + } + } + + /// + /// Asynchronously serialise data to an output stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The output stream to which the serialised data will be written. + /// + /// + /// A representing the asynchronous operation. + /// + public Task WriteAsync(OutputFormatterContext context, Stream stream) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + if (!SupportedMediaTypes.Contains(context.MediaType)) + throw new NotSupportedException($"The {nameof(JsonFormatter)} cannot write content of type '{context.MediaType}'."); + + using (TextWriter writer = context.CreateWriter(stream)) + { + JsonSerializer serializer = JsonSerializer.Create(SerializerSettings); + serializer.Serialize(writer, context.Data, context.DataType); + } + + return Task.CompletedTask; + } + } } diff --git a/src/HTTPlease.Formatters.Json/JsonFormatterExtensions.cs b/src/HTTPlease.Formatters.Json/JsonFormatterExtensions.cs index 4b1d848..2beaaea 100644 --- a/src/HTTPlease.Formatters.Json/JsonFormatterExtensions.cs +++ b/src/HTTPlease.Formatters.Json/JsonFormatterExtensions.cs @@ -3,37 +3,37 @@ namespace HTTPlease { - using Formatters; - using Formatters.Json; - - /// - /// Extension methods for content formatters. - /// + using Formatters; + using Formatters.Json; + + /// + /// Extension methods for content formatters. + /// public static class JsonFormatterExtensions { - /// - /// Add the JSON content formatter. - /// - /// - /// The content formatter collection. - /// - /// - /// Optional settings for the JSON serialiser. - /// - /// - /// The content formatter collection (enables method-chaining). - /// - public static IFormatterCollection AddJsonFormatter(this IFormatterCollection formatters, JsonSerializerSettings serializerSettings = null) - { - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); + /// + /// Add the JSON content formatter. + /// + /// + /// The content formatter collection. + /// + /// + /// Optional settings for the JSON serialiser. + /// + /// + /// The content formatter collection (enables method-chaining). + /// + public static IFormatterCollection AddJsonFormatter(this IFormatterCollection formatters, JsonSerializerSettings serializerSettings = null) + { + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); - formatters.Add(new JsonFormatter - { - SerializerSettings = serializerSettings - }); + formatters.Add(new JsonFormatter + { + SerializerSettings = serializerSettings + }); - return formatters; - } + return formatters; + } } } diff --git a/src/HTTPlease.Formatters.Json/JsonFormatterFactoryExtensions.cs b/src/HTTPlease.Formatters.Json/JsonFormatterFactoryExtensions.cs index d8883e8..7f493b0 100644 --- a/src/HTTPlease.Formatters.Json/JsonFormatterFactoryExtensions.cs +++ b/src/HTTPlease.Formatters.Json/JsonFormatterFactoryExtensions.cs @@ -3,109 +3,109 @@ namespace HTTPlease { - using Formatters.Json; - - /// - /// JSON request extension methods for . - /// - public static class JsonFormatterFactoryExtensions + using Formatters.Json; + + /// + /// JSON request extension methods for . + /// + public static class JsonFormatterFactoryExtensions { - /// - /// Create a new HTTP request that expects and uses JSON as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Json(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses JSON as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Json(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - return requestFactory.Json(requestUri, null); - } + return requestFactory.Json(requestUri, null); + } - /// - /// Create a new HTTP request that expects and uses JSON as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The JSON serialiser settings used by the . - /// - /// - /// The new . - /// - public static HttpRequest Json(this HttpRequestFactory requestFactory, string requestUri, JsonSerializerSettings serializerSettings) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses JSON as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The JSON serialiser settings used by the . + /// + /// + /// The new . + /// + public static HttpRequest Json(this HttpRequestFactory requestFactory, string requestUri, JsonSerializerSettings serializerSettings) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectJson() - .UseJson(serializerSettings); - } + return + requestFactory.Create(requestUri) + .ExpectJson() + .UseJson(serializerSettings); + } - /// - /// Create a new HTTP request that expects and uses JSON as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Json(this HttpRequestFactory requestFactory, Uri requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses JSON as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Json(this HttpRequestFactory requestFactory, Uri requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - return requestFactory.Json(requestUri, null); - } + return requestFactory.Json(requestUri, null); + } - /// - /// Create a new HTTP request that expects and uses JSON as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The JSON serialiser settings used by the . - /// - /// - /// The new . - /// - public static HttpRequest Json(this HttpRequestFactory requestFactory, Uri requestUri, JsonSerializerSettings serializerSettings) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses JSON as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The JSON serialiser settings used by the . + /// + /// + /// The new . + /// + public static HttpRequest Json(this HttpRequestFactory requestFactory, Uri requestUri, JsonSerializerSettings serializerSettings) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectJson() - .UseJson(serializerSettings); - } - } + return + requestFactory.Create(requestUri) + .ExpectJson() + .UseJson(serializerSettings); + } + } } diff --git a/src/HTTPlease.Formatters.Json/JsonFormatterRequestExtensions.cs b/src/HTTPlease.Formatters.Json/JsonFormatterRequestExtensions.cs index 93ad8b8..322f337 100644 --- a/src/HTTPlease.Formatters.Json/JsonFormatterRequestExtensions.cs +++ b/src/HTTPlease.Formatters.Json/JsonFormatterRequestExtensions.cs @@ -3,64 +3,64 @@ namespace HTTPlease { - using Formatters.Json; + using Formatters.Json; - /// - /// Formatter-related extension methods for / . - /// - public static class JsonFormatterRequestExtensions - { - /// - /// Create a copy of the , configuring it to only use the JSON formatters. - /// - /// - /// The . - /// - /// - /// used to configure the formatter's behaviour. - /// - /// - /// The new . - /// - public static HttpRequest UseJson(this HttpRequest request, JsonSerializerSettings serializerSettings = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Formatter-related extension methods for / . + /// + public static class JsonFormatterRequestExtensions + { + /// + /// Create a copy of the , configuring it to only use the JSON formatters. + /// + /// + /// The . + /// + /// + /// used to configure the formatter's behaviour. + /// + /// + /// The new . + /// + public static HttpRequest UseJson(this HttpRequest request, JsonSerializerSettings serializerSettings = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - // TODO: Clear all existing formatters, first. + // TODO: Clear all existing formatters, first. - return request.WithFormatter(new JsonFormatter - { - SerializerSettings = serializerSettings - }); - } + return request.WithFormatter(new JsonFormatter + { + SerializerSettings = serializerSettings + }); + } - /// - /// Create a copy of the , configuring it to only use the JSON formatters. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// used to configure the formatter's behaviour. - /// - /// - /// The new . - /// - public static HttpRequest UseJson(this HttpRequest request, JsonSerializerSettings serializerSettings = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to only use the JSON formatters. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// used to configure the formatter's behaviour. + /// + /// + /// The new . + /// + public static HttpRequest UseJson(this HttpRequest request, JsonSerializerSettings serializerSettings = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - // TODO: Clear all existing formatters, first. + // TODO: Clear all existing formatters, first. - return request.WithFormatter(new JsonFormatter - { - SerializerSettings = serializerSettings - }); - } - } + return request.WithFormatter(new JsonFormatter + { + SerializerSettings = serializerSettings + }); + } + } } diff --git a/src/HTTPlease.Formatters.Json/JsonFormatterTypedFactoryExtensions.cs b/src/HTTPlease.Formatters.Json/JsonFormatterTypedFactoryExtensions.cs index 80b1c4c..70b292f 100644 --- a/src/HTTPlease.Formatters.Json/JsonFormatterTypedFactoryExtensions.cs +++ b/src/HTTPlease.Formatters.Json/JsonFormatterTypedFactoryExtensions.cs @@ -2,67 +2,67 @@ namespace HTTPlease.Formatters.Json { - /// - /// JSON request extension methods for . - /// - public static class JsonFormatterTypedFactoryExtensions + /// + /// JSON request extension methods for . + /// + public static class JsonFormatterTypedFactoryExtensions { - /// - /// Create a new HTTP request that expects and uses JSON as its primary format. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Json(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses JSON as its primary format. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Json(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectJson() - .UseJson(); - } + return + requestFactory.Create(requestUri) + .ExpectJson() + .UseJson(); + } - /// - /// Create a new HTTP request that expects and uses JSON as its primary format. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Json(this HttpRequestFactory requestFactory, Uri requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses JSON as its primary format. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Json(this HttpRequestFactory requestFactory, Uri requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectJson() - .UseJson(); - } - } + return + requestFactory.Create(requestUri) + .ExpectJson() + .UseJson(); + } + } } diff --git a/src/HTTPlease.Formatters.Xml/TypedFactoryExtensions.cs b/src/HTTPlease.Formatters.Xml/TypedFactoryExtensions.cs index 651f028..56fc3e3 100644 --- a/src/HTTPlease.Formatters.Xml/TypedFactoryExtensions.cs +++ b/src/HTTPlease.Formatters.Xml/TypedFactoryExtensions.cs @@ -2,67 +2,67 @@ namespace HTTPlease.Formatters.Xml { - /// - /// XML request extension methods for . - /// - public static class TypedFactoryExtensions + /// + /// XML request extension methods for . + /// + public static class TypedFactoryExtensions { - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest CreateXml(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest CreateXml(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXml(); - } + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXml(); + } - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest CreateXml(this HttpRequestFactory requestFactory, Uri requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest CreateXml(this HttpRequestFactory requestFactory, Uri requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXml(); - } - } + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXml(); + } + } } diff --git a/src/HTTPlease.Formatters.Xml/XmlFormatter.cs b/src/HTTPlease.Formatters.Xml/XmlFormatter.cs index 5273f7b..9a074e0 100644 --- a/src/HTTPlease.Formatters.Xml/XmlFormatter.cs +++ b/src/HTTPlease.Formatters.Xml/XmlFormatter.cs @@ -7,120 +7,120 @@ namespace HTTPlease.Formatters.Xml { - /// - /// Content formatter for XML. - /// - /// - /// Uses , so for CoreCLR you'll need to reference System.Runtime.Serialization.Primitives. - /// - public class XmlFormatter - : IInputFormatter, IOutputFormatter - { - /// - /// Create a new . - /// - public XmlFormatter() - { - } + /// + /// Content formatter for XML. + /// + /// + /// Uses , so for CoreCLR you'll need to reference System.Runtime.Serialization.Primitives. + /// + public class XmlFormatter + : IInputFormatter, IOutputFormatter + { + /// + /// Create a new . + /// + public XmlFormatter() + { + } - /// - /// Content types supported by the formatter. - /// - public ISet SupportedMediaTypes { get; } = new HashSet { WellKnownMediaTypes.Xml }; + /// + /// Content types supported by the formatter. + /// + public ISet SupportedMediaTypes { get; } = new HashSet { WellKnownMediaTypes.Xml }; - /// - /// Determine whether the formatter can deserialise the specified data. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// true, if the formatter can deserialise the data; otherwise, false. - /// - public bool CanRead(InputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Determine whether the formatter can deserialise the specified data. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// true, if the formatter can deserialise the data; otherwise, false. + /// + public bool CanRead(InputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - return SupportedMediaTypes.Contains(context.MediaType); - } + return SupportedMediaTypes.Contains(context.MediaType); + } - /// - /// Determine whether the formatter can serialise the specified data. - /// - /// - /// Contextual information about the data being serialised. - /// - /// - /// true, if the formatter can serialise the data; otherwise, false. - /// - public bool CanWrite(OutputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Determine whether the formatter can serialise the specified data. + /// + /// + /// Contextual information about the data being serialised. + /// + /// + /// true, if the formatter can serialise the data; otherwise, false. + /// + public bool CanWrite(OutputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - return SupportedMediaTypes.Contains(context.MediaType); - } + return SupportedMediaTypes.Contains(context.MediaType); + } - /// - /// Asynchronously deserialise data from an input stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The input stream from which to read serialised data. - /// - /// - /// The deserialised object. - /// - public Task ReadAsync(InputFormatterContext context, Stream stream) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Asynchronously deserialise data from an input stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The input stream from which to read serialised data. + /// + /// + /// The deserialised object. + /// + public Task ReadAsync(InputFormatterContext context, Stream stream) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); - using (XmlReader reader = XmlReader.Create(stream)) - { - DataContractSerializer serializer = new DataContractSerializer(context.DataType); - object data = serializer.ReadObject(reader); + using (XmlReader reader = XmlReader.Create(stream)) + { + DataContractSerializer serializer = new DataContractSerializer(context.DataType); + object data = serializer.ReadObject(reader); - return Task.FromResult(data); - } - } + return Task.FromResult(data); + } + } - /// - /// Asynchronously serialise data to an output stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The output stream to which the serialised data will be written. - /// - /// - /// A representing the asynchronous operation. - /// - public Task WriteAsync(OutputFormatterContext context, Stream stream) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Asynchronously serialise data to an output stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The output stream to which the serialised data will be written. + /// + /// + /// A representing the asynchronous operation. + /// + public Task WriteAsync(OutputFormatterContext context, Stream stream) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); - if (!SupportedMediaTypes.Contains(context.MediaType)) - throw new NotSupportedException($"The {nameof(XmlFormatter)} cannot write content of type '{context.MediaType}'."); + if (!SupportedMediaTypes.Contains(context.MediaType)) + throw new NotSupportedException($"The {nameof(XmlFormatter)} cannot write content of type '{context.MediaType}'."); - using (XmlWriter writer = XmlWriter.Create(stream)) - { - DataContractSerializer serializer = new DataContractSerializer(context.DataType); - serializer.WriteObject(writer, context.Data); + using (XmlWriter writer = XmlWriter.Create(stream)) + { + DataContractSerializer serializer = new DataContractSerializer(context.DataType); + serializer.WriteObject(writer, context.Data); - return Task.CompletedTask; - } - } - } + return Task.CompletedTask; + } + } + } } diff --git a/src/HTTPlease.Formatters.Xml/XmlFormatterExtensions.cs b/src/HTTPlease.Formatters.Xml/XmlFormatterExtensions.cs index 03a4068..19b0e25 100644 --- a/src/HTTPlease.Formatters.Xml/XmlFormatterExtensions.cs +++ b/src/HTTPlease.Formatters.Xml/XmlFormatterExtensions.cs @@ -2,31 +2,31 @@ namespace HTTPlease { - using Formatters; - using Formatters.Xml; - - /// - /// Extension methods for content formatters. - /// + using Formatters; + using Formatters.Xml; + + /// + /// Extension methods for content formatters. + /// public static class XmlFormatterExtensions { - /// - /// Add the XML content formatter. - /// - /// - /// The content formatter collection. - /// - /// - /// The content formatter collection (enables method-chaining). - /// - public static IFormatterCollection AddXmlFormatter(this IFormatterCollection formatters) - { - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); + /// + /// Add the XML content formatter. + /// + /// + /// The content formatter collection. + /// + /// + /// The content formatter collection (enables method-chaining). + /// + public static IFormatterCollection AddXmlFormatter(this IFormatterCollection formatters) + { + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); - formatters.Add(new XmlFormatter()); + formatters.Add(new XmlFormatter()); - return formatters; - } + return formatters; + } } } diff --git a/src/HTTPlease.Formatters.Xml/XmlFormatterFactoryExtensions.cs b/src/HTTPlease.Formatters.Xml/XmlFormatterFactoryExtensions.cs index 7d166a1..924b6d0 100644 --- a/src/HTTPlease.Formatters.Xml/XmlFormatterFactoryExtensions.cs +++ b/src/HTTPlease.Formatters.Xml/XmlFormatterFactoryExtensions.cs @@ -2,61 +2,61 @@ namespace HTTPlease { - /// - /// XML request extension methods for . - /// - public static class XmlFormatterFactoryExtensions + /// + /// XML request extension methods for . + /// + public static class XmlFormatterFactoryExtensions { - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Xml(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Xml(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXml(); - } - - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest Xml(this HttpRequestFactory requestFactory, Uri requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXml(); + } + + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest Xml(this HttpRequestFactory requestFactory, Uri requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXml(); - } - } + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXml(); + } + } } diff --git a/src/HTTPlease.Formatters.Xml/XmlFormatterRequestExtensions.cs b/src/HTTPlease.Formatters.Xml/XmlFormatterRequestExtensions.cs index 02eb58d..a3cf49d 100644 --- a/src/HTTPlease.Formatters.Xml/XmlFormatterRequestExtensions.cs +++ b/src/HTTPlease.Formatters.Xml/XmlFormatterRequestExtensions.cs @@ -2,52 +2,52 @@ namespace HTTPlease { - using Formatters.Xml; + using Formatters.Xml; - /// - /// Formatter-related extension methods for / . - /// - public static class XmlFormatterRequestExtensions - { - /// - /// Create a copy of the , configuring it to only use the XML formatters. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest UseXml(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Formatter-related extension methods for / . + /// + public static class XmlFormatterRequestExtensions + { + /// + /// Create a copy of the , configuring it to only use the XML formatters. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest UseXml(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - // TODO: Clear all existing formatters, first. + // TODO: Clear all existing formatters, first. - return request.WithFormatter(new XmlFormatter()); - } + return request.WithFormatter(new XmlFormatter()); + } - /// - /// Create a copy of the , configuring it to only use the XML formatters. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest UseXml(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to only use the XML formatters. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest UseXml(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - // TODO: Clear all existing formatters, first. + // TODO: Clear all existing formatters, first. - return request.WithFormatter(new XmlFormatter()); - } - } + return request.WithFormatter(new XmlFormatter()); + } + } } diff --git a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatter.cs b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatter.cs index f1228b9..bda094c 100644 --- a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatter.cs +++ b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatter.cs @@ -7,117 +7,117 @@ namespace HTTPlease.Formatters.Xml { - /// - /// Content formatter for XML that uses the . - /// - public class XmlSerializerFormatter - : IInputFormatter, IOutputFormatter - { - /// - /// Create a new . - /// - public XmlSerializerFormatter() - { - } + /// + /// Content formatter for XML that uses the . + /// + public class XmlSerializerFormatter + : IInputFormatter, IOutputFormatter + { + /// + /// Create a new . + /// + public XmlSerializerFormatter() + { + } - /// - /// Content types supported by the formatter. - /// - public ISet SupportedMediaTypes { get; } = new HashSet { WellKnownMediaTypes.Xml }; + /// + /// Content types supported by the formatter. + /// + public ISet SupportedMediaTypes { get; } = new HashSet { WellKnownMediaTypes.Xml }; - /// - /// Determine whether the formatter can deserialise the specified data. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// true, if the formatter can deserialise the data; otherwise, false. - /// - public bool CanRead(InputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Determine whether the formatter can deserialise the specified data. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// true, if the formatter can deserialise the data; otherwise, false. + /// + public bool CanRead(InputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - return SupportedMediaTypes.Contains(context.MediaType); - } + return SupportedMediaTypes.Contains(context.MediaType); + } - /// - /// Determine whether the formatter can serialise the specified data. - /// - /// - /// Contextual information about the data being serialised. - /// - /// - /// true, if the formatter can serialise the data; otherwise, false. - /// - public bool CanWrite(OutputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Determine whether the formatter can serialise the specified data. + /// + /// + /// Contextual information about the data being serialised. + /// + /// + /// true, if the formatter can serialise the data; otherwise, false. + /// + public bool CanWrite(OutputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - return SupportedMediaTypes.Contains(context.MediaType); - } + return SupportedMediaTypes.Contains(context.MediaType); + } - /// - /// Asynchronously deserialise data from an input stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The input stream from which to read serialised data. - /// - /// - /// The deserialised object. - /// - public Task ReadAsync(InputFormatterContext context, Stream stream) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Asynchronously deserialise data from an input stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The input stream from which to read serialised data. + /// + /// + /// The deserialised object. + /// + public Task ReadAsync(InputFormatterContext context, Stream stream) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); - using (XmlReader reader = XmlReader.Create(stream)) - { - XmlSerializer serializer = new XmlSerializer(context.DataType); - object data = serializer.Deserialize(reader); + using (XmlReader reader = XmlReader.Create(stream)) + { + XmlSerializer serializer = new XmlSerializer(context.DataType); + object data = serializer.Deserialize(reader); - return Task.FromResult(data); - } - } + return Task.FromResult(data); + } + } - /// - /// Asynchronously serialise data to an output stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The output stream to which the serialised data will be written. - /// - /// - /// A representing the asynchronous operation. - /// - public Task WriteAsync(OutputFormatterContext context, Stream stream) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + /// + /// Asynchronously serialise data to an output stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The output stream to which the serialised data will be written. + /// + /// + /// A representing the asynchronous operation. + /// + public Task WriteAsync(OutputFormatterContext context, Stream stream) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); - if (!SupportedMediaTypes.Contains(context.MediaType)) - throw new NotSupportedException($"The {nameof(XmlSerializerFormatter)} cannot write content of type '{context.MediaType}'."); + if (!SupportedMediaTypes.Contains(context.MediaType)) + throw new NotSupportedException($"The {nameof(XmlSerializerFormatter)} cannot write content of type '{context.MediaType}'."); - using (XmlWriter writer = XmlWriter.Create(stream)) - { - XmlSerializer serializer = new XmlSerializer(context.DataType); - serializer.Serialize(writer, context.Data); + using (XmlWriter writer = XmlWriter.Create(stream)) + { + XmlSerializer serializer = new XmlSerializer(context.DataType); + serializer.Serialize(writer, context.Data); - return Task.CompletedTask; - } - } - } + return Task.CompletedTask; + } + } + } } diff --git a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterExtensions.cs b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterExtensions.cs index 5046d16..f50dea4 100644 --- a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterExtensions.cs +++ b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterExtensions.cs @@ -2,31 +2,31 @@ namespace HTTPlease { - using Formatters; - using Formatters.Xml; + using Formatters; + using Formatters.Xml; - /// - /// Extension methods for content formatters. - /// + /// + /// Extension methods for content formatters. + /// public static class XmlSerializerFormatterExtensions { - /// - /// Add the XML serialiser content formatter. - /// - /// - /// The content formatter collection. - /// - /// - /// The content formatter collection (enables method-chaining). - /// - public static IFormatterCollection AddXmlSerializerFormatter(this IFormatterCollection formatters) - { - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); + /// + /// Add the XML serialiser content formatter. + /// + /// + /// The content formatter collection. + /// + /// + /// The content formatter collection (enables method-chaining). + /// + public static IFormatterCollection AddXmlSerializerFormatter(this IFormatterCollection formatters) + { + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); - formatters.Add(new XmlSerializerFormatter()); + formatters.Add(new XmlSerializerFormatter()); - return formatters; - } + return formatters; + } } } diff --git a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterFactoryExtensions.cs b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterFactoryExtensions.cs index 089da13..efafe78 100644 --- a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterFactoryExtensions.cs +++ b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterFactoryExtensions.cs @@ -2,61 +2,61 @@ namespace HTTPlease { - /// - /// XML serialiser request extension methods for . - /// - public static class XmlSerializerFormatterFactoryExtensions + /// + /// XML serialiser request extension methods for . + /// + public static class XmlSerializerFormatterFactoryExtensions { - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest XmlSerializer(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest XmlSerializer(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXmlSerializer(); - } - - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest XmlSerializer(this HttpRequestFactory requestFactory, Uri requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXmlSerializer(); + } + + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest XmlSerializer(this HttpRequestFactory requestFactory, Uri requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXmlSerializer(); - } - } + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXmlSerializer(); + } + } } diff --git a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterRequestExtensions.cs b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterRequestExtensions.cs index 902fc30..878dd2e 100644 --- a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterRequestExtensions.cs +++ b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterRequestExtensions.cs @@ -2,52 +2,52 @@ namespace HTTPlease { - using Formatters.Xml; + using Formatters.Xml; - /// - /// Formatter-related extension methods for / . - /// - public static class XmlSerializerFormatterRequestExtensions - { - /// - /// Create a copy of the , configuring it to only use the . - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest UseXmlSerializer(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Formatter-related extension methods for / . + /// + public static class XmlSerializerFormatterRequestExtensions + { + /// + /// Create a copy of the , configuring it to only use the . + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest UseXmlSerializer(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - // TODO: Clear all existing formatters, first. + // TODO: Clear all existing formatters, first. - return request.WithFormatter(new XmlSerializerFormatter()); - } + return request.WithFormatter(new XmlSerializerFormatter()); + } - /// - /// Create a copy of the , configuring it to only use the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest UseXmlSerializer(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to only use the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest UseXmlSerializer(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - // TODO: Clear all existing formatters, first. + // TODO: Clear all existing formatters, first. - return request.WithFormatter(new XmlSerializerFormatter()); - } - } + return request.WithFormatter(new XmlSerializerFormatter()); + } + } } diff --git a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterTypedFactoryExtensions.cs b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterTypedFactoryExtensions.cs index 78500c6..882526e 100644 --- a/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterTypedFactoryExtensions.cs +++ b/src/HTTPlease.Formatters.XmlSerializer/XmlSerializerFormatterTypedFactoryExtensions.cs @@ -2,67 +2,67 @@ namespace HTTPlease { - /// - /// XML request extension methods for . - /// - public static class XmlSerializerFormatterTypedFactoryExtensions + /// + /// XML request extension methods for . + /// + public static class XmlSerializerFormatterTypedFactoryExtensions { - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest CreateXmlSerializer(this HttpRequestFactory requestFactory, string requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest CreateXmlSerializer(this HttpRequestFactory requestFactory, string requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (String.IsNullOrWhiteSpace(requestUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); + if (String.IsNullOrWhiteSpace(requestUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'requestUri'.", nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXmlSerializer(); - } + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXmlSerializer(); + } - /// - /// Create a new HTTP request that expects and uses XML as its primary format. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The HTTP request factory. - /// - /// - /// The request URI (can be relative or absolute). - /// - /// - /// The new . - /// - public static HttpRequest CreateXmlSerializer(this HttpRequestFactory requestFactory, Uri requestUri) - { - if (requestFactory == null) - throw new ArgumentNullException(nameof(requestFactory)); + /// + /// Create a new HTTP request that expects and uses XML as its primary format. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The HTTP request factory. + /// + /// + /// The request URI (can be relative or absolute). + /// + /// + /// The new . + /// + public static HttpRequest CreateXmlSerializer(this HttpRequestFactory requestFactory, Uri requestUri) + { + if (requestFactory == null) + throw new ArgumentNullException(nameof(requestFactory)); - if (requestUri == null) - throw new ArgumentNullException(nameof(requestUri)); + if (requestUri == null) + throw new ArgumentNullException(nameof(requestUri)); - return - requestFactory.Create(requestUri) - .ExpectXml() - .UseXmlSerializer(); - } - } + return + requestFactory.Create(requestUri) + .ExpectXml() + .UseXmlSerializer(); + } + } } diff --git a/src/HTTPlease.Formatters/ContentExtensions.cs b/src/HTTPlease.Formatters/ContentExtensions.cs index b571c7a..35bf32e 100644 --- a/src/HTTPlease.Formatters/ContentExtensions.cs +++ b/src/HTTPlease.Formatters/ContentExtensions.cs @@ -7,101 +7,101 @@ namespace HTTPlease.Formatters { - /// - /// Extension methods for working with . - /// - public static class ContentExtensions + /// + /// Extension methods for working with . + /// + public static class ContentExtensions { - /// - /// Asynchronously read the body content as the specified type. - /// - /// - /// The CLR data-type that the body content will be deserialised into. - /// - /// - /// The to read. - /// - /// - /// The content formatter used to deserialise the body content. - /// - /// - /// The deserialised body content. - /// - public static Task ReadAsAsync(this HttpContent content, IInputFormatter formatter) - { - if (content == null) - throw new ArgumentNullException(nameof(content)); + /// + /// Asynchronously read the body content as the specified type. + /// + /// + /// The CLR data-type that the body content will be deserialised into. + /// + /// + /// The to read. + /// + /// + /// The content formatter used to deserialise the body content. + /// + /// + /// The deserialised body content. + /// + public static Task ReadAsAsync(this HttpContent content, IInputFormatter formatter) + { + if (content == null) + throw new ArgumentNullException(nameof(content)); - InputFormatterContext formatterContext = content.CreateInputFormatterContext(); + InputFormatterContext formatterContext = content.CreateInputFormatterContext(); - return content.ReadAsAsync(formatter, formatterContext); - } + return content.ReadAsAsync(formatter, formatterContext); + } - /// - /// Asynchronously read the body content as the specified type. - /// - /// - /// The CLR data-type that the body content will be deserialised into. - /// - /// - /// The to read. - /// - /// - /// The content formatter used to deserialise the body content. - /// - /// - /// Contextual information about the content body being deserialised. - /// - /// - /// The deserialised body content. - /// - public static async Task ReadAsAsync(this HttpContent content, IInputFormatter formatter, InputFormatterContext formatterContext) - { - if (content == null) - throw new ArgumentNullException(nameof(content)); + /// + /// Asynchronously read the body content as the specified type. + /// + /// + /// The CLR data-type that the body content will be deserialised into. + /// + /// + /// The to read. + /// + /// + /// The content formatter used to deserialise the body content. + /// + /// + /// Contextual information about the content body being deserialised. + /// + /// + /// The deserialised body content. + /// + public static async Task ReadAsAsync(this HttpContent content, IInputFormatter formatter, InputFormatterContext formatterContext) + { + if (content == null) + throw new ArgumentNullException(nameof(content)); - if (formatterContext == null) - throw new ArgumentNullException(nameof(formatterContext)); + if (formatterContext == null) + throw new ArgumentNullException(nameof(formatterContext)); - using (Stream responseStream = await content.ReadAsStreamAsync().ConfigureAwait(false)) - { - object responseBody = await formatter.ReadAsync(formatterContext, responseStream).ConfigureAwait(false); + using (Stream responseStream = await content.ReadAsStreamAsync().ConfigureAwait(false)) + { + object responseBody = await formatter.ReadAsync(formatterContext, responseStream).ConfigureAwait(false); - return (TBody)responseBody; - } - } + return (TBody)responseBody; + } + } - /// - /// Create an for reading the HTTP message content. - /// - /// - /// The CLR data type into which the message body will be deserialised. - /// - /// - /// The HTTP message content. - /// - /// - /// The configured . - /// - public static InputFormatterContext CreateInputFormatterContext(this HttpContent content) - { - if (content == null) - throw new ArgumentNullException(nameof(content)); + /// + /// Create an for reading the HTTP message content. + /// + /// + /// The CLR data type into which the message body will be deserialised. + /// + /// + /// The HTTP message content. + /// + /// + /// The configured . + /// + public static InputFormatterContext CreateInputFormatterContext(this HttpContent content) + { + if (content == null) + throw new ArgumentNullException(nameof(content)); - MediaTypeHeaderValue contentTypeHeader = content.Headers.ContentType; - if (contentTypeHeader == null) - throw new InvalidOperationException("Response is missing 'Content-Type' header."); // TODO: Consider custom exception type. + MediaTypeHeaderValue contentTypeHeader = content.Headers.ContentType; + if (contentTypeHeader == null) + throw new InvalidOperationException("Response is missing 'Content-Type' header."); // TODO: Consider custom exception type. - Encoding encoding = !String.IsNullOrWhiteSpace(contentTypeHeader.CharSet) ? - Encoding.GetEncoding(contentTypeHeader.CharSet) - : - Encoding.UTF8; + Encoding encoding = !String.IsNullOrWhiteSpace(contentTypeHeader.CharSet) ? + Encoding.GetEncoding(contentTypeHeader.CharSet) + : + Encoding.UTF8; - return new InputFormatterContext( - dataType: typeof(TBody), - mediaType: contentTypeHeader.MediaType, - encoding: encoding - ); - } - } + return new InputFormatterContext( + dataType: typeof(TBody), + mediaType: contentTypeHeader.MediaType, + encoding: encoding + ); + } + } } diff --git a/src/HTTPlease.Formatters/EncodingWithoutPreamble.cs b/src/HTTPlease.Formatters/EncodingWithoutPreamble.cs index d00e50a..bb39a46 100644 --- a/src/HTTPlease.Formatters/EncodingWithoutPreamble.cs +++ b/src/HTTPlease.Formatters/EncodingWithoutPreamble.cs @@ -2,22 +2,22 @@ namespace HTTPlease { - /// - /// Well-known text encodings for output (i.see. no preambles). - /// - /// - /// Some web APIs don't like being sent preambles to indicate text encoding (usually indicated by HTTP headers instead). - /// - public static class OutputEncoding - { - /// - /// UTF-8 encoding (no preamble). - /// - public static readonly Encoding UTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + /// + /// Well-known text encodings for output (i.see. no preambles). + /// + /// + /// Some web APIs don't like being sent preambles to indicate text encoding (usually indicated by HTTP headers instead). + /// + public static class OutputEncoding + { + /// + /// UTF-8 encoding (no preamble). + /// + public static readonly Encoding UTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - /// - /// Unicode / UTF-16 encoding (no preamble). - /// - public static readonly Encoding Unicode = new UnicodeEncoding(bigEndian: false, byteOrderMark: false); - } + /// + /// Unicode / UTF-16 encoding (no preamble). + /// + public static readonly Encoding Unicode = new UnicodeEncoding(bigEndian: false, byteOrderMark: false); + } } \ No newline at end of file diff --git a/src/HTTPlease.Formatters/FormattedObjectContent.cs b/src/HTTPlease.Formatters/FormattedObjectContent.cs index a6f6b39..1916c51 100644 --- a/src/HTTPlease.Formatters/FormattedObjectContent.cs +++ b/src/HTTPlease.Formatters/FormattedObjectContent.cs @@ -8,154 +8,154 @@ namespace HTTPlease.Formatters { - /// - /// HTTP content formatted using an . - /// - public class FormattedObjectContent - : HttpContent - { - /// - /// The default encoding used by . - /// - public static readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - - /// - /// Create new formatted object content. - /// - /// - /// The that will be used to serialise the data. - /// - /// - /// The data that will be serialised to form the content. - /// - /// - /// The type of data that will be serialised to form the content. - /// - /// - /// The content type being serialised. - /// - /// - /// Uses UTF-8 encoding. - /// - public FormattedObjectContent(IOutputFormatter formatter, Type dataType, object data, string mediaType) - : this(formatter, data, dataType, mediaType, DefaultEncoding) - { - } - - /// - /// Create new formatted object content. - /// - /// - /// The that will be used to serialise the data. - /// - /// - /// The data that will be serialised to form the content. - /// - /// - /// The type of data that will be serialised to form the content. - /// - /// - /// The media type that the formatter should produce. - /// - /// - /// The that the formatter should use for serialised data. - /// - public FormattedObjectContent(IOutputFormatter formatter, object data, Type dataType, string mediaType, Encoding encoding) - : this(formatter, new OutputFormatterContext(data, dataType, mediaType, encoding)) - { - } - - /// - /// Create new formatted object content. - /// - /// - /// The that will be used to serialise the data. - /// - /// - /// Contextual information use by the . - /// - public FormattedObjectContent(IOutputFormatter formatter, OutputFormatterContext formatterContext) - { - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - if (formatterContext == null) - throw new ArgumentNullException(nameof(formatterContext)); - - Formatter = formatter; - FormatterContext = formatterContext; - Headers.ContentType = new MediaTypeHeaderValue(MediaType); - } - - /// - /// The that will be used to serialise the data. - /// - public IOutputFormatter Formatter { get; } - - /// - /// Contextual information use by the . - /// - public OutputFormatterContext FormatterContext { get; } - - /// - /// The type of data that will be serialised to form the content. - /// - public Type DataType => FormatterContext.DataType; - - /// - /// The data that will be serialised to form the content. - /// - public object Data => FormatterContext.Data; - - /// - /// The media type that the formatter should produce. - /// - public string MediaType => FormatterContext.MediaType; - - /// - /// The that the formatter should use for serialised data. - /// - public Encoding Encoding => FormatterContext.Encoding; - - /// - /// Try to pre-compute the formatted content length. - /// - /// - /// The length (in bytes) of the content. - /// - /// Always -1, since length is not known before serialisation. - /// - /// - /// false. - /// - protected override bool TryComputeLength(out long length) - { - // We don't know the length in advance. - length = -1; - - return false; - } - - /// - /// Serialize the HTTP content to a stream as an asynchronous operation. - /// - /// - /// The target stream. - /// - /// - /// Information about the transport (channel binding token, for example). - /// - /// Can be null. - /// - /// - /// Returns .The task object representing the asynchronous operation. - /// - protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - - await Formatter.WriteAsync(FormatterContext, stream); - } - } + /// + /// HTTP content formatted using an . + /// + public class FormattedObjectContent + : HttpContent + { + /// + /// The default encoding used by . + /// + public static readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + /// + /// Create new formatted object content. + /// + /// + /// The that will be used to serialise the data. + /// + /// + /// The data that will be serialised to form the content. + /// + /// + /// The type of data that will be serialised to form the content. + /// + /// + /// The content type being serialised. + /// + /// + /// Uses UTF-8 encoding. + /// + public FormattedObjectContent(IOutputFormatter formatter, Type dataType, object data, string mediaType) + : this(formatter, data, dataType, mediaType, DefaultEncoding) + { + } + + /// + /// Create new formatted object content. + /// + /// + /// The that will be used to serialise the data. + /// + /// + /// The data that will be serialised to form the content. + /// + /// + /// The type of data that will be serialised to form the content. + /// + /// + /// The media type that the formatter should produce. + /// + /// + /// The that the formatter should use for serialised data. + /// + public FormattedObjectContent(IOutputFormatter formatter, object data, Type dataType, string mediaType, Encoding encoding) + : this(formatter, new OutputFormatterContext(data, dataType, mediaType, encoding)) + { + } + + /// + /// Create new formatted object content. + /// + /// + /// The that will be used to serialise the data. + /// + /// + /// Contextual information use by the . + /// + public FormattedObjectContent(IOutputFormatter formatter, OutputFormatterContext formatterContext) + { + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + if (formatterContext == null) + throw new ArgumentNullException(nameof(formatterContext)); + + Formatter = formatter; + FormatterContext = formatterContext; + Headers.ContentType = new MediaTypeHeaderValue(MediaType); + } + + /// + /// The that will be used to serialise the data. + /// + public IOutputFormatter Formatter { get; } + + /// + /// Contextual information use by the . + /// + public OutputFormatterContext FormatterContext { get; } + + /// + /// The type of data that will be serialised to form the content. + /// + public Type DataType => FormatterContext.DataType; + + /// + /// The data that will be serialised to form the content. + /// + public object Data => FormatterContext.Data; + + /// + /// The media type that the formatter should produce. + /// + public string MediaType => FormatterContext.MediaType; + + /// + /// The that the formatter should use for serialised data. + /// + public Encoding Encoding => FormatterContext.Encoding; + + /// + /// Try to pre-compute the formatted content length. + /// + /// + /// The length (in bytes) of the content. + /// + /// Always -1, since length is not known before serialisation. + /// + /// + /// false. + /// + protected override bool TryComputeLength(out long length) + { + // We don't know the length in advance. + length = -1; + + return false; + } + + /// + /// Serialize the HTTP content to a stream as an asynchronous operation. + /// + /// + /// The target stream. + /// + /// + /// Information about the transport (channel binding token, for example). + /// + /// Can be null. + /// + /// + /// Returns .The task object representing the asynchronous operation. + /// + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + await Formatter.WriteAsync(FormatterContext, stream); + } + } } diff --git a/src/HTTPlease.Formatters/FormatterClientExtensions.cs b/src/HTTPlease.Formatters/FormatterClientExtensions.cs index 9b7b663..09ab716 100644 --- a/src/HTTPlease.Formatters/FormatterClientExtensions.cs +++ b/src/HTTPlease.Formatters/FormatterClientExtensions.cs @@ -5,243 +5,243 @@ namespace HTTPlease { - using Formatters; - - /// - /// Extension methods for invocation of untyped s using an . - /// - public static class FormatterClientExtensions - { - /// - /// Asynchronously execute a request as an HTTP POST. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional object to be used as the the request body. - /// - /// - /// If is specified, the media type to be used - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PostAsync(this HttpClient httpClient, HttpRequest request, object postBody, string mediaType, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Post, postBody, mediaType, baseUri: httpClient.BaseAddress)) - { - return await httpClient.SendAsync(requestMessage, cancellationToken); - } - } - - /// - /// Asynchronously perform an HTTP POST request, serialising the request to JSON. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The object that will be serialised into the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the operation. - /// - /// - /// A representing the asynchronous request, whose result is the response message. - /// - public static Task PostAsJsonAsync(this HttpClient httpClient, HttpRequest request, object postBody, CancellationToken cancellationToken = default) - { - return httpClient.PostAsync(request, postBody, WellKnownMediaTypes.Json, cancellationToken); - } - - /// - /// Asynchronously execute a request as an HTTP PUT. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional object to be used as the the request body. - /// - /// - /// If is specified, the media type to be used - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PutAsync(this HttpClient httpClient, HttpRequest request, object putBody, string mediaType, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Put, putBody, mediaType, baseUri: httpClient.BaseAddress)) - { - return await httpClient.SendAsync(requestMessage, cancellationToken); - } - } - - /// - /// Asynchronously perform an HTTP PUT request, serialising the request to JSON. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The object that will be serialised into the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the operation. - /// - /// - /// A representing the asynchronous request, whose result is the response message. - /// - public static Task PutAsJsonAsync(this HttpClient httpClient, HttpRequest request, object putBody, CancellationToken cancellationToken = default) - { - return httpClient.PutAsync(request, putBody, WellKnownMediaTypes.Json, cancellationToken); - } - - /// - /// Asynchronously execute a request as an HTTP PATCH. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional object to be used as the the request body. - /// - /// - /// If is specified, the media type to be used - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task PatchAsync(this HttpClient httpClient, HttpRequest request, object patchBody, string mediaType, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(OtherHttpMethods.Patch, patchBody, mediaType, baseUri: httpClient.BaseAddress)) - { - return await httpClient.SendAsync(requestMessage, cancellationToken); - } - } - - /// - /// Asynchronously perform an HTTP PATCH request, serialising the request to JSON. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// The object that will be serialised into the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the operation. - /// - /// - /// A representing the asynchronous request, whose result is the response message. - /// - public static Task PatchAsJsonAsync(this HttpClient httpClient, HttpRequest request, object patchBody, CancellationToken cancellationToken = default) - { - return httpClient.PatchAsync(request, patchBody, WellKnownMediaTypes.Json, cancellationToken); - } - - /// - /// Asynchronously execute a request as an HTTP DELETE. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional object to be used as the the request body. - /// - /// - /// If is specified, the media type to be used - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static async Task DeleteAsync(this HttpClient httpClient, HttpRequest request, object deleteBody, string mediaType, CancellationToken cancellationToken = default) - { - if (httpClient == null) - throw new ArgumentNullException(nameof(httpClient)); - - if (request == null) - throw new ArgumentNullException(nameof(request)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Delete, deleteBody, mediaType, baseUri: httpClient.BaseAddress)) - { - return await httpClient.SendAsync(requestMessage, cancellationToken); - } - } - - /// - /// Asynchronously execute a request as an HTTP DELETE, serialising the request to JSON. - /// - /// - /// The used to execute the request. - /// - /// - /// The HTTP request. - /// - /// - /// An optional object to be used as the the request body. - /// - /// - /// An optional cancellation token that can be used to cancel the asynchronous operation. - /// - /// - /// An representing the response. - /// - public static Task DeleteAsJsonAsync(this HttpClient httpClient, HttpRequest request, object deleteBody, CancellationToken cancellationToken = default) - { - return httpClient.DeleteAsync(request, deleteBody, WellKnownMediaTypes.Json, cancellationToken); - } - } + using Formatters; + + /// + /// Extension methods for invocation of untyped s using an . + /// + public static class FormatterClientExtensions + { + /// + /// Asynchronously execute a request as an HTTP POST. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional object to be used as the the request body. + /// + /// + /// If is specified, the media type to be used + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PostAsync(this HttpClient httpClient, HttpRequest request, object postBody, string mediaType, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Post, postBody, mediaType, baseUri: httpClient.BaseAddress)) + { + return await httpClient.SendAsync(requestMessage, cancellationToken); + } + } + + /// + /// Asynchronously perform an HTTP POST request, serialising the request to JSON. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The object that will be serialised into the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the operation. + /// + /// + /// A representing the asynchronous request, whose result is the response message. + /// + public static Task PostAsJsonAsync(this HttpClient httpClient, HttpRequest request, object postBody, CancellationToken cancellationToken = default) + { + return httpClient.PostAsync(request, postBody, WellKnownMediaTypes.Json, cancellationToken); + } + + /// + /// Asynchronously execute a request as an HTTP PUT. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional object to be used as the the request body. + /// + /// + /// If is specified, the media type to be used + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PutAsync(this HttpClient httpClient, HttpRequest request, object putBody, string mediaType, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Put, putBody, mediaType, baseUri: httpClient.BaseAddress)) + { + return await httpClient.SendAsync(requestMessage, cancellationToken); + } + } + + /// + /// Asynchronously perform an HTTP PUT request, serialising the request to JSON. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The object that will be serialised into the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the operation. + /// + /// + /// A representing the asynchronous request, whose result is the response message. + /// + public static Task PutAsJsonAsync(this HttpClient httpClient, HttpRequest request, object putBody, CancellationToken cancellationToken = default) + { + return httpClient.PutAsync(request, putBody, WellKnownMediaTypes.Json, cancellationToken); + } + + /// + /// Asynchronously execute a request as an HTTP PATCH. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional object to be used as the the request body. + /// + /// + /// If is specified, the media type to be used + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task PatchAsync(this HttpClient httpClient, HttpRequest request, object patchBody, string mediaType, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(OtherHttpMethods.Patch, patchBody, mediaType, baseUri: httpClient.BaseAddress)) + { + return await httpClient.SendAsync(requestMessage, cancellationToken); + } + } + + /// + /// Asynchronously perform an HTTP PATCH request, serialising the request to JSON. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// The object that will be serialised into the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the operation. + /// + /// + /// A representing the asynchronous request, whose result is the response message. + /// + public static Task PatchAsJsonAsync(this HttpClient httpClient, HttpRequest request, object patchBody, CancellationToken cancellationToken = default) + { + return httpClient.PatchAsync(request, patchBody, WellKnownMediaTypes.Json, cancellationToken); + } + + /// + /// Asynchronously execute a request as an HTTP DELETE. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional object to be used as the the request body. + /// + /// + /// If is specified, the media type to be used + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static async Task DeleteAsync(this HttpClient httpClient, HttpRequest request, object deleteBody, string mediaType, CancellationToken cancellationToken = default) + { + if (httpClient == null) + throw new ArgumentNullException(nameof(httpClient)); + + if (request == null) + throw new ArgumentNullException(nameof(request)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Delete, deleteBody, mediaType, baseUri: httpClient.BaseAddress)) + { + return await httpClient.SendAsync(requestMessage, cancellationToken); + } + } + + /// + /// Asynchronously execute a request as an HTTP DELETE, serialising the request to JSON. + /// + /// + /// The used to execute the request. + /// + /// + /// The HTTP request. + /// + /// + /// An optional object to be used as the the request body. + /// + /// + /// An optional cancellation token that can be used to cancel the asynchronous operation. + /// + /// + /// An representing the response. + /// + public static Task DeleteAsJsonAsync(this HttpClient httpClient, HttpRequest request, object deleteBody, CancellationToken cancellationToken = default) + { + return httpClient.DeleteAsync(request, deleteBody, WellKnownMediaTypes.Json, cancellationToken); + } + } } diff --git a/src/HTTPlease.Formatters/FormatterCollection.cs b/src/HTTPlease.Formatters/FormatterCollection.cs index 5040972..345dc7b 100644 --- a/src/HTTPlease.Formatters/FormatterCollection.cs +++ b/src/HTTPlease.Formatters/FormatterCollection.cs @@ -5,280 +5,280 @@ namespace HTTPlease.Formatters { - /// - /// A collection of content formatters. - /// - public class FormatterCollection - : IFormatterCollection + /// + /// A collection of content formatters. + /// + public class FormatterCollection + : IFormatterCollection { - #region Instance data - - /// - /// The underlying collection of formatters, keyed by type. - /// - readonly Dictionary _formatters = new Dictionary(); - - #endregion // Instance data - - #region Construction - - /// - /// Create a new . - /// - public FormatterCollection() - { - } - - /// - /// Create a new by copying the specified . - /// - /// - /// The to copy. - /// - public FormatterCollection(FormatterCollection formatterCollection) - { - if (formatterCollection == null) - throw new ArgumentNullException(nameof(formatterCollection)); - - foreach (KeyValuePair formatter in formatterCollection._formatters) - _formatters.Add(formatter.Key, formatter.Value); - } - - /// - /// Create a new . - /// - /// - /// The formatters that the collection will initially contain. - /// - public FormatterCollection(IEnumerable formatters) - { - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); - - foreach (IFormatter formatter in formatters) - Add(formatter); - } - - /// - /// Create a new . - /// - /// - /// The formatters that the collection will initially contain. - /// - public FormatterCollection(params IFormatter[] formatters) - : this((IEnumerable)formatters) - { - } - - #endregion // Construction - - #region Collection - - /// - /// The number of formatters in the collection. - /// - public int Count => _formatters.Count; - - /// - /// Add a formatter to the collection. - /// - /// - /// The formatter to add. - /// - public void Add(IFormatter formatter) - { - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - Type formatterType = formatter.GetType(); - if (_formatters.ContainsKey(formatterType)) - throw new InvalidOperationException($"The collection already contains a formatter of type '{formatterType.FullName}'."); - - _formatters.Add(formatterType, formatter); - } - - /// - /// Determine whether the collection contains the specified formatter instance. - /// - /// - /// The formatter. - /// - /// - /// true, if the collection contains the formatter; otherwise, false. - /// - public bool Contains(IFormatter formatter) - { - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - Type formatterType = formatter.GetType(); - - return _formatters.ContainsKey(formatterType); - } - - /// - /// Determine whether the collection contains a formatter of the specified type. - /// - /// - /// The formatter type. - /// - /// - /// true, if the collection contains a formatter of the specified type; otherwise, false. - /// - public bool Contains(Type formatterType) - { - if (formatterType == null) - throw new ArgumentNullException(nameof(formatterType)); - - return _formatters.ContainsKey(formatterType); - } - - /// - /// Remove the specified formatter (if it is present in the collection). - /// - /// - /// The formatter to remove. - /// - /// - /// true, if the formatter was removed; otherwise, false. - /// - public bool Remove(IFormatter formatter) - { - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - Type formatterType = formatter.GetType(); - - return _formatters.Remove(formatterType); - } - - /// - /// Remove the formatter of the specified type (if it is present in the collection). - /// - /// - /// The type of formatter to remove. - /// - /// - /// true, if the formatter was removed; otherwise, false. - /// - public bool Remove(Type formatterType) - { - if (formatterType == null) - throw new ArgumentNullException(nameof(formatterType)); - - return _formatters.Remove(formatterType); - } - - /// - /// Remove all formatters from the collection. - /// - public void Clear() - { - _formatters.Clear(); - } - - #endregion // Collection - - #region Find a formatter - - /// - /// Get the most appropriate formatter to read the specified data. - /// - /// - /// Contextual information about the data to deserialise. - /// - /// - /// The formatter, or null if none of the formatters in the collection can handle the specified content type. - /// - /// - /// is null. - /// - public IInputFormatter FindInputFormatter(InputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return - _formatters.Values - .OfType() - .FirstOrDefault(formatter => formatter.CanRead(context)); - } - - /// - /// Find the most appropriate formatter to write the specified data. - /// - /// - /// Contextual information about the data to deserialise. - /// - /// - /// The formatter, or null if none of the formatters in the collection can handle the specified content type. - /// - /// - /// is null. - /// - public IOutputFormatter FindOutputFormatter(OutputFormatterContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - return - _formatters.Values - .OfType() - .FirstOrDefault(formatter => formatter.CanWrite(context)); - } - - #endregion // Find a formatter - - #region IEnumerable - - /// - /// Get a typed enumerator for the formatters in the collection. - /// - /// - /// The enumerator. - /// - public IEnumerator GetEnumerator() - { - return _formatters.Values.GetEnumerator(); - } - - /// - /// Get an untyped enumerator for the formatters in the collection. - /// - /// - /// The enumerator. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion // IEnumerable - - #region ICollection - - /// - /// Is the collection read-only? - /// - bool ICollection.IsReadOnly => false; - - /// - /// Copy the formatters in the collection to an array. - /// - /// - /// The destination array. - /// - /// - /// The starting index in the destination array. - /// - public void CopyTo(IFormatter[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - - _formatters.Values.CopyTo(array, arrayIndex); - } - - #endregion // ICollection - } + #region Instance data + + /// + /// The underlying collection of formatters, keyed by type. + /// + readonly Dictionary _formatters = new Dictionary(); + + #endregion // Instance data + + #region Construction + + /// + /// Create a new . + /// + public FormatterCollection() + { + } + + /// + /// Create a new by copying the specified . + /// + /// + /// The to copy. + /// + public FormatterCollection(FormatterCollection formatterCollection) + { + if (formatterCollection == null) + throw new ArgumentNullException(nameof(formatterCollection)); + + foreach (KeyValuePair formatter in formatterCollection._formatters) + _formatters.Add(formatter.Key, formatter.Value); + } + + /// + /// Create a new . + /// + /// + /// The formatters that the collection will initially contain. + /// + public FormatterCollection(IEnumerable formatters) + { + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); + + foreach (IFormatter formatter in formatters) + Add(formatter); + } + + /// + /// Create a new . + /// + /// + /// The formatters that the collection will initially contain. + /// + public FormatterCollection(params IFormatter[] formatters) + : this((IEnumerable)formatters) + { + } + + #endregion // Construction + + #region Collection + + /// + /// The number of formatters in the collection. + /// + public int Count => _formatters.Count; + + /// + /// Add a formatter to the collection. + /// + /// + /// The formatter to add. + /// + public void Add(IFormatter formatter) + { + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + Type formatterType = formatter.GetType(); + if (_formatters.ContainsKey(formatterType)) + throw new InvalidOperationException($"The collection already contains a formatter of type '{formatterType.FullName}'."); + + _formatters.Add(formatterType, formatter); + } + + /// + /// Determine whether the collection contains the specified formatter instance. + /// + /// + /// The formatter. + /// + /// + /// true, if the collection contains the formatter; otherwise, false. + /// + public bool Contains(IFormatter formatter) + { + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + Type formatterType = formatter.GetType(); + + return _formatters.ContainsKey(formatterType); + } + + /// + /// Determine whether the collection contains a formatter of the specified type. + /// + /// + /// The formatter type. + /// + /// + /// true, if the collection contains a formatter of the specified type; otherwise, false. + /// + public bool Contains(Type formatterType) + { + if (formatterType == null) + throw new ArgumentNullException(nameof(formatterType)); + + return _formatters.ContainsKey(formatterType); + } + + /// + /// Remove the specified formatter (if it is present in the collection). + /// + /// + /// The formatter to remove. + /// + /// + /// true, if the formatter was removed; otherwise, false. + /// + public bool Remove(IFormatter formatter) + { + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + Type formatterType = formatter.GetType(); + + return _formatters.Remove(formatterType); + } + + /// + /// Remove the formatter of the specified type (if it is present in the collection). + /// + /// + /// The type of formatter to remove. + /// + /// + /// true, if the formatter was removed; otherwise, false. + /// + public bool Remove(Type formatterType) + { + if (formatterType == null) + throw new ArgumentNullException(nameof(formatterType)); + + return _formatters.Remove(formatterType); + } + + /// + /// Remove all formatters from the collection. + /// + public void Clear() + { + _formatters.Clear(); + } + + #endregion // Collection + + #region Find a formatter + + /// + /// Get the most appropriate formatter to read the specified data. + /// + /// + /// Contextual information about the data to deserialise. + /// + /// + /// The formatter, or null if none of the formatters in the collection can handle the specified content type. + /// + /// + /// is null. + /// + public IInputFormatter FindInputFormatter(InputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return + _formatters.Values + .OfType() + .FirstOrDefault(formatter => formatter.CanRead(context)); + } + + /// + /// Find the most appropriate formatter to write the specified data. + /// + /// + /// Contextual information about the data to deserialise. + /// + /// + /// The formatter, or null if none of the formatters in the collection can handle the specified content type. + /// + /// + /// is null. + /// + public IOutputFormatter FindOutputFormatter(OutputFormatterContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return + _formatters.Values + .OfType() + .FirstOrDefault(formatter => formatter.CanWrite(context)); + } + + #endregion // Find a formatter + + #region IEnumerable + + /// + /// Get a typed enumerator for the formatters in the collection. + /// + /// + /// The enumerator. + /// + public IEnumerator GetEnumerator() + { + return _formatters.Values.GetEnumerator(); + } + + /// + /// Get an untyped enumerator for the formatters in the collection. + /// + /// + /// The enumerator. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion // IEnumerable + + #region ICollection + + /// + /// Is the collection read-only? + /// + bool ICollection.IsReadOnly => false; + + /// + /// Copy the formatters in the collection to an array. + /// + /// + /// The destination array. + /// + /// + /// The starting index in the destination array. + /// + public void CopyTo(IFormatter[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + _formatters.Values.CopyTo(array, arrayIndex); + } + + #endregion // ICollection + } } diff --git a/src/HTTPlease.Formatters/FormatterCollectionExtensions.cs b/src/HTTPlease.Formatters/FormatterCollectionExtensions.cs index 6765d91..2948388 100644 --- a/src/HTTPlease.Formatters/FormatterCollectionExtensions.cs +++ b/src/HTTPlease.Formatters/FormatterCollectionExtensions.cs @@ -2,30 +2,30 @@ namespace HTTPlease.Formatters { - /// - /// Extension methods for working with s. - /// + /// + /// Extension methods for working with s. + /// public static class FormatterCollectionExtensions { - /// - /// Remove the formatter of the specified type from the collection (if it is present). - /// - /// - /// The type of formatter to remove. - /// - /// - /// The formatter to remove. - /// - /// - /// true, if the formatter was removed; otherwise, false. - /// - public static bool Remove(this IFormatterCollection formatters) - where TFormatter : IFormatter - { - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); + /// + /// Remove the formatter of the specified type from the collection (if it is present). + /// + /// + /// The type of formatter to remove. + /// + /// + /// The formatter to remove. + /// + /// + /// true, if the formatter was removed; otherwise, false. + /// + public static bool Remove(this IFormatterCollection formatters) + where TFormatter : IFormatter + { + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); - return formatters.Remove(typeof(TFormatter)); - } + return formatters.Remove(typeof(TFormatter)); + } } } diff --git a/src/HTTPlease.Formatters/FormatterRequestExtensions.cs b/src/HTTPlease.Formatters/FormatterRequestExtensions.cs index 13c9e7f..7e8d9a2 100644 --- a/src/HTTPlease.Formatters/FormatterRequestExtensions.cs +++ b/src/HTTPlease.Formatters/FormatterRequestExtensions.cs @@ -5,271 +5,271 @@ namespace HTTPlease { - using Formatters; + using Formatters; - /// - /// Extension methods for working with s. - /// - public static class FormatterRequestExtensions + /// + /// Extension methods for working with s. + /// + public static class FormatterRequestExtensions { - /// - /// Create a copy of the , configuring it to accept the JSON ("application/json") media type. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest ExpectJson(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to accept the JSON ("application/json") media type. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest ExpectJson(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return request.AcceptMediaType(WellKnownMediaTypes.Json); - } + return request.AcceptMediaType(WellKnownMediaTypes.Json); + } - /// - /// Create a copy of the , configuring it to accept the JSON ("application/json") media type. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest ExpectJson(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to accept the JSON ("application/json") media type. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest ExpectJson(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return request.AcceptMediaType(WellKnownMediaTypes.Json); - } + return request.AcceptMediaType(WellKnownMediaTypes.Json); + } - /// - /// Create a copy of the , configuring it to accept the XML ("application/json") media type. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest ExpectXml(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to accept the XML ("application/json") media type. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest ExpectXml(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return request.AcceptMediaType(WellKnownMediaTypes.Xml); - } + return request.AcceptMediaType(WellKnownMediaTypes.Xml); + } - /// - /// Create a copy of the , configuring it to accept the XML ("application/json") media type. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The new . - /// - public static HttpRequest ExpectXml(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , configuring it to accept the XML ("application/json") media type. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The new . + /// + public static HttpRequest ExpectXml(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return request.AcceptMediaType(WellKnownMediaTypes.Xml); - } - - /// - /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. - /// - /// - /// The . - /// - /// - /// The HTTP request method. - /// - /// - /// The request body content. - /// - /// - /// The request body media type to use. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, object bodyContent, string mediaType, Uri baseUri = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + return request.AcceptMediaType(WellKnownMediaTypes.Xml); + } + + /// + /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. + /// + /// + /// The . + /// + /// + /// The HTTP request method. + /// + /// + /// The request body content. + /// + /// + /// The request body media type to use. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, object bodyContent, string mediaType, Uri baseUri = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return request.BuildRequestMessage(httpMethod, bodyContent, mediaType, OutputEncoding.UTF8, baseUri); - } + return request.BuildRequestMessage(httpMethod, bodyContent, mediaType, OutputEncoding.UTF8, baseUri); + } - /// - /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. - /// - /// - /// The . - /// - /// - /// The HTTP request method. - /// - /// - /// The request body content. - /// - /// - /// The request body media type to use. - /// - /// - /// The request body encoding to use. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, object bodyContent, string mediaType, Encoding encoding, Uri baseUri = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. + /// + /// + /// The . + /// + /// + /// The HTTP request method. + /// + /// + /// The request body content. + /// + /// + /// The request body media type to use. + /// + /// + /// The request body encoding to use. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, object bodyContent, string mediaType, Encoding encoding, Uri baseUri = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - HttpContent httpContent = bodyContent as HttpContent; - if (httpContent == null && bodyContent != null) - { - IFormatterCollection formatters = request.CreateFormatterCollection(); + HttpContent httpContent = bodyContent as HttpContent; + if (httpContent == null && bodyContent != null) + { + IFormatterCollection formatters = request.CreateFormatterCollection(); - OutputFormatterContext writeContext = new OutputFormatterContext(bodyContent, bodyContent.GetType(), mediaType, encoding); - IOutputFormatter writeFormatter = formatters.FindOutputFormatter(writeContext); - if (writeFormatter == null) - throw new HttpRequestException($"None of the supplied formatters can write data of type '{writeContext.DataType.FullName}' to media type '{writeContext.MediaType}'."); + OutputFormatterContext writeContext = new OutputFormatterContext(bodyContent, bodyContent.GetType(), mediaType, encoding); + IOutputFormatter writeFormatter = formatters.FindOutputFormatter(writeContext); + if (writeFormatter == null) + throw new HttpRequestException($"None of the supplied formatters can write data of type '{writeContext.DataType.FullName}' to media type '{writeContext.MediaType}'."); - httpContent = new FormattedObjectContent(writeFormatter, writeContext); - } + httpContent = new FormattedObjectContent(writeFormatter, writeContext); + } - return request.BuildRequestMessage(httpMethod, httpContent, baseUri); - } + return request.BuildRequestMessage(httpMethod, httpContent, baseUri); + } - /// - /// Create a copy of the , adding the specified content formatter. - /// - /// - /// The . - /// - /// - /// The content formatter to add. - /// - /// - /// The new . - /// - public static HttpRequest WithFormatter(this HttpRequest request, IFormatter formatter) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , adding the specified content formatter. + /// + /// + /// The . + /// + /// + /// The content formatter to add. + /// + /// + /// The new . + /// + public static HttpRequest WithFormatter(this HttpRequest request, IFormatter formatter) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - Type formatterType = formatter.GetType(); + Type formatterType = formatter.GetType(); - ImmutableDictionary formatters = request.GetFormatters(); - bool isFirstFormatter = formatters.Count == 0; + ImmutableDictionary formatters = request.GetFormatters(); + bool isFirstFormatter = formatters.Count == 0; - formatters = formatters.SetItem(formatterType, formatter); + formatters = formatters.SetItem(formatterType, formatter); - return request.Clone(properties => - { - properties[MessageProperties.ContentFormatters] = formatters; + return request.Clone(properties => + { + properties[MessageProperties.ContentFormatters] = formatters; - // If this is the first formatter we're adding, then make sure that we'll populate the formatter collection for each outgoing request. - if (isFirstFormatter) - { - properties[nameof(request.RequestActions)] = request.RequestActions.Add((requestMessage, context) => - { - requestMessage.Properties[MessageProperties.ContentFormatters] = new FormatterCollection(formatters.Values); - }); - } - }); - } + // If this is the first formatter we're adding, then make sure that we'll populate the formatter collection for each outgoing request. + if (isFirstFormatter) + { + properties[nameof(request.RequestActions)] = request.RequestActions.Add((requestMessage, context) => + { + requestMessage.Properties[MessageProperties.ContentFormatters] = new FormatterCollection(formatters.Values); + }); + } + }); + } - /// - /// Create a copy of the , adding the specified content formatter. - /// - /// - /// The . - /// - /// - /// The type of content formatter to remove. - /// - /// - /// The new . - /// - public static HttpRequest WithoutFormatter(this HttpRequest request, Type formatterType) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a copy of the , adding the specified content formatter. + /// + /// + /// The . + /// + /// + /// The type of content formatter to remove. + /// + /// + /// The new . + /// + public static HttpRequest WithoutFormatter(this HttpRequest request, Type formatterType) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (formatterType == null) - throw new ArgumentNullException(nameof(formatterType)); + if (formatterType == null) + throw new ArgumentNullException(nameof(formatterType)); - ImmutableDictionary formatters = request.GetFormatters(); - if (formatters == null) - return request; + ImmutableDictionary formatters = request.GetFormatters(); + if (formatters == null) + return request; - if (!formatters.ContainsKey(formatterType)) - return request; + if (!formatters.ContainsKey(formatterType)) + return request; - return request.Clone(properties => - { - properties[MessageProperties.ContentFormatters] = formatters.Remove(formatterType); - }); - } + return request.Clone(properties => + { + properties[MessageProperties.ContentFormatters] = formatters.Remove(formatterType); + }); + } - /// - /// Get the collection formatters used by the . - /// - /// - /// The . - /// - /// - /// An immutable dictionary of formatters, keyed by type. - /// - public static ImmutableDictionary GetFormatters(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Get the collection formatters used by the . + /// + /// + /// The . + /// + /// + /// An immutable dictionary of formatters, keyed by type. + /// + public static ImmutableDictionary GetFormatters(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - object formatters; - if (request.Properties.TryGetValue(MessageProperties.ContentFormatters, out formatters)) - return (ImmutableDictionary)formatters; + object formatters; + if (request.Properties.TryGetValue(MessageProperties.ContentFormatters, out formatters)) + return (ImmutableDictionary)formatters; - return ImmutableDictionary.Empty; - } + return ImmutableDictionary.Empty; + } - /// - /// Create an from the request's registered formatters. - /// - /// - /// The . - /// - /// - /// An representing the formatter collection. - /// - public static IFormatterCollection CreateFormatterCollection(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create an from the request's registered formatters. + /// + /// + /// The . + /// + /// + /// An representing the formatter collection. + /// + public static IFormatterCollection CreateFormatterCollection(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return new FormatterCollection( - request.GetFormatters().Values - ); - } - } + return new FormatterCollection( + request.GetFormatters().Values + ); + } + } } diff --git a/src/HTTPlease.Formatters/FormatterResponseExtensions.cs b/src/HTTPlease.Formatters/FormatterResponseExtensions.cs index 9f95948..250e189 100644 --- a/src/HTTPlease.Formatters/FormatterResponseExtensions.cs +++ b/src/HTTPlease.Formatters/FormatterResponseExtensions.cs @@ -6,610 +6,610 @@ namespace HTTPlease { - using Formatters; - - /// - /// Extension methods for the s returned asynchronously by invocation of s by s. - /// - public static class FormatterResponseExtensions - { - /// - /// Asynchronously read the response body as the specified type using a specific content formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// A that will be used to read the response body. - /// - /// - /// Optional s that are expected and should therefore not prevent the response from being deserialised. - /// - /// If not specified, then the standard behaviour provided by is used. - /// - /// - /// The deserialised response body. - /// - public static async Task ReadContentAsAsync(this Task response, IInputFormatter formatter, params HttpStatusCode[] expectedStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!expectedStatusCodes.Contains(responseMessage.StatusCode)) - responseMessage.EnsureSuccessStatusCode(); // Default behaviour. - - return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); - } - } - - /// - /// Asynchronously read the response body as the specified type, selecting the most appropriate content formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// The that will be used to select an appropriate content formatter for reading the response body. - /// - /// - /// Optional s that are expected and should therefore not prevent the response from being deserialised. - /// - /// If not specified, then the standard behaviour provided by is used. - /// - /// - /// The deserialised response body. - /// - public static async Task ReadContentAsAsync(this Task response, IFormatterCollection formatters, params HttpStatusCode[] expectedStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!expectedStatusCodes.Contains(responseMessage.StatusCode)) - responseMessage.EnsureSuccessStatusCode(); // Default behaviour. - - return await responseMessage.ReadContentAsAsync(formatters).ConfigureAwait(false); - } - } - - /// - /// Asynchronously read the response body as the specified type using a specific content formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// Optional s that are expected and should therefore not prevent the response from being deserialised. - /// - /// If not specified, then the standard behaviour provided by is used. - /// - /// - /// The deserialised response body. - /// - /// - /// No content formatters were configured for the request that generated the response message. - /// - /// Consider using the overload of ReadAsAsync that takes a specific . - /// - public static async Task ReadContentAsAsync(this Task response, params HttpStatusCode[] expectedStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!expectedStatusCodes.Contains(responseMessage.StatusCode)) - responseMessage.EnsureSuccessStatusCode(); // Default behaviour. - - return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); - } - } - - /// - /// Asynchronously read the response body as the specified type. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// The that will be used to read the response body. - /// - /// - /// A delegate that is called to get a in the event that the response status code is not valid. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - public static Task ReadContentAsAsync(this Task response, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - if (onFailureResponse == null) - throw new ArgumentNullException(nameof(onFailureResponse)); - - return response.ReadContentAsAsync(formatter, responseMessage => onFailureResponse(), successStatusCodes); - } - - /// - /// Asynchronously read the response body as the specified type. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// The that will be used to read the response body. - /// - /// - /// A delegate that is called to get a in the event that the response status code is not valid. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - public static async Task ReadContentAsAsync(this Task response, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (onFailureResponse == null) - throw new ArgumentNullException(nameof(onFailureResponse)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) - return onFailureResponse(responseMessage); - - return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); - } - } - - /// - /// Asynchronously read the response body as the specified type, selecting the most appropriate formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// A delegate that is called to get a in the event that the response status code is not valid. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - public static Task ReadContentAsAsync(this Task response, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (onFailureResponse == null) - throw new ArgumentNullException(nameof(onFailureResponse)); - - return response.ReadContentAsAsync(responseMessage => onFailureResponse(), successStatusCodes); - } - - /// - /// Asynchronously read the response body as the specified type, selecting the most appropriate formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The asynchronous response. - /// - /// - /// A delegate that is called to get a in the event that the response status code is not valid. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - public static async Task ReadContentAsAsync(this Task response, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (onFailureResponse == null) - throw new ArgumentNullException(nameof(onFailureResponse)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) - return onFailureResponse(responseMessage); - - return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); - } + using Formatters; + + /// + /// Extension methods for the s returned asynchronously by invocation of s by s. + /// + public static class FormatterResponseExtensions + { + /// + /// Asynchronously read the response body as the specified type using a specific content formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// A that will be used to read the response body. + /// + /// + /// Optional s that are expected and should therefore not prevent the response from being deserialised. + /// + /// If not specified, then the standard behaviour provided by is used. + /// + /// + /// The deserialised response body. + /// + public static async Task ReadContentAsAsync(this Task response, IInputFormatter formatter, params HttpStatusCode[] expectedStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!expectedStatusCodes.Contains(responseMessage.StatusCode)) + responseMessage.EnsureSuccessStatusCode(); // Default behaviour. + + return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); + } } - /// - /// Asynchronously read the response body as the specified type using the most appropriate formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. - /// - /// - /// The asynchronous response. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - /// - /// The response status code was unexpected or did not represent success. - /// - /// - /// No formatters were configured for the request, or an appropriate formatter could not be found in the request's list of formatters. - /// - public static async Task ReadContentAsAsync(this Task response, params HttpStatusCode[] successStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) - { - TError error = await responseMessage.ReadContentAsAsync().ConfigureAwait(false); - - throw new HttpRequestException(responseMessage.StatusCode, error); - } - - return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); - } - } - - /// - /// Asynchronously read the response body as the specified type using the specified formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. - /// - /// - /// The asynchronous response. - /// - /// - /// The that will be used to read the response body. - /// - /// - /// A delegate that is called to get a in the event that the response status code is unexpected or does not represent success. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - /// - /// The response status code was unexpected or did not represent success. - /// - public static async Task ReadContentAsAsync(this Task response, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) - { - if (response == null) - throw new ArgumentNullException(nameof(response)); - - if (onFailureResponse == null) - throw new ArgumentNullException(nameof(onFailureResponse)); - - using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) - { - if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) - { - TError error = onFailureResponse(responseMessage); - if (error == null) - throw new InvalidOperationException("The failure response handler returned null."); - - throw new HttpRequestException(responseMessage.StatusCode, error); - } - - return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); - } - } - - /// - /// Determine if the response has body content. - /// - /// - /// The response message. - /// - /// - /// true, if the response has a non-zero content length. - /// - public static bool HasBody(this HttpResponseMessage responseMessage) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (responseMessage.Content == null) - return false; - - return responseMessage.Content.Headers.ContentLength.GetValueOrDefault() > 0; - } - - /// - /// Ensure that the response has body content. - /// - /// - /// The response message. - /// - /// - /// The response message (enables inline use). - /// - public static HttpResponseMessage EnsureHasBody(this HttpResponseMessage responseMessage) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (responseMessage.HasBody()) - return responseMessage; - - throw new InvalidOperationException("The response body is empty."); // TODO: Consider custom exception type. + /// + /// Asynchronously read the response body as the specified type, selecting the most appropriate content formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// The that will be used to select an appropriate content formatter for reading the response body. + /// + /// + /// Optional s that are expected and should therefore not prevent the response from being deserialised. + /// + /// If not specified, then the standard behaviour provided by is used. + /// + /// + /// The deserialised response body. + /// + public static async Task ReadContentAsAsync(this Task response, IFormatterCollection formatters, params HttpStatusCode[] expectedStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!expectedStatusCodes.Contains(responseMessage.StatusCode)) + responseMessage.EnsureSuccessStatusCode(); // Default behaviour. + + return await responseMessage.ReadContentAsAsync(formatters).ConfigureAwait(false); + } } - /// - /// Deserialise the response message's content into the specified CLR data type using the most appropriate formatter. - /// - /// - /// The CLR data type into which the body will be deserialised. - /// - /// - /// The response message. - /// - /// - /// The deserialised message body. - /// - /// - /// No formatters were configured for the request, or an appropriate formatter could not be found in the request's list of formatters. - /// - public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - // TODO: All overloads should return default(TBody) instead of throwing when body is empty! - // TODO: Otherwise, leave these overloads as-is, and create TryReadContentAsAsync overloads that don't throw. - responseMessage.EnsureHasBody(); - - IFormatterCollection formatters = responseMessage.GetFormatters(); - if (formatters == null || formatters.Count == 0) - throw new InvalidOperationException("No content formatters were configured for the request that generated the response message."); // TODO: Consider custom exception type. - - return await responseMessage.ReadContentAsAsync(formatters).ConfigureAwait(false); - } - - /// - /// Deserialise the response message's content into the specified CLR data type using the most appropriate formatter. - /// - /// - /// The CLR data type into which the body will be deserialised. - /// - /// - /// The response message. - /// - /// - /// The collection of content formatters from which to select an appropriate formatter. - /// - /// - /// The deserialised message body. - /// - /// - /// An appropriate formatter could not be found in the request's list of formatters. - /// - public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IFormatterCollection formatters) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (formatters == null) - throw new ArgumentNullException(nameof(formatters)); - - responseMessage.EnsureHasBody(); - - InputFormatterContext readContext = responseMessage.Content.CreateInputFormatterContext(); - IInputFormatter readFormatter = formatters.FindInputFormatter(readContext); - if (readFormatter == null) - throw new InvalidOperationException($"None of the supplied formatters can read data of type '{readContext.DataType.FullName}' from media type '{readContext.MediaType}'."); - - return await responseMessage.ReadContentAsAsync(readFormatter, readContext).ConfigureAwait(false); - } - - /// - /// Deserialise the response message's body content into the specified CLR data type using the specified formatter. - /// - /// - /// The CLR data type into which the body content will be deserialised. - /// - /// - /// The response message. - /// - /// - /// The content formatter that will be used to deserialise the body content. - /// - /// - /// The deserialised message body. - /// - public static Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IInputFormatter formatter) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - responseMessage.EnsureHasBody(); - - InputFormatterContext formatterContext = responseMessage.Content.CreateInputFormatterContext(); - - return responseMessage.ReadContentAsAsync(formatter, formatterContext); - } - - /// - /// Deserialise the response message's body content into the specified CLR data type using the specified formatter. - /// - /// - /// The CLR data type into which the body content will be deserialised. - /// - /// - /// The response message. - /// - /// - /// The content formatter that will be used to deserialise the body content. - /// - /// - /// Contextual information for the formatter about the body content. - /// - /// - /// The deserialised message body. - /// - /// - /// An appropriate formatter could not be found in the request's list of formatters. - /// - public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IInputFormatter formatter, InputFormatterContext formatterContext) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); - - if (formatterContext == null) - throw new ArgumentNullException(nameof(formatterContext)); - - responseMessage.EnsureHasBody(); - - return await responseMessage.Content.ReadAsAsync(formatter, formatterContext).ConfigureAwait(false); - } - - /// - /// Asynchronously read the response body as the specified type using the most appropriate formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. - /// - /// - /// The response message. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - /// - /// The response status code was unexpected or did not represent success. - /// - /// - /// No formatters were configured for the request, or an appropriate formatter could not be found in the request's list of formatters. - /// - public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, params HttpStatusCode[] successStatusCodes) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) - { - TError error = await responseMessage.ReadContentAsAsync().ConfigureAwait(false); - - throw new HttpRequestException(responseMessage.StatusCode, error); - } - - return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); - } - - /// - /// Asynchronously read the response body as the specified type using the specified formatter. - /// - /// - /// The CLR type into which the body content will be deserialised. - /// - /// - /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. - /// - /// - /// The response message. - /// - /// - /// The that will be used to read the response body. - /// - /// - /// A delegate that is called to get a in the event that the response status code is unexpected or does not represent success. - /// - /// - /// Optional s that should be treated as representing a successful response. - /// - /// - /// The deserialised body. - /// - /// - /// The response status code was unexpected or did not represent success. - /// - public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) - { - if (responseMessage == null) - throw new ArgumentNullException(nameof(responseMessage)); - - if (onFailureResponse == null) - throw new ArgumentNullException(nameof(onFailureResponse)); - - if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) - { - TError error = onFailureResponse(responseMessage); - if (error == null) - throw new InvalidOperationException("The failure response handler returned null."); - - throw new HttpRequestException(responseMessage.StatusCode, error); - } - - return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); - } - } + /// + /// Asynchronously read the response body as the specified type using a specific content formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// Optional s that are expected and should therefore not prevent the response from being deserialised. + /// + /// If not specified, then the standard behaviour provided by is used. + /// + /// + /// The deserialised response body. + /// + /// + /// No content formatters were configured for the request that generated the response message. + /// + /// Consider using the overload of ReadAsAsync that takes a specific . + /// + public static async Task ReadContentAsAsync(this Task response, params HttpStatusCode[] expectedStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!expectedStatusCodes.Contains(responseMessage.StatusCode)) + responseMessage.EnsureSuccessStatusCode(); // Default behaviour. + + return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); + } + } + + /// + /// Asynchronously read the response body as the specified type. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// The that will be used to read the response body. + /// + /// + /// A delegate that is called to get a in the event that the response status code is not valid. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + public static Task ReadContentAsAsync(this Task response, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + if (onFailureResponse == null) + throw new ArgumentNullException(nameof(onFailureResponse)); + + return response.ReadContentAsAsync(formatter, responseMessage => onFailureResponse(), successStatusCodes); + } + + /// + /// Asynchronously read the response body as the specified type. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// The that will be used to read the response body. + /// + /// + /// A delegate that is called to get a in the event that the response status code is not valid. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + public static async Task ReadContentAsAsync(this Task response, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (onFailureResponse == null) + throw new ArgumentNullException(nameof(onFailureResponse)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) + return onFailureResponse(responseMessage); + + return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); + } + } + + /// + /// Asynchronously read the response body as the specified type, selecting the most appropriate formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// A delegate that is called to get a in the event that the response status code is not valid. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + public static Task ReadContentAsAsync(this Task response, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (onFailureResponse == null) + throw new ArgumentNullException(nameof(onFailureResponse)); + + return response.ReadContentAsAsync(responseMessage => onFailureResponse(), successStatusCodes); + } + + /// + /// Asynchronously read the response body as the specified type, selecting the most appropriate formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The asynchronous response. + /// + /// + /// A delegate that is called to get a in the event that the response status code is not valid. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + public static async Task ReadContentAsAsync(this Task response, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (onFailureResponse == null) + throw new ArgumentNullException(nameof(onFailureResponse)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) + return onFailureResponse(responseMessage); + + return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); + } + } + + /// + /// Asynchronously read the response body as the specified type using the most appropriate formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. + /// + /// + /// The asynchronous response. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + /// + /// The response status code was unexpected or did not represent success. + /// + /// + /// No formatters were configured for the request, or an appropriate formatter could not be found in the request's list of formatters. + /// + public static async Task ReadContentAsAsync(this Task response, params HttpStatusCode[] successStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) + { + TError error = await responseMessage.ReadContentAsAsync().ConfigureAwait(false); + + throw new HttpRequestException(responseMessage.StatusCode, error); + } + + return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); + } + } + + /// + /// Asynchronously read the response body as the specified type using the specified formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. + /// + /// + /// The asynchronous response. + /// + /// + /// The that will be used to read the response body. + /// + /// + /// A delegate that is called to get a in the event that the response status code is unexpected or does not represent success. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + /// + /// The response status code was unexpected or did not represent success. + /// + public static async Task ReadContentAsAsync(this Task response, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) + { + if (response == null) + throw new ArgumentNullException(nameof(response)); + + if (onFailureResponse == null) + throw new ArgumentNullException(nameof(onFailureResponse)); + + using (HttpResponseMessage responseMessage = await response.ConfigureAwait(false)) + { + if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) + { + TError error = onFailureResponse(responseMessage); + if (error == null) + throw new InvalidOperationException("The failure response handler returned null."); + + throw new HttpRequestException(responseMessage.StatusCode, error); + } + + return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); + } + } + + /// + /// Determine if the response has body content. + /// + /// + /// The response message. + /// + /// + /// true, if the response has a non-zero content length. + /// + public static bool HasBody(this HttpResponseMessage responseMessage) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (responseMessage.Content == null) + return false; + + return responseMessage.Content.Headers.ContentLength.GetValueOrDefault() > 0; + } + + /// + /// Ensure that the response has body content. + /// + /// + /// The response message. + /// + /// + /// The response message (enables inline use). + /// + public static HttpResponseMessage EnsureHasBody(this HttpResponseMessage responseMessage) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (responseMessage.HasBody()) + return responseMessage; + + throw new InvalidOperationException("The response body is empty."); // TODO: Consider custom exception type. + } + + /// + /// Deserialise the response message's content into the specified CLR data type using the most appropriate formatter. + /// + /// + /// The CLR data type into which the body will be deserialised. + /// + /// + /// The response message. + /// + /// + /// The deserialised message body. + /// + /// + /// No formatters were configured for the request, or an appropriate formatter could not be found in the request's list of formatters. + /// + public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + // TODO: All overloads should return default(TBody) instead of throwing when body is empty! + // TODO: Otherwise, leave these overloads as-is, and create TryReadContentAsAsync overloads that don't throw. + responseMessage.EnsureHasBody(); + + IFormatterCollection formatters = responseMessage.GetFormatters(); + if (formatters == null || formatters.Count == 0) + throw new InvalidOperationException("No content formatters were configured for the request that generated the response message."); // TODO: Consider custom exception type. + + return await responseMessage.ReadContentAsAsync(formatters).ConfigureAwait(false); + } + + /// + /// Deserialise the response message's content into the specified CLR data type using the most appropriate formatter. + /// + /// + /// The CLR data type into which the body will be deserialised. + /// + /// + /// The response message. + /// + /// + /// The collection of content formatters from which to select an appropriate formatter. + /// + /// + /// The deserialised message body. + /// + /// + /// An appropriate formatter could not be found in the request's list of formatters. + /// + public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IFormatterCollection formatters) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (formatters == null) + throw new ArgumentNullException(nameof(formatters)); + + responseMessage.EnsureHasBody(); + + InputFormatterContext readContext = responseMessage.Content.CreateInputFormatterContext(); + IInputFormatter readFormatter = formatters.FindInputFormatter(readContext); + if (readFormatter == null) + throw new InvalidOperationException($"None of the supplied formatters can read data of type '{readContext.DataType.FullName}' from media type '{readContext.MediaType}'."); + + return await responseMessage.ReadContentAsAsync(readFormatter, readContext).ConfigureAwait(false); + } + + /// + /// Deserialise the response message's body content into the specified CLR data type using the specified formatter. + /// + /// + /// The CLR data type into which the body content will be deserialised. + /// + /// + /// The response message. + /// + /// + /// The content formatter that will be used to deserialise the body content. + /// + /// + /// The deserialised message body. + /// + public static Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IInputFormatter formatter) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + responseMessage.EnsureHasBody(); + + InputFormatterContext formatterContext = responseMessage.Content.CreateInputFormatterContext(); + + return responseMessage.ReadContentAsAsync(formatter, formatterContext); + } + + /// + /// Deserialise the response message's body content into the specified CLR data type using the specified formatter. + /// + /// + /// The CLR data type into which the body content will be deserialised. + /// + /// + /// The response message. + /// + /// + /// The content formatter that will be used to deserialise the body content. + /// + /// + /// Contextual information for the formatter about the body content. + /// + /// + /// The deserialised message body. + /// + /// + /// An appropriate formatter could not be found in the request's list of formatters. + /// + public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IInputFormatter formatter, InputFormatterContext formatterContext) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + if (formatterContext == null) + throw new ArgumentNullException(nameof(formatterContext)); + + responseMessage.EnsureHasBody(); + + return await responseMessage.Content.ReadAsAsync(formatter, formatterContext).ConfigureAwait(false); + } + + /// + /// Asynchronously read the response body as the specified type using the most appropriate formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. + /// + /// + /// The response message. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + /// + /// The response status code was unexpected or did not represent success. + /// + /// + /// No formatters were configured for the request, or an appropriate formatter could not be found in the request's list of formatters. + /// + public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, params HttpStatusCode[] successStatusCodes) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) + { + TError error = await responseMessage.ReadContentAsAsync().ConfigureAwait(false); + + throw new HttpRequestException(responseMessage.StatusCode, error); + } + + return await responseMessage.ReadContentAsAsync().ConfigureAwait(false); + } + + /// + /// Asynchronously read the response body as the specified type using the specified formatter. + /// + /// + /// The CLR type into which the body content will be deserialised. + /// + /// + /// The CLR type that will be returned in the event that the response status code is unexpected or does not represent success. + /// + /// + /// The response message. + /// + /// + /// The that will be used to read the response body. + /// + /// + /// A delegate that is called to get a in the event that the response status code is unexpected or does not represent success. + /// + /// + /// Optional s that should be treated as representing a successful response. + /// + /// + /// The deserialised body. + /// + /// + /// The response status code was unexpected or did not represent success. + /// + public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage, IInputFormatter formatter, Func onFailureResponse, params HttpStatusCode[] successStatusCodes) + { + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + if (onFailureResponse == null) + throw new ArgumentNullException(nameof(onFailureResponse)); + + if (!successStatusCodes.Contains(responseMessage.StatusCode) && !responseMessage.IsSuccessStatusCode) + { + TError error = onFailureResponse(responseMessage); + if (error == null) + throw new InvalidOperationException("The failure response handler returned null."); + + throw new HttpRequestException(responseMessage.StatusCode, error); + } + + return await responseMessage.ReadContentAsAsync(formatter).ConfigureAwait(false); + } + } } diff --git a/src/HTTPlease.Formatters/FormatterTypedRequestExtensions.cs b/src/HTTPlease.Formatters/FormatterTypedRequestExtensions.cs index 3aeeaf8..61a1e79 100644 --- a/src/HTTPlease.Formatters/FormatterTypedRequestExtensions.cs +++ b/src/HTTPlease.Formatters/FormatterTypedRequestExtensions.cs @@ -5,218 +5,218 @@ namespace HTTPlease { - using Formatters; + using Formatters; - /// - /// Extension methods for working with s. - /// - public static class FormatterTypedRequestExtensions + /// + /// Extension methods for working with s. + /// + public static class FormatterTypedRequestExtensions { - /// - /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP request method. - /// - /// - /// The instance to use as a context for resolving deferred template parameters. - /// - /// - /// The request body content. - /// - /// - /// The request body media type to use. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, TContext context, object bodyContent, string mediaType, Uri baseUri = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - return request.BuildRequestMessage(httpMethod, context, bodyContent, mediaType, OutputEncoding.UTF8, baseUri); - } - - /// - /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP request method. - /// - /// - /// The instance to use as a context for resolving deferred template parameters. - /// - /// - /// The request body content. - /// - /// - /// The request body media type to use. - /// - /// - /// The request body encoding to use. - /// - /// - /// An optional base URI to use if the request does not already have an absolute request URI. - /// - /// - /// The configured . - /// - public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, TContext context, object bodyContent, string mediaType, Encoding encoding, Uri baseUri = null) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - HttpContent httpContent = bodyContent as HttpContent; - if (httpContent == null && bodyContent != null) - { - IFormatterCollection formatters = request.CreateFormatterCollection(); - - OutputFormatterContext writeContext = new OutputFormatterContext(bodyContent, bodyContent.GetType(), mediaType, encoding); - IOutputFormatter writeFormatter = formatters.FindOutputFormatter(writeContext); - if (writeFormatter == null) - throw new HttpRequestException($"None of the supplied formatters can write data of type '{writeContext.DataType.FullName}' to media type '{writeContext.MediaType}'."); - - httpContent = new FormattedObjectContent(writeFormatter, writeContext); - } - - return request.BuildRequestMessage(httpMethod, context, httpContent, baseUri); - } - - /// - /// Create a copy of the , adding the specified content formatter. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The content formatter to add. - /// - /// - /// The new . - /// - public static HttpRequest WithFormatter(this HttpRequest request, IFormatter formatter) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - Type formatterType = formatter.GetType(); - - return request.Clone(properties => - { - ImmutableDictionary formatters = request.GetFormatters(); - - // If this is the first formatter we're adding, then make sure that we'll populate the formatter collection for each outgoing request. - if (formatters.Count == 0) - { - properties[nameof(request.RequestActions)] = request.RequestActions.Add((requestMessage, context) => - { - requestMessage.Properties[MessageProperties.ContentFormatters] = new FormatterCollection(formatters.Values); - }); - } - - properties[MessageProperties.ContentFormatters] = formatters.SetItem(formatterType, formatter); - }); - } - - /// - /// Create a copy of the , adding the specified content formatter. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The type of content formatter to remove. - /// - /// - /// The new . - /// - public static HttpRequest WithoutFormatter(this HttpRequest request, Type formatterType) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (formatterType == null) - throw new ArgumentNullException(nameof(formatterType)); - - ImmutableDictionary formatters = request.GetFormatters(); - if (formatters == null) - return request; - - if (!formatters.ContainsKey(formatterType)) - return request; - - return request.Clone(properties => - { - properties[MessageProperties.ContentFormatters] = formatters.Remove(formatterType); - }); - } - - /// - /// Get the collection formatters used by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// An immutable dictionary of formatters, keyed by type. - /// - public static ImmutableDictionary GetFormatters(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - object formatters; - if (request.Properties.TryGetValue(MessageProperties.ContentFormatters, out formatters)) - return (ImmutableDictionary)formatters; - - return ImmutableDictionary.Empty; - } - - /// - /// Create an from the request's registered formatters. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// An representing the formatter collection. - /// - public static IFormatterCollection CreateFormatterCollection(this HttpRequest request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - return new FormatterCollection( - request.GetFormatters().Values - ); - } - } + /// + /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP request method. + /// + /// + /// The instance to use as a context for resolving deferred template parameters. + /// + /// + /// The request body content. + /// + /// + /// The request body media type to use. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, TContext context, object bodyContent, string mediaType, Uri baseUri = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + return request.BuildRequestMessage(httpMethod, context, bodyContent, mediaType, OutputEncoding.UTF8, baseUri); + } + + /// + /// Build an HTTP request message, selecting an appropriate content formatter to serialise its body content. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP request method. + /// + /// + /// The instance to use as a context for resolving deferred template parameters. + /// + /// + /// The request body content. + /// + /// + /// The request body media type to use. + /// + /// + /// The request body encoding to use. + /// + /// + /// An optional base URI to use if the request does not already have an absolute request URI. + /// + /// + /// The configured . + /// + public static HttpRequestMessage BuildRequestMessage(this HttpRequest request, HttpMethod httpMethod, TContext context, object bodyContent, string mediaType, Encoding encoding, Uri baseUri = null) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + HttpContent httpContent = bodyContent as HttpContent; + if (httpContent == null && bodyContent != null) + { + IFormatterCollection formatters = request.CreateFormatterCollection(); + + OutputFormatterContext writeContext = new OutputFormatterContext(bodyContent, bodyContent.GetType(), mediaType, encoding); + IOutputFormatter writeFormatter = formatters.FindOutputFormatter(writeContext); + if (writeFormatter == null) + throw new HttpRequestException($"None of the supplied formatters can write data of type '{writeContext.DataType.FullName}' to media type '{writeContext.MediaType}'."); + + httpContent = new FormattedObjectContent(writeFormatter, writeContext); + } + + return request.BuildRequestMessage(httpMethod, context, httpContent, baseUri); + } + + /// + /// Create a copy of the , adding the specified content formatter. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The content formatter to add. + /// + /// + /// The new . + /// + public static HttpRequest WithFormatter(this HttpRequest request, IFormatter formatter) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + Type formatterType = formatter.GetType(); + + return request.Clone(properties => + { + ImmutableDictionary formatters = request.GetFormatters(); + + // If this is the first formatter we're adding, then make sure that we'll populate the formatter collection for each outgoing request. + if (formatters.Count == 0) + { + properties[nameof(request.RequestActions)] = request.RequestActions.Add((requestMessage, context) => + { + requestMessage.Properties[MessageProperties.ContentFormatters] = new FormatterCollection(formatters.Values); + }); + } + + properties[MessageProperties.ContentFormatters] = formatters.SetItem(formatterType, formatter); + }); + } + + /// + /// Create a copy of the , adding the specified content formatter. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The type of content formatter to remove. + /// + /// + /// The new . + /// + public static HttpRequest WithoutFormatter(this HttpRequest request, Type formatterType) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (formatterType == null) + throw new ArgumentNullException(nameof(formatterType)); + + ImmutableDictionary formatters = request.GetFormatters(); + if (formatters == null) + return request; + + if (!formatters.ContainsKey(formatterType)) + return request; + + return request.Clone(properties => + { + properties[MessageProperties.ContentFormatters] = formatters.Remove(formatterType); + }); + } + + /// + /// Get the collection formatters used by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// An immutable dictionary of formatters, keyed by type. + /// + public static ImmutableDictionary GetFormatters(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + object formatters; + if (request.Properties.TryGetValue(MessageProperties.ContentFormatters, out formatters)) + return (ImmutableDictionary)formatters; + + return ImmutableDictionary.Empty; + } + + /// + /// Create an from the request's registered formatters. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// An representing the formatter collection. + /// + public static IFormatterCollection CreateFormatterCollection(this HttpRequest request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + return new FormatterCollection( + request.GetFormatters().Values + ); + } + } } diff --git a/src/HTTPlease.Formatters/IFormatter.cs b/src/HTTPlease.Formatters/IFormatter.cs index 0781ee0..2c2a799 100644 --- a/src/HTTPlease.Formatters/IFormatter.cs +++ b/src/HTTPlease.Formatters/IFormatter.cs @@ -2,14 +2,14 @@ namespace HTTPlease.Formatters { - /// - /// Represents a content formatter. - /// + /// + /// Represents a content formatter. + /// public interface IFormatter { - /// - /// Content types supported by the formatter. - /// - ISet SupportedMediaTypes { get; } - } + /// + /// Content types supported by the formatter. + /// + ISet SupportedMediaTypes { get; } + } } diff --git a/src/HTTPlease.Formatters/IFormatterCollection.cs b/src/HTTPlease.Formatters/IFormatterCollection.cs index 5a1f492..378cb09 100644 --- a/src/HTTPlease.Formatters/IFormatterCollection.cs +++ b/src/HTTPlease.Formatters/IFormatterCollection.cs @@ -3,60 +3,60 @@ namespace HTTPlease.Formatters { - /// - /// Represents a collection of content formatters. - /// + /// + /// Represents a collection of content formatters. + /// public interface IFormatterCollection - : ICollection - { - /// - /// Get the most appropriate formatter to read the specified data. - /// - /// - /// Contextual information about the data to deserialise. - /// - /// - /// The formatter, or null if none of the formatters in the collection can handle the specified content type. - /// - /// - /// is null. - /// - IInputFormatter FindInputFormatter(InputFormatterContext context); + : ICollection + { + /// + /// Get the most appropriate formatter to read the specified data. + /// + /// + /// Contextual information about the data to deserialise. + /// + /// + /// The formatter, or null if none of the formatters in the collection can handle the specified content type. + /// + /// + /// is null. + /// + IInputFormatter FindInputFormatter(InputFormatterContext context); - /// - /// Find the most appropriate formatter to write the specified data. - /// - /// - /// Contextual information about the data to deserialise. - /// - /// - /// The formatter, or null if none of the formatters in the collection can handle the specified content type. - /// - /// - /// is null. - /// - IOutputFormatter FindOutputFormatter(OutputFormatterContext context); + /// + /// Find the most appropriate formatter to write the specified data. + /// + /// + /// Contextual information about the data to deserialise. + /// + /// + /// The formatter, or null if none of the formatters in the collection can handle the specified content type. + /// + /// + /// is null. + /// + IOutputFormatter FindOutputFormatter(OutputFormatterContext context); - /// - /// Determine whether the collection contains a formatter of the specified type. - /// - /// - /// The formatter type. - /// - /// - /// true, if the collection contains a formatter of the specified type; otherwise, false. - /// - bool Contains(Type formatterType); + /// + /// Determine whether the collection contains a formatter of the specified type. + /// + /// + /// The formatter type. + /// + /// + /// true, if the collection contains a formatter of the specified type; otherwise, false. + /// + bool Contains(Type formatterType); - /// - /// Remove the formatter of the specified type (if it is present in the collection). - /// - /// - /// The type of formatter to remove. - /// - /// - /// true, if the formatter was removed; otherwise, false. - /// - bool Remove(Type formatterType); - } + /// + /// Remove the formatter of the specified type (if it is present in the collection). + /// + /// + /// The type of formatter to remove. + /// + /// + /// true, if the formatter was removed; otherwise, false. + /// + bool Remove(Type formatterType); + } } diff --git a/src/HTTPlease.Formatters/IInputFormatter.cs b/src/HTTPlease.Formatters/IInputFormatter.cs index a119092..6c5ff77 100644 --- a/src/HTTPlease.Formatters/IInputFormatter.cs +++ b/src/HTTPlease.Formatters/IInputFormatter.cs @@ -3,35 +3,35 @@ namespace HTTPlease.Formatters { - /// - /// Represents a facility for deserialising data for one or more media types. - /// + /// + /// Represents a facility for deserialising data for one or more media types. + /// public interface IInputFormatter - : IFormatter - { - /// - /// Determine whether the formatter can deserialise the specified data. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// true, if the formatter can deserialise the data; otherwise, false. - /// - bool CanRead(InputFormatterContext context); + : IFormatter + { + /// + /// Determine whether the formatter can deserialise the specified data. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// true, if the formatter can deserialise the data; otherwise, false. + /// + bool CanRead(InputFormatterContext context); - /// - /// Asynchronously deserialise data from an input stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The input stream from which to read serialised data. - /// - /// - /// The deserialised object. - /// - Task ReadAsync(InputFormatterContext context, Stream stream); - } + /// + /// Asynchronously deserialise data from an input stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The input stream from which to read serialised data. + /// + /// + /// The deserialised object. + /// + Task ReadAsync(InputFormatterContext context, Stream stream); + } } diff --git a/src/HTTPlease.Formatters/IInputOutputFormatter.cs b/src/HTTPlease.Formatters/IInputOutputFormatter.cs index 072478d..63955e0 100644 --- a/src/HTTPlease.Formatters/IInputOutputFormatter.cs +++ b/src/HTTPlease.Formatters/IInputOutputFormatter.cs @@ -1,10 +1,10 @@ namespace HTTPlease.Formatters { - /// - /// Represents a formatter that can both serialise and deserialise data. - /// + /// + /// Represents a formatter that can both serialise and deserialise data. + /// public interface IInputOutputFormatter - : IInputFormatter, IOutputFormatter + : IInputFormatter, IOutputFormatter { } } diff --git a/src/HTTPlease.Formatters/IOutputFormatter.cs b/src/HTTPlease.Formatters/IOutputFormatter.cs index 06f03e3..ea71ebc 100644 --- a/src/HTTPlease.Formatters/IOutputFormatter.cs +++ b/src/HTTPlease.Formatters/IOutputFormatter.cs @@ -3,35 +3,35 @@ namespace HTTPlease.Formatters { - /// - /// Represents a facility for serialising data to one or more media types. - /// + /// + /// Represents a facility for serialising data to one or more media types. + /// public interface IOutputFormatter - : IFormatter + : IFormatter { - /// - /// Determine whether the formatter can serialise the specified data. - /// - /// - /// Contextual information about the data being serialised. - /// - /// - /// true, if the formatter can serialise the data; otherwise, false. - /// - bool CanWrite(OutputFormatterContext context); + /// + /// Determine whether the formatter can serialise the specified data. + /// + /// + /// Contextual information about the data being serialised. + /// + /// + /// true, if the formatter can serialise the data; otherwise, false. + /// + bool CanWrite(OutputFormatterContext context); - /// - /// Asynchronously serialise data to an output stream. - /// - /// - /// Contextual information about the data being deserialised. - /// - /// - /// The output stream to which the serialised data will be written. - /// - /// - /// A representing the asynchronous operation. - /// - Task WriteAsync(OutputFormatterContext context, Stream stream); + /// + /// Asynchronously serialise data to an output stream. + /// + /// + /// Contextual information about the data being deserialised. + /// + /// + /// The output stream to which the serialised data will be written. + /// + /// + /// A representing the asynchronous operation. + /// + Task WriteAsync(OutputFormatterContext context, Stream stream); } } diff --git a/src/HTTPlease.Formatters/InputFormatterContext.cs b/src/HTTPlease.Formatters/InputFormatterContext.cs index e00132d..8e17602 100644 --- a/src/HTTPlease.Formatters/InputFormatterContext.cs +++ b/src/HTTPlease.Formatters/InputFormatterContext.cs @@ -4,63 +4,63 @@ namespace HTTPlease.Formatters { - /// - /// Contextual information used by input formatters. - /// - public class InputFormatterContext - { - /// - /// Create a new . - /// - /// - /// The CLR type into which the data will be deserialised. - /// - /// - /// The media type that the formatter should expect. - /// - /// - /// The content encoding that the formatter should expect. - /// - public InputFormatterContext(Type dataType, string mediaType, Encoding encoding) - { - DataType = dataType; - MediaType = mediaType; - Encoding = encoding; - } + /// + /// Contextual information used by input formatters. + /// + public class InputFormatterContext + { + /// + /// Create a new . + /// + /// + /// The CLR type into which the data will be deserialised. + /// + /// + /// The media type that the formatter should expect. + /// + /// + /// The content encoding that the formatter should expect. + /// + public InputFormatterContext(Type dataType, string mediaType, Encoding encoding) + { + DataType = dataType; + MediaType = mediaType; + Encoding = encoding; + } - /// - /// The CLR type into which the data will be deserialised. - /// - public Type DataType { get; } + /// + /// The CLR type into which the data will be deserialised. + /// + public Type DataType { get; } - /// - /// The media type that the formatter should expect. - /// - public string MediaType { get; } + /// + /// The media type that the formatter should expect. + /// + public string MediaType { get; } - /// - /// The content encoding that the formatter should expect. - /// - public Encoding Encoding { get; } + /// + /// The content encoding that the formatter should expect. + /// + public Encoding Encoding { get; } - /// - /// Create a from the specified input stream. - /// - /// - /// The input stream. - /// - /// - /// The . - /// - /// - /// The , when closed, will not close the input stream. - /// - public virtual TextReader CreateReader(Stream inputStream) - { - if (inputStream == null) - throw new ArgumentNullException(nameof(inputStream)); + /// + /// Create a from the specified input stream. + /// + /// + /// The input stream. + /// + /// + /// The . + /// + /// + /// The , when closed, will not close the input stream. + /// + public virtual TextReader CreateReader(Stream inputStream) + { + if (inputStream == null) + throw new ArgumentNullException(nameof(inputStream)); - return StreamHelper.CreateTransientTextReader(inputStream, Encoding); - } - } + return StreamHelper.CreateTransientTextReader(inputStream, Encoding); + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Formatters/MessageExtensions.cs b/src/HTTPlease.Formatters/MessageExtensions.cs index 6d604d0..8df0db5 100644 --- a/src/HTTPlease.Formatters/MessageExtensions.cs +++ b/src/HTTPlease.Formatters/MessageExtensions.cs @@ -3,75 +3,75 @@ namespace HTTPlease { - using Formatters; + using Formatters; - /// - /// Formatter-related extension methods for / . - /// - public static class FormatterMessageExtensions + /// + /// Formatter-related extension methods for / . + /// + public static class FormatterMessageExtensions { - /// - /// Get the message's (if any). - /// - /// - /// The HTTP request message. - /// - /// - /// The content formatters, or null if the message does not have any associated formatters. - /// - public static IFormatterCollection GetFormatters(this HttpRequestMessage message) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); + /// + /// Get the message's (if any). + /// + /// + /// The HTTP request message. + /// + /// + /// The content formatters, or null if the message does not have any associated formatters. + /// + public static IFormatterCollection GetFormatters(this HttpRequestMessage message) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); - object contentFormatters; - message.Properties.TryGetValue(MessageProperties.ContentFormatters, out contentFormatters); + object contentFormatters; + message.Properties.TryGetValue(MessageProperties.ContentFormatters, out contentFormatters); - return (IFormatterCollection)contentFormatters; - } + return (IFormatterCollection)contentFormatters; + } - /// - /// Get the message's (if any). - /// - /// - /// The HTTP request message. - /// - /// - /// The content formatters, or null if the message does not have any associated formatters. - /// - /// - /// Can only be called on an whose contains a valid . - /// - public static IFormatterCollection GetFormatters(this HttpResponseMessage message) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); + /// + /// Get the message's (if any). + /// + /// + /// The HTTP request message. + /// + /// + /// The content formatters, or null if the message does not have any associated formatters. + /// + /// + /// Can only be called on an whose contains a valid . + /// + public static IFormatterCollection GetFormatters(this HttpResponseMessage message) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); - HttpRequestMessage requestMessage = message.RequestMessage; - if (requestMessage == null) - throw new InvalidOperationException("This operation is only valid on a response message produced by invoking an HttpRequest (the response message does not have an associated request message)."); + HttpRequestMessage requestMessage = message.RequestMessage; + if (requestMessage == null) + throw new InvalidOperationException("This operation is only valid on a response message produced by invoking an HttpRequest (the response message does not have an associated request message)."); - object contentFormatters; - message.RequestMessage.Properties.TryGetValue(MessageProperties.ContentFormatters, out contentFormatters); + object contentFormatters; + message.RequestMessage.Properties.TryGetValue(MessageProperties.ContentFormatters, out contentFormatters); - return (IFormatterCollection)contentFormatters; - } + return (IFormatterCollection)contentFormatters; + } - /// - /// Set the message's . - /// - /// - /// The HTTP request message. - /// - /// - /// The content formatters (if any). - /// - public static void SetFormatters(this HttpRequestMessage message, IFormatterCollection contentFormatters) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); + /// + /// Set the message's . + /// + /// + /// The HTTP request message. + /// + /// + /// The content formatters (if any). + /// + public static void SetFormatters(this HttpRequestMessage message, IFormatterCollection contentFormatters) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); - message.Properties[MessageProperties.ContentFormatters] = contentFormatters; - } - } + message.Properties[MessageProperties.ContentFormatters] = contentFormatters; + } + } } diff --git a/src/HTTPlease.Formatters/OutputFormatterContext.cs b/src/HTTPlease.Formatters/OutputFormatterContext.cs index b9726ec..6a67801 100644 --- a/src/HTTPlease.Formatters/OutputFormatterContext.cs +++ b/src/HTTPlease.Formatters/OutputFormatterContext.cs @@ -3,74 +3,74 @@ namespace HTTPlease.Formatters { - using System.IO; + using System.IO; - /// - /// Contextual information used by output formatters. - /// - public class OutputFormatterContext - { - /// - /// Create a new . - /// - /// - /// The data being serialised. - /// - /// - /// The CLR type whose data will be serialised. - /// - /// - /// The media type that the formatter should produced. - /// - /// - /// The content encoding that the formatter should use. - /// - public OutputFormatterContext(object data, Type dataType, string mediaType, Encoding encoding) - { - Data = data; - DataType = dataType; - MediaType = mediaType; - Encoding = encoding; - } + /// + /// Contextual information used by output formatters. + /// + public class OutputFormatterContext + { + /// + /// Create a new . + /// + /// + /// The data being serialised. + /// + /// + /// The CLR type whose data will be serialised. + /// + /// + /// The media type that the formatter should produced. + /// + /// + /// The content encoding that the formatter should use. + /// + public OutputFormatterContext(object data, Type dataType, string mediaType, Encoding encoding) + { + Data = data; + DataType = dataType; + MediaType = mediaType; + Encoding = encoding; + } - /// - /// The data being serialised. - /// - public object Data { get; } + /// + /// The data being serialised. + /// + public object Data { get; } - /// - /// The CLR type whose data will be serialised. - /// - public Type DataType { get; } + /// + /// The CLR type whose data will be serialised. + /// + public Type DataType { get; } - /// - /// The content type being serialised. - /// - public string MediaType { get; } + /// + /// The content type being serialised. + /// + public string MediaType { get; } - /// - /// The content encoding. - /// - public Encoding Encoding { get; } + /// + /// The content encoding. + /// + public Encoding Encoding { get; } - /// - /// Create a from the specified output stream. - /// - /// - /// The output stream. - /// - /// - /// The . - /// - /// - /// The , when closed, will not close the output stream. - /// - public virtual TextWriter CreateWriter(Stream outputStream) - { - if (outputStream == null) - throw new ArgumentNullException(nameof(outputStream)); + /// + /// Create a from the specified output stream. + /// + /// + /// The output stream. + /// + /// + /// The . + /// + /// + /// The , when closed, will not close the output stream. + /// + public virtual TextWriter CreateWriter(Stream outputStream) + { + if (outputStream == null) + throw new ArgumentNullException(nameof(outputStream)); - return StreamHelper.CreateTransientTextWriter(outputStream, Encoding); - } - } + return StreamHelper.CreateTransientTextWriter(outputStream, Encoding); + } + } } \ No newline at end of file diff --git a/src/HTTPlease.Formatters/StreamHelper.cs b/src/HTTPlease.Formatters/StreamHelper.cs index a08f32c..be1c739 100644 --- a/src/HTTPlease.Formatters/StreamHelper.cs +++ b/src/HTTPlease.Formatters/StreamHelper.cs @@ -4,83 +4,83 @@ namespace HTTPlease.Formatters { - /// - /// Helper methods for formatters working with streams. - /// - static class StreamHelper - { - /// - /// The default buffer size for s / s created by the helper. - /// - public const int DefaultBufferSize = 1024; + /// + /// Helper methods for formatters working with streams. + /// + static class StreamHelper + { + /// + /// The default buffer size for s / s created by the helper. + /// + public const int DefaultBufferSize = 1024; - /// - /// Create a that, when it is closed, leaves its input stream open. - /// - /// - /// The input stream. - /// - /// - /// The stream's text encoding. - /// - /// - /// An optional buffer size. - /// - /// Defaults to . - /// - /// - /// The . - /// - public static TextReader CreateTransientTextReader(Stream inputStream, Encoding encoding, int bufferSize = DefaultBufferSize) - { - if (inputStream == null) - throw new ArgumentNullException(nameof(inputStream)); + /// + /// Create a that, when it is closed, leaves its input stream open. + /// + /// + /// The input stream. + /// + /// + /// The stream's text encoding. + /// + /// + /// An optional buffer size. + /// + /// Defaults to . + /// + /// + /// The . + /// + public static TextReader CreateTransientTextReader(Stream inputStream, Encoding encoding, int bufferSize = DefaultBufferSize) + { + if (inputStream == null) + throw new ArgumentNullException(nameof(inputStream)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); + if (encoding == null) + throw new ArgumentNullException(nameof(encoding)); - if (bufferSize < DefaultBufferSize) - throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, $"Buffer size cannot be less than {bufferSize} bytes."); + if (bufferSize < DefaultBufferSize) + throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, $"Buffer size cannot be less than {bufferSize} bytes."); - return new StreamReader(inputStream, encoding, - detectEncodingFromByteOrderMarks: false, - bufferSize: bufferSize, - leaveOpen: true - ); - } + return new StreamReader(inputStream, encoding, + detectEncodingFromByteOrderMarks: false, + bufferSize: bufferSize, + leaveOpen: true + ); + } - /// - /// Create a that, when it is closed, leaves its output stream open. - /// - /// - /// The output stream. - /// - /// - /// The stream's text encoding. - /// - /// - /// An optional buffer size. - /// - /// Defaults to . - /// - /// - /// The . - /// - public static TextWriter CreateTransientTextWriter(Stream outputStream, Encoding encoding, int bufferSize = DefaultBufferSize) - { - if (outputStream == null) - throw new ArgumentNullException(nameof(outputStream)); + /// + /// Create a that, when it is closed, leaves its output stream open. + /// + /// + /// The output stream. + /// + /// + /// The stream's text encoding. + /// + /// + /// An optional buffer size. + /// + /// Defaults to . + /// + /// + /// The . + /// + public static TextWriter CreateTransientTextWriter(Stream outputStream, Encoding encoding, int bufferSize = DefaultBufferSize) + { + if (outputStream == null) + throw new ArgumentNullException(nameof(outputStream)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); + if (encoding == null) + throw new ArgumentNullException(nameof(encoding)); - if (bufferSize < DefaultBufferSize) - throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, $"Buffer size cannot be less than {bufferSize} bytes."); + if (bufferSize < DefaultBufferSize) + throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, $"Buffer size cannot be less than {bufferSize} bytes."); - return new StreamWriter(outputStream, encoding, - bufferSize: bufferSize, - leaveOpen: true - ); - } - } + return new StreamWriter(outputStream, encoding, + bufferSize: bufferSize, + leaveOpen: true + ); + } + } } diff --git a/src/HTTPlease.Formatters/WellKnownMediaTypes.cs b/src/HTTPlease.Formatters/WellKnownMediaTypes.cs index c384b94..3325072 100644 --- a/src/HTTPlease.Formatters/WellKnownMediaTypes.cs +++ b/src/HTTPlease.Formatters/WellKnownMediaTypes.cs @@ -1,23 +1,23 @@ namespace HTTPlease.Formatters { - /// - /// Well-known media type constants. - /// - public static class WellKnownMediaTypes - { - /// - /// The JSON media type. - /// - public static readonly string Json = "application/json"; + /// + /// Well-known media type constants. + /// + public static class WellKnownMediaTypes + { + /// + /// The JSON media type. + /// + public static readonly string Json = "application/json"; - /// - /// The plain-text media type. - /// - public static readonly string PlainText = "text/plain"; + /// + /// The plain-text media type. + /// + public static readonly string PlainText = "text/plain"; - /// - /// The JSON media type. - /// - public static readonly string Xml = "text/xml"; - } + /// + /// The JSON media type. + /// + public static readonly string Xml = "text/xml"; + } } diff --git a/src/HTTPlease.Security/Abstractions/HttpRequestAuthenticationProvider.cs b/src/HTTPlease.Security/Abstractions/HttpRequestAuthenticationProvider.cs index ae3c3a8..78e1f2c 100644 --- a/src/HTTPlease.Security/Abstractions/HttpRequestAuthenticationProvider.cs +++ b/src/HTTPlease.Security/Abstractions/HttpRequestAuthenticationProvider.cs @@ -4,33 +4,33 @@ namespace HTTPlease.Security.Abstractions { - using Core.Utilities; + using Core.Utilities; - /// - /// The base class for HTTP request authentication mechanisms. - /// - public abstract class HttpRequestAuthenticationProvider - : DisposableObject, IHttpRequestAuthenticationProvider - { - /// - /// Create new . - /// - protected HttpRequestAuthenticationProvider() - { - } + /// + /// The base class for HTTP request authentication mechanisms. + /// + public abstract class HttpRequestAuthenticationProvider + : DisposableObject, IHttpRequestAuthenticationProvider + { + /// + /// Create new . + /// + protected HttpRequestAuthenticationProvider() + { + } - /// - /// Asynchronously configure an outgoing request message to add information required for authentication. - /// - /// - /// The outgoing HTTP request message. - /// - /// - /// An optional that can be used to cancel the asynchronous operation. - /// - /// - /// A representing the asynchronous operation. - /// - public abstract Task AddAuthenticationAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = new CancellationToken()); + /// + /// Asynchronously configure an outgoing request message to add information required for authentication. + /// + /// + /// The outgoing HTTP request message. + /// + /// + /// An optional that can be used to cancel the asynchronous operation. + /// + /// + /// A representing the asynchronous operation. + /// + public abstract Task AddAuthenticationAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = new CancellationToken()); } } diff --git a/src/HTTPlease.Security/Abstractions/HttpRequestHeaderAuthenticationProvider.cs b/src/HTTPlease.Security/Abstractions/HttpRequestHeaderAuthenticationProvider.cs index 384424b..a3c654e 100644 --- a/src/HTTPlease.Security/Abstractions/HttpRequestHeaderAuthenticationProvider.cs +++ b/src/HTTPlease.Security/Abstractions/HttpRequestHeaderAuthenticationProvider.cs @@ -6,60 +6,60 @@ namespace HTTPlease.Security.Abstractions { - /// - /// The base class for authentication using the HTTP "Authorization" header. - /// - public abstract class HttpRequestHeaderAuthenticationProvider - : HttpRequestAuthenticationProvider + /// + /// The base class for authentication using the HTTP "Authorization" header. + /// + public abstract class HttpRequestHeaderAuthenticationProvider + : HttpRequestAuthenticationProvider { - /// - /// Create new . - /// - protected HttpRequestHeaderAuthenticationProvider() - { - } + /// + /// Create new . + /// + protected HttpRequestHeaderAuthenticationProvider() + { + } - /// - /// The name of the authentication scheme to add to the "Authorization" header. - /// - public abstract string AuthenticationScheme { get; } + /// + /// The name of the authentication scheme to add to the "Authorization" header. + /// + public abstract string AuthenticationScheme { get; } - /// - /// Asynchronously provide an "Authorization" header value for the specified request URI. - /// - /// - /// The HTTP request URI to which the header will apply. - /// - /// - /// An optional that can be used to cancel the asynchronous operation. - /// - /// - /// The authentication parameter value (will be appended to the ). - /// - public abstract Task GetAuthenticationParameterAsync(Uri requestUri, CancellationToken cancellationToken = new CancellationToken()); + /// + /// Asynchronously provide an "Authorization" header value for the specified request URI. + /// + /// + /// The HTTP request URI to which the header will apply. + /// + /// + /// An optional that can be used to cancel the asynchronous operation. + /// + /// + /// The authentication parameter value (will be appended to the ). + /// + public abstract Task GetAuthenticationParameterAsync(Uri requestUri, CancellationToken cancellationToken = new CancellationToken()); - /// - /// Asynchronously configure an outgoing request message to add information required for authentication. - /// - /// - /// The outgoing HTTP request message. - /// - /// - /// An optional that can be used to cancel the asynchronous operation. - /// - /// - /// A representing the asynchronous operation. - /// - public sealed override async Task AddAuthenticationAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = new CancellationToken()) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); + /// + /// Asynchronously configure an outgoing request message to add information required for authentication. + /// + /// + /// The outgoing HTTP request message. + /// + /// + /// An optional that can be used to cancel the asynchronous operation. + /// + /// + /// A representing the asynchronous operation. + /// + public sealed override async Task AddAuthenticationAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = new CancellationToken()) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); - string authenticationParameter = await GetAuthenticationParameterAsync(requestMessage.RequestUri, cancellationToken); - if (authenticationParameter == null) - return; + string authenticationParameter = await GetAuthenticationParameterAsync(requestMessage.RequestUri, cancellationToken); + if (authenticationParameter == null) + return; - requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationScheme, authenticationParameter); - } + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationScheme, authenticationParameter); + } } } diff --git a/src/HTTPlease.Security/Abstractions/IHttpRequestAuthenticationProvider.cs b/src/HTTPlease.Security/Abstractions/IHttpRequestAuthenticationProvider.cs index b98ebf1..4b06fb4 100644 --- a/src/HTTPlease.Security/Abstractions/IHttpRequestAuthenticationProvider.cs +++ b/src/HTTPlease.Security/Abstractions/IHttpRequestAuthenticationProvider.cs @@ -4,23 +4,23 @@ namespace HTTPlease.Security.Abstractions { - /// - /// Represents a mechanism for adding authentication information to HTTP request messages. - /// - public interface IHttpRequestAuthenticationProvider - { - /// - /// Asynchronously configure an outgoing request message to add information required for authentication. - /// - /// - /// The outgoing HTTP request message. - /// - /// - /// An optional that can be used to cancel the asynchronous operation. - /// - /// - /// A representing the asynchronous operation. - /// - Task AddAuthenticationAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default); - } + /// + /// Represents a mechanism for adding authentication information to HTTP request messages. + /// + public interface IHttpRequestAuthenticationProvider + { + /// + /// Asynchronously configure an outgoing request message to add information required for authentication. + /// + /// + /// The outgoing HTTP request message. + /// + /// + /// An optional that can be used to cancel the asynchronous operation. + /// + /// + /// A representing the asynchronous operation. + /// + Task AddAuthenticationAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default); + } } diff --git a/src/HTTPlease.Security/ClientBuilderExtensions.cs b/src/HTTPlease.Security/ClientBuilderExtensions.cs index 741288d..895fc22 100644 --- a/src/HTTPlease.Security/ClientBuilderExtensions.cs +++ b/src/HTTPlease.Security/ClientBuilderExtensions.cs @@ -7,63 +7,63 @@ namespace HTTPlease.Security using MessageHandlers; /// - /// Security-related extension methods for the HTTP client builder. + /// Security-related extension methods for the HTTP client builder. /// public static class ClientBuilderExtensions { - /// - /// Create a copy of the with transparent authentication support for its clients. - /// - /// - /// The HTTP client builder. - /// - /// - /// The used to add authentication to outgoing requests. - /// - /// - /// The new HTTP client builder. - /// - /// - /// In general, this overload should only be used in test scenarios (or where you will only ever have a single HTTP client). - /// It is not good practice to share an between multiple clients. - /// - public static ClientBuilder WithAuthentication(this ClientBuilder clientBuilder, IHttpRequestAuthenticationProvider authenticationProvider) - { - if (clientBuilder == null) - throw new ArgumentNullException(nameof(clientBuilder)); + /// + /// Create a copy of the with transparent authentication support for its clients. + /// + /// + /// The HTTP client builder. + /// + /// + /// The used to add authentication to outgoing requests. + /// + /// + /// The new HTTP client builder. + /// + /// + /// In general, this overload should only be used in test scenarios (or where you will only ever have a single HTTP client). + /// It is not good practice to share an between multiple clients. + /// + public static ClientBuilder WithAuthentication(this ClientBuilder clientBuilder, IHttpRequestAuthenticationProvider authenticationProvider) + { + if (clientBuilder == null) + throw new ArgumentNullException(nameof(clientBuilder)); - if (authenticationProvider == null) - throw new ArgumentNullException(nameof(authenticationProvider)); + if (authenticationProvider == null) + throw new ArgumentNullException(nameof(authenticationProvider)); - return clientBuilder.WithAuthentication(() => authenticationProvider); - } + return clientBuilder.WithAuthentication(() => authenticationProvider); + } - /// - /// Create a copy of the with transparent authentication support for its clients. - /// - /// - /// The HTTP client builder. - /// - /// - /// A/// delegate that creates a new for each produced by the . - /// - /// - /// The new HTTP client builder. - /// - public static ClientBuilder WithAuthentication(this ClientBuilder clientBuilder, Func authenticationProviderFactory) - { - if (clientBuilder == null) - throw new ArgumentNullException(nameof(clientBuilder)); + /// + /// Create a copy of the with transparent authentication support for its clients. + /// + /// + /// The HTTP client builder. + /// + /// + /// A/// delegate that creates a new for each produced by the . + /// + /// + /// The new HTTP client builder. + /// + public static ClientBuilder WithAuthentication(this ClientBuilder clientBuilder, Func authenticationProviderFactory) + { + if (clientBuilder == null) + throw new ArgumentNullException(nameof(clientBuilder)); - if (authenticationProviderFactory == null) - throw new ArgumentNullException(nameof(authenticationProviderFactory)); + if (authenticationProviderFactory == null) + throw new ArgumentNullException(nameof(authenticationProviderFactory)); - return clientBuilder.AddHandler(() => - { - IHttpRequestAuthenticationProvider authenticationProvider = authenticationProviderFactory(); + return clientBuilder.AddHandler(() => + { + IHttpRequestAuthenticationProvider authenticationProvider = authenticationProviderFactory(); - return new AuthenticationMessageHandler(authenticationProvider); - }); - } - } + return new AuthenticationMessageHandler(authenticationProvider); + }); + } + } } diff --git a/src/HTTPlease.Security/MessageHandlers/AuthenticationMessageHandler.cs b/src/HTTPlease.Security/MessageHandlers/AuthenticationMessageHandler.cs index 82eebf8..0274ccf 100644 --- a/src/HTTPlease.Security/MessageHandlers/AuthenticationMessageHandler.cs +++ b/src/HTTPlease.Security/MessageHandlers/AuthenticationMessageHandler.cs @@ -5,71 +5,71 @@ namespace HTTPlease.Security.MessageHandlers { - using Abstractions; + using Abstractions; - /// - /// A HTTP message handler that adds authentication to outgoing request messages. - /// - public sealed class AuthenticationMessageHandler - : DelegatingHandler - { - /// - /// The authentication provider for outgoing requests. - /// - readonly IHttpRequestAuthenticationProvider _authenticationProvider; + /// + /// A HTTP message handler that adds authentication to outgoing request messages. + /// + public sealed class AuthenticationMessageHandler + : DelegatingHandler + { + /// + /// The authentication provider for outgoing requests. + /// + readonly IHttpRequestAuthenticationProvider _authenticationProvider; - /// - /// Create a new that uses the specified provider for authentication. - /// - /// - /// The authentication provider for outgoing requests. - /// - public AuthenticationMessageHandler(IHttpRequestAuthenticationProvider authenticationProvider) - { - if (authenticationProvider == null) - throw new ArgumentNullException(nameof(authenticationProvider)); + /// + /// Create a new that uses the specified provider for authentication. + /// + /// + /// The authentication provider for outgoing requests. + /// + public AuthenticationMessageHandler(IHttpRequestAuthenticationProvider authenticationProvider) + { + if (authenticationProvider == null) + throw new ArgumentNullException(nameof(authenticationProvider)); - _authenticationProvider = authenticationProvider; - } + _authenticationProvider = authenticationProvider; + } - /// - /// Dispose of resources being used by the . - /// - /// - /// Explicit disposal? - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - IDisposable providerDisposal = _authenticationProvider as IDisposable; - if (providerDisposal != null) - providerDisposal.Dispose(); - } + /// + /// Dispose of resources being used by the . + /// + /// + /// Explicit disposal? + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + IDisposable providerDisposal = _authenticationProvider as IDisposable; + if (providerDisposal != null) + providerDisposal.Dispose(); + } - base.Dispose(disposing); - } + base.Dispose(disposing); + } - /// - /// Asynchronously process an HTTP request message and its response. - /// - /// - /// The outgoing . - /// - /// - /// A that can be used to cancel the asynchronous operation. - /// - /// - /// The incoming HTTP response message. - /// - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - if (cancellationToken == null) - throw new ArgumentNullException(nameof(cancellationToken)); + /// + /// Asynchronously process an HTTP request message and its response. + /// + /// + /// The outgoing . + /// + /// + /// A that can be used to cancel the asynchronous operation. + /// + /// + /// The incoming HTTP response message. + /// + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (cancellationToken == null) + throw new ArgumentNullException(nameof(cancellationToken)); - await _authenticationProvider.AddAuthenticationAsync(request, cancellationToken); - - return await base.SendAsync(request, cancellationToken); - } - } + await _authenticationProvider.AddAuthenticationAsync(request, cancellationToken); + + return await base.SendAsync(request, cancellationToken); + } + } } diff --git a/src/HTTPlease.Testability.Xunit/MessageAssert.cs b/src/HTTPlease.Testability.Xunit/MessageAssert.cs index 6351854..0b507cb 100644 --- a/src/HTTPlease.Testability.Xunit/MessageAssert.cs +++ b/src/HTTPlease.Testability.Xunit/MessageAssert.cs @@ -5,101 +5,101 @@ namespace HTTPlease.Testability { - /// - /// Assertion functionality for HTTP request / response message generated by / . - /// - public static class MessageAssert + /// + /// Assertion functionality for HTTP request / response message generated by / . + /// + public static class MessageAssert { - /// - /// Assert that the request message has the specified URI. - /// - /// - /// The . - /// - /// - /// The expected URI. - /// - public static void HasRequestUri(HttpRequestMessage requestMessage, string expectedUri) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); + /// + /// Assert that the request message has the specified URI. + /// + /// + /// The . + /// + /// + /// The expected URI. + /// + public static void HasRequestUri(HttpRequestMessage requestMessage, string expectedUri) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); - if (String.IsNullOrWhiteSpace(expectedUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'expectedUri'.", nameof(expectedUri)); + if (String.IsNullOrWhiteSpace(expectedUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'expectedUri'.", nameof(expectedUri)); - HasRequestUri(requestMessage, - new Uri(expectedUri, UriKind.RelativeOrAbsolute) - ); - } + HasRequestUri(requestMessage, + new Uri(expectedUri, UriKind.RelativeOrAbsolute) + ); + } - /// - /// Assert that the request message has the specified URI. - /// - /// - /// The . - /// - /// - /// The expected URI. - /// - public static void HasRequestUri(HttpRequestMessage requestMessage, Uri expectedUri) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); + /// + /// Assert that the request message has the specified URI. + /// + /// + /// The . + /// + /// + /// The expected URI. + /// + public static void HasRequestUri(HttpRequestMessage requestMessage, Uri expectedUri) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); - Assert.Equal(expectedUri, - requestMessage.RequestUri - ); - } + Assert.Equal(expectedUri, + requestMessage.RequestUri + ); + } - /// - /// Assert that the request message's Accept header contains the specified media type. - /// - /// - /// The . - /// - /// - /// The expected media type. - /// - public static void AcceptsMediaType(HttpRequestMessage requestMessage, string mediaType) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); + /// + /// Assert that the request message's Accept header contains the specified media type. + /// + /// + /// The . + /// + /// + /// The expected media type. + /// + public static void AcceptsMediaType(HttpRequestMessage requestMessage, string mediaType) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); - if (String.IsNullOrWhiteSpace(mediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); + if (String.IsNullOrWhiteSpace(mediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); - Assert.Contains(requestMessage.Headers.Accept, - accept => accept.MediaType == mediaType - ); - } + Assert.Contains(requestMessage.Headers.Accept, + accept => accept.MediaType == mediaType + ); + } - /// - /// Asynchronously assert that the request message body is equal to the specified string. - /// - /// - /// The HTTP request message to examine. - /// - /// - /// A string containing the expected message body. - /// - /// - /// The actual message body. - /// - public static async Task BodyIsAsync(HttpRequestMessage requestMessage, string expectedBody) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); + /// + /// Asynchronously assert that the request message body is equal to the specified string. + /// + /// + /// The HTTP request message to examine. + /// + /// + /// A string containing the expected message body. + /// + /// + /// The actual message body. + /// + public static async Task BodyIsAsync(HttpRequestMessage requestMessage, string expectedBody) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); - string actualBody = null; - if (requestMessage.Content != null) - actualBody = await requestMessage.Content.ReadAsStringAsync(); + string actualBody = null; + if (requestMessage.Content != null) + actualBody = await requestMessage.Content.ReadAsStringAsync(); - Assert.Equal(expectedBody, actualBody); + Assert.Equal(expectedBody, actualBody); - return actualBody; - } - } + return actualBody; + } + } } diff --git a/src/HTTPlease.Testability.Xunit/MessageExtensions.cs b/src/HTTPlease.Testability.Xunit/MessageExtensions.cs index e6b6b5c..e87cb89 100644 --- a/src/HTTPlease.Testability.Xunit/MessageExtensions.cs +++ b/src/HTTPlease.Testability.Xunit/MessageExtensions.cs @@ -4,126 +4,126 @@ namespace HTTPlease.Testability { - using Formatters; + using Formatters; - /// - /// Extension methods for / . - /// - public static class MessageExtensions - { - /// - /// Create a response message with an status code. - /// - /// - /// The response message. - /// - /// - /// The configured response message. - /// - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Extension methods for / . + /// + public static class MessageExtensions + { + /// + /// Create a response message with an status code. + /// + /// + /// The response message. + /// + /// + /// The configured response message. + /// + public static HttpResponseMessage CreateResponse(this HttpRequestMessage request) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return request.CreateResponse(HttpStatusCode.OK); - } + return request.CreateResponse(HttpStatusCode.OK); + } - /// - /// Create a response message. - /// - /// - /// The response message. - /// - /// - /// The response status code. - /// - /// - /// The configured response message. - /// - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a response message. + /// + /// + /// The response message. + /// + /// + /// The response status code. + /// + /// + /// The configured response message. + /// + public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - HttpResponseMessage response = new HttpResponseMessage(statusCode); - try - { - response.RequestMessage = request; - } - catch - { - using (response) - { - throw; - } - } + HttpResponseMessage response = new HttpResponseMessage(statusCode); + try + { + response.RequestMessage = request; + } + catch + { + using (response) + { + throw; + } + } - return response; - } + return response; + } - /// - /// Create a response message. - /// - /// - /// The response message. - /// - /// - /// The response status code. - /// - /// - /// The response body (media type will be "text/plain"). - /// - /// - /// The configured response message. - /// - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string responseBody) - { - return CreateResponse(request, statusCode, responseBody, WellKnownMediaTypes.PlainText); - } + /// + /// Create a response message. + /// + /// + /// The response message. + /// + /// + /// The response status code. + /// + /// + /// The response body (media type will be "text/plain"). + /// + /// + /// The configured response message. + /// + public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string responseBody) + { + return CreateResponse(request, statusCode, responseBody, WellKnownMediaTypes.PlainText); + } - /// - /// Create a response message. - /// - /// - /// The response message. - /// - /// - /// The response status code. - /// - /// - /// The response body. - /// - /// - /// The response media type. - /// - /// - /// The configured response message. - /// - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string responseBody, string mediaType) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Create a response message. + /// + /// + /// The response message. + /// + /// + /// The response status code. + /// + /// + /// The response body. + /// + /// + /// The response media type. + /// + /// + /// The configured response message. + /// + public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string responseBody, string mediaType) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (responseBody != null && String.IsNullOrWhiteSpace(mediaType)) - throw new ArgumentException("Must specify a valid media type if specifying a response body.", nameof(mediaType)); + if (responseBody != null && String.IsNullOrWhiteSpace(mediaType)) + throw new ArgumentException("Must specify a valid media type if specifying a response body.", nameof(mediaType)); - HttpResponseMessage response = request.CreateResponse(statusCode); - if (responseBody == null) - return response; + HttpResponseMessage response = request.CreateResponse(statusCode); + if (responseBody == null) + return response; - try - { - response.Content = new StringContent(responseBody, OutputEncoding.UTF8, mediaType); - } - catch - { - using (response) - { - throw; - } - } + try + { + response.Content = new StringContent(responseBody, OutputEncoding.UTF8, mediaType); + } + catch + { + using (response) + { + throw; + } + } - return response; - } - } + return response; + } + } } diff --git a/src/HTTPlease.Testability.Xunit/Mocks/MockMessageHandler.cs b/src/HTTPlease.Testability.Xunit/Mocks/MockMessageHandler.cs index a4d394c..080030f 100644 --- a/src/HTTPlease.Testability.Xunit/Mocks/MockMessageHandler.cs +++ b/src/HTTPlease.Testability.Xunit/Mocks/MockMessageHandler.cs @@ -6,83 +6,83 @@ namespace HTTPlease.Testability.Mocks { - /// - /// Mock that calls an arbitrary delegate to receive and respond to a message. - /// - public sealed class MockMessageHandler - : DelegatingHandler - { - /// - /// The handler implementation. - /// - readonly Func> _handlerImplementation; + /// + /// Mock that calls an arbitrary delegate to receive and respond to a message. + /// + public sealed class MockMessageHandler + : DelegatingHandler + { + /// + /// The handler implementation. + /// + readonly Func> _handlerImplementation; - /// - /// Create a new mock message handler. - /// - /// - /// The handler implementation. - /// - public MockMessageHandler(Func handlerImplementation) - { - if (handlerImplementation == null) - throw new ArgumentNullException(nameof(handlerImplementation)); + /// + /// Create a new mock message handler. + /// + /// + /// The handler implementation. + /// + public MockMessageHandler(Func handlerImplementation) + { + if (handlerImplementation == null) + throw new ArgumentNullException(nameof(handlerImplementation)); - _handlerImplementation = request => Task.Factory.StartNew( - () => handlerImplementation(request) - ); - } + _handlerImplementation = request => Task.Factory.StartNew( + () => handlerImplementation(request) + ); + } - /// - /// Create a new mock message handler. - /// - /// - /// The handler implementation. - /// - public MockMessageHandler(Func> handlerImplementation) - { - if (handlerImplementation == null) - throw new ArgumentNullException(nameof(handlerImplementation)); + /// + /// Create a new mock message handler. + /// + /// + /// The handler implementation. + /// + public MockMessageHandler(Func> handlerImplementation) + { + if (handlerImplementation == null) + throw new ArgumentNullException(nameof(handlerImplementation)); - _handlerImplementation = handlerImplementation; - } + _handlerImplementation = handlerImplementation; + } - /// - /// Asynchronously handle a request - /// - /// - /// The request message. - /// - /// - /// A cancellation token that can be used to cancel the operation. - /// - /// - /// A representing the asynchronous operation, whose result is the response message. - /// - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Asynchronously handle a request + /// + /// + /// The request message. + /// + /// + /// A cancellation token that can be used to cancel the operation. + /// + /// + /// A representing the asynchronous operation, whose result is the response message. + /// + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - return await _handlerImplementation(request); - } + return await _handlerImplementation(request); + } - /// - /// Create an that wraps the . - /// - /// - /// The new - /// - public HttpClient CreateClient() => new HttpClient(this); - - /// - /// Create an instance of the default mock message handler (responds to any request with ). - /// - public static MockMessageHandler Default() - { - return new MockMessageHandler( - request => request.CreateResponse(HttpStatusCode.OK) - ); - } - } + /// + /// Create an that wraps the . + /// + /// + /// The new + /// + public HttpClient CreateClient() => new HttpClient(this); + + /// + /// Create an instance of the default mock message handler (responds to any request with ). + /// + public static MockMessageHandler Default() + { + return new MockMessageHandler( + request => request.CreateResponse(HttpStatusCode.OK) + ); + } + } } diff --git a/src/HTTPlease.Testability.Xunit/RequestAssert.cs b/src/HTTPlease.Testability.Xunit/RequestAssert.cs index 7958ec2..ff269cc 100644 --- a/src/HTTPlease.Testability.Xunit/RequestAssert.cs +++ b/src/HTTPlease.Testability.Xunit/RequestAssert.cs @@ -4,672 +4,672 @@ namespace HTTPlease.Testability { - /// - /// Assertion functionality for / . - /// - public static class RequestAssert + /// + /// Assertion functionality for / . + /// + public static class RequestAssert { - #region Untyped requests - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The . - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method and no base URI. - /// - public static void MessageHasUri(HttpRequest request, string expectedUri) - { - MessageHasUri(request, HttpMethod.Get, - baseUri: null, - expectedUri: expectedUri - ); - } - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The . - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method and no base URI. - /// - public static void MessageHasUri(HttpRequest request, Uri expectedUri) - { - MessageHasUri(request, HttpMethod.Get, - baseUri: null, - expectedUri: expectedUri - ); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The . - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method. - /// - public static void MessageHasUri(HttpRequest request, Uri baseUri, string expectedUri) - { - MessageHasUri(request, HttpMethod.Get, baseUri, expectedUri); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The . - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method. - /// - public static void MessageHasUri(HttpRequest request, Uri baseUri, Uri expectedUri) - { - MessageHasUri(request, HttpMethod.Get, baseUri, expectedUri); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - public static void MessageHasUri(HttpRequest request, HttpMethod method, Uri baseUri, string expectedUri) - { - if (String.IsNullOrWhiteSpace(expectedUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'expectedUri'.", nameof(expectedUri)); - - Message(request, method, baseUri, requestMessage => - { - MessageAssert.HasRequestUri(requestMessage, expectedUri); - }); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - public static void MessageHasUri(HttpRequest request, HttpMethod method, Uri baseUri, Uri expectedUri) - { - Message(request, method, baseUri, requestMessage => - { - MessageAssert.HasRequestUri(requestMessage, expectedUri); - }); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, Action assertion) - { - Message(request, method, - bodyContent: null, - baseUri: null, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The request base URI. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, Uri baseUri, Action assertion) - { - Message(request, method, - bodyContent: null, - baseUri: baseUri, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// representing the request body content. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, HttpContent bodyContent, Action assertion) - { - Message(request, method, bodyContent, null, assertion); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// representing the request body content. - /// - /// - /// An optional base URI for the request. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, HttpContent bodyContent, Uri baseUri, Action assertion) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (assertion == null) - throw new ArgumentNullException(nameof(assertion)); - - if (method == null) - throw new ArgumentNullException(nameof(method)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, bodyContent, baseUri)) - { - assertion(requestMessage); - } - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// representing the request body content. - /// - /// - /// An optional base URI for the request. - /// - /// - /// An asynchronous delegate that makes assertions about the . - /// - public static async Task MessageAsync(HttpRequest request, HttpMethod method, HttpContent bodyContent, Uri baseUri, Func asyncAssertion) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (asyncAssertion == null) - throw new ArgumentNullException(nameof(asyncAssertion)); - - if (method == null) - throw new ArgumentNullException(nameof(method)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, bodyContent, baseUri)) - { - await asyncAssertion(requestMessage); - } - } - - #endregion // Untyped requests - - #region Typed requests - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The instance used as a context for resolving deferred values for each request. - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method and no base URI. - /// - public static void MessageHasUri(HttpRequest request, TContext context, string expectedUri) - { - MessageHasUri(request, HttpMethod.Get, context, - baseUri: null, - expectedUri: expectedUri - ); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The instance used as a context for resolving deferred values for each request. - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method and no base URI. - /// - public static void MessageHasUri(HttpRequest request, TContext context, Uri expectedUri) - { - MessageHasUri(request, HttpMethod.Get, context, - baseUri: null, - expectedUri: expectedUri - ); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The instance used as a context for resolving deferred values for each request. - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method. - /// - public static void MessageHasUri(HttpRequest request, TContext context, Uri baseUri, string expectedUri) - { - MessageHasUri(request, HttpMethod.Get, context, baseUri, expectedUri); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The instance used as a context for resolving deferred values for each request. - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - /// - /// Uses the HTTP GET method. - /// - public static void MessageHasUri(HttpRequest request, TContext context, Uri baseUri, Uri expectedUri) - { - MessageHasUri(request, HttpMethod.Get, context, baseUri, expectedUri); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values for each request. - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - public static void MessageHasUri(HttpRequest request, HttpMethod method, TContext context, Uri baseUri, string expectedUri) - { - if (String.IsNullOrWhiteSpace(expectedUri)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'expectedUri'.", nameof(expectedUri)); - - Message(request, method, context, baseUri, requestMessage => - { - MessageAssert.HasRequestUri(requestMessage, expectedUri); - }); - } - - /// - /// Assert that the generated by the has the specified URI. - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values for each request. - /// - /// - /// The base URI for the request. - /// - /// - /// The expected URI. - /// - public static void MessageHasUri(HttpRequest request, HttpMethod method, TContext context, Uri baseUri, Uri expectedUri) - { - Message(request, method, context, baseUri, requestMessage => - { - MessageAssert.HasRequestUri(requestMessage, expectedUri); - }); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// A delegate that makes assertions about the . - /// - /// - /// Uses the default value for . - /// - public static void Message(HttpRequest request, HttpMethod method, Action assertion) - { - Message(request, method, - context: default(TContext), - bodyContent: null, - baseUri: null, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The request base URI. - /// - /// - /// A delegate that makes assertions about the . - /// - /// - /// Uses the default value for . - /// - public static void Message(HttpRequest request, HttpMethod method, Uri baseUri, Action assertion) - { - Message(request, method, - context: default(TContext), - bodyContent: null, - baseUri: baseUri, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, TContext context, Action assertion) - { - Message(request, method, context, - bodyContent: null, - baseUri: null, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values. - /// - /// - /// The request base URI. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, TContext context, Uri baseUri, Action assertion) - { - Message(request, method, context, - bodyContent: null, - baseUri: baseUri, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values. - /// - /// - /// representing the request body content. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, TContext context, HttpContent bodyContent, Action assertion) - { - Message(request, method, context, bodyContent, - baseUri: null, - assertion: assertion - ); - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values. - /// - /// - /// representing the request body content. - /// - /// - /// An optional base URI for the request. - /// - /// - /// A delegate that makes assertions about the . - /// - public static void Message(HttpRequest request, HttpMethod method, TContext context, HttpContent bodyContent, Uri baseUri, Action assertion) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (assertion == null) - throw new ArgumentNullException(nameof(assertion)); - - if (method == null) - throw new ArgumentNullException(nameof(method)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, context, bodyContent, baseUri)) - { - assertion(requestMessage); - } - } - - /// - /// Make assertions about the generated by the . - /// - /// - /// The type of object used as a context for resolving deferred parameters. - /// - /// - /// The . - /// - /// - /// The HTTP method (e.g. GET / POST / PUT). - /// - /// - /// The instance used as a context for resolving deferred values. - /// - /// - /// representing the request body content. - /// - /// - /// An optional base URI for the request. - /// - /// - /// An asynchronous delegate that makes assertions about the . - /// - public static async Task MessageAsync(HttpRequest request, HttpMethod method, TContext context, HttpContent bodyContent, Uri baseUri, Func asyncAssertion) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); - - if (asyncAssertion == null) - throw new ArgumentNullException(nameof(asyncAssertion)); - - if (method == null) - throw new ArgumentNullException(nameof(method)); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, context, bodyContent, baseUri)) - { - await asyncAssertion(requestMessage); - } - } - - #endregion // Typed requests - } + #region Untyped requests + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The . + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method and no base URI. + /// + public static void MessageHasUri(HttpRequest request, string expectedUri) + { + MessageHasUri(request, HttpMethod.Get, + baseUri: null, + expectedUri: expectedUri + ); + } + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The . + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method and no base URI. + /// + public static void MessageHasUri(HttpRequest request, Uri expectedUri) + { + MessageHasUri(request, HttpMethod.Get, + baseUri: null, + expectedUri: expectedUri + ); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The . + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method. + /// + public static void MessageHasUri(HttpRequest request, Uri baseUri, string expectedUri) + { + MessageHasUri(request, HttpMethod.Get, baseUri, expectedUri); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The . + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method. + /// + public static void MessageHasUri(HttpRequest request, Uri baseUri, Uri expectedUri) + { + MessageHasUri(request, HttpMethod.Get, baseUri, expectedUri); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + public static void MessageHasUri(HttpRequest request, HttpMethod method, Uri baseUri, string expectedUri) + { + if (String.IsNullOrWhiteSpace(expectedUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'expectedUri'.", nameof(expectedUri)); + + Message(request, method, baseUri, requestMessage => + { + MessageAssert.HasRequestUri(requestMessage, expectedUri); + }); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + public static void MessageHasUri(HttpRequest request, HttpMethod method, Uri baseUri, Uri expectedUri) + { + Message(request, method, baseUri, requestMessage => + { + MessageAssert.HasRequestUri(requestMessage, expectedUri); + }); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, Action assertion) + { + Message(request, method, + bodyContent: null, + baseUri: null, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The request base URI. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, Uri baseUri, Action assertion) + { + Message(request, method, + bodyContent: null, + baseUri: baseUri, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// representing the request body content. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, HttpContent bodyContent, Action assertion) + { + Message(request, method, bodyContent, null, assertion); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// representing the request body content. + /// + /// + /// An optional base URI for the request. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, HttpContent bodyContent, Uri baseUri, Action assertion) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (assertion == null) + throw new ArgumentNullException(nameof(assertion)); + + if (method == null) + throw new ArgumentNullException(nameof(method)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, bodyContent, baseUri)) + { + assertion(requestMessage); + } + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// representing the request body content. + /// + /// + /// An optional base URI for the request. + /// + /// + /// An asynchronous delegate that makes assertions about the . + /// + public static async Task MessageAsync(HttpRequest request, HttpMethod method, HttpContent bodyContent, Uri baseUri, Func asyncAssertion) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (asyncAssertion == null) + throw new ArgumentNullException(nameof(asyncAssertion)); + + if (method == null) + throw new ArgumentNullException(nameof(method)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, bodyContent, baseUri)) + { + await asyncAssertion(requestMessage); + } + } + + #endregion // Untyped requests + + #region Typed requests + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The instance used as a context for resolving deferred values for each request. + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method and no base URI. + /// + public static void MessageHasUri(HttpRequest request, TContext context, string expectedUri) + { + MessageHasUri(request, HttpMethod.Get, context, + baseUri: null, + expectedUri: expectedUri + ); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The instance used as a context for resolving deferred values for each request. + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method and no base URI. + /// + public static void MessageHasUri(HttpRequest request, TContext context, Uri expectedUri) + { + MessageHasUri(request, HttpMethod.Get, context, + baseUri: null, + expectedUri: expectedUri + ); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The instance used as a context for resolving deferred values for each request. + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method. + /// + public static void MessageHasUri(HttpRequest request, TContext context, Uri baseUri, string expectedUri) + { + MessageHasUri(request, HttpMethod.Get, context, baseUri, expectedUri); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The instance used as a context for resolving deferred values for each request. + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + /// + /// Uses the HTTP GET method. + /// + public static void MessageHasUri(HttpRequest request, TContext context, Uri baseUri, Uri expectedUri) + { + MessageHasUri(request, HttpMethod.Get, context, baseUri, expectedUri); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values for each request. + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + public static void MessageHasUri(HttpRequest request, HttpMethod method, TContext context, Uri baseUri, string expectedUri) + { + if (String.IsNullOrWhiteSpace(expectedUri)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'expectedUri'.", nameof(expectedUri)); + + Message(request, method, context, baseUri, requestMessage => + { + MessageAssert.HasRequestUri(requestMessage, expectedUri); + }); + } + + /// + /// Assert that the generated by the has the specified URI. + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values for each request. + /// + /// + /// The base URI for the request. + /// + /// + /// The expected URI. + /// + public static void MessageHasUri(HttpRequest request, HttpMethod method, TContext context, Uri baseUri, Uri expectedUri) + { + Message(request, method, context, baseUri, requestMessage => + { + MessageAssert.HasRequestUri(requestMessage, expectedUri); + }); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// A delegate that makes assertions about the . + /// + /// + /// Uses the default value for . + /// + public static void Message(HttpRequest request, HttpMethod method, Action assertion) + { + Message(request, method, + context: default(TContext), + bodyContent: null, + baseUri: null, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The request base URI. + /// + /// + /// A delegate that makes assertions about the . + /// + /// + /// Uses the default value for . + /// + public static void Message(HttpRequest request, HttpMethod method, Uri baseUri, Action assertion) + { + Message(request, method, + context: default(TContext), + bodyContent: null, + baseUri: baseUri, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, TContext context, Action assertion) + { + Message(request, method, context, + bodyContent: null, + baseUri: null, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values. + /// + /// + /// The request base URI. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, TContext context, Uri baseUri, Action assertion) + { + Message(request, method, context, + bodyContent: null, + baseUri: baseUri, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values. + /// + /// + /// representing the request body content. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, TContext context, HttpContent bodyContent, Action assertion) + { + Message(request, method, context, bodyContent, + baseUri: null, + assertion: assertion + ); + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values. + /// + /// + /// representing the request body content. + /// + /// + /// An optional base URI for the request. + /// + /// + /// A delegate that makes assertions about the . + /// + public static void Message(HttpRequest request, HttpMethod method, TContext context, HttpContent bodyContent, Uri baseUri, Action assertion) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (assertion == null) + throw new ArgumentNullException(nameof(assertion)); + + if (method == null) + throw new ArgumentNullException(nameof(method)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, context, bodyContent, baseUri)) + { + assertion(requestMessage); + } + } + + /// + /// Make assertions about the generated by the . + /// + /// + /// The type of object used as a context for resolving deferred parameters. + /// + /// + /// The . + /// + /// + /// The HTTP method (e.g. GET / POST / PUT). + /// + /// + /// The instance used as a context for resolving deferred values. + /// + /// + /// representing the request body content. + /// + /// + /// An optional base URI for the request. + /// + /// + /// An asynchronous delegate that makes assertions about the . + /// + public static async Task MessageAsync(HttpRequest request, HttpMethod method, TContext context, HttpContent bodyContent, Uri baseUri, Func asyncAssertion) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + if (asyncAssertion == null) + throw new ArgumentNullException(nameof(asyncAssertion)); + + if (method == null) + throw new ArgumentNullException(nameof(method)); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(method, context, bodyContent, baseUri)) + { + await asyncAssertion(requestMessage); + } + } + + #endregion // Typed requests + } } diff --git a/src/HTTPlease.Testability.Xunit/TestClients.cs b/src/HTTPlease.Testability.Xunit/TestClients.cs index f99e41b..61b601e 100644 --- a/src/HTTPlease.Testability.Xunit/TestClients.cs +++ b/src/HTTPlease.Testability.Xunit/TestClients.cs @@ -6,390 +6,390 @@ namespace HTTPlease.Testability { - using Mocks; + using Mocks; - /// - /// Factory methods for mocked s used by tests. - /// + /// + /// Factory methods for mocked s used by tests. + /// public static class TestClients - { - /// - /// Create an that always responds to requests with the status code. - /// - /// - /// The configured . - /// - public static HttpClient RespondWithOk() - { - return TestHandlers.RespondWith(HttpStatusCode.OK).CreateClient(); - } + { + /// + /// Create an that always responds to requests with the status code. + /// + /// + /// The configured . + /// + public static HttpClient RespondWithOk() + { + return TestHandlers.RespondWith(HttpStatusCode.OK).CreateClient(); + } - /// - /// Create an that always responds to requests with the status code. - /// - /// - /// The configured . - /// - public static HttpClient RespondWithBadRequest() - { - return TestHandlers.RespondWith(HttpStatusCode.BadRequest).CreateClient(); - } + /// + /// Create an that always responds to requests with the status code. + /// + /// + /// The configured . + /// + public static HttpClient RespondWithBadRequest() + { + return TestHandlers.RespondWith(HttpStatusCode.BadRequest).CreateClient(); + } - /// - /// Create an that always responds to requests with the status code. - /// - /// - /// A string to be used as the response message body. - /// - /// - /// The response media type. - /// - /// - /// The configured . - /// - public static HttpClient RespondWithBadRequest(string responseBody, string responseMediaType) - { - if (String.IsNullOrWhiteSpace(responseMediaType)) - throw new ArgumentException("Must specify a valid media type.", nameof(responseMediaType)); + /// + /// Create an that always responds to requests with the status code. + /// + /// + /// A string to be used as the response message body. + /// + /// + /// The response media type. + /// + /// + /// The configured . + /// + public static HttpClient RespondWithBadRequest(string responseBody, string responseMediaType) + { + if (String.IsNullOrWhiteSpace(responseMediaType)) + throw new ArgumentException("Must specify a valid media type.", nameof(responseMediaType)); - return - TestHandlers.RespondWith( - request => request.CreateResponse(HttpStatusCode.BadRequest, responseBody, responseMediaType) - ) - .CreateClient(); - } + return + TestHandlers.RespondWith( + request => request.CreateResponse(HttpStatusCode.BadRequest, responseBody, responseMediaType) + ) + .CreateClient(); + } - /// - /// Create an that always responds to requests with the specified status code. - /// - /// - /// The HTTP status code. - /// - /// - /// The configured . - /// - public static HttpClient RespondWith(HttpStatusCode statusCode) - { - return - TestHandlers.RespondWith( - request => request.CreateResponse(statusCode) - ) - .CreateClient(); - } + /// + /// Create an that always responds to requests with the specified status code. + /// + /// + /// The HTTP status code. + /// + /// + /// The configured . + /// + public static HttpClient RespondWith(HttpStatusCode statusCode) + { + return + TestHandlers.RespondWith( + request => request.CreateResponse(statusCode) + ) + .CreateClient(); + } - /// - /// Create an that calls the specified delegate to synchronously respond to requests. - /// - /// - /// A delegate that takes an incoming and returns an outgoing . - /// - /// - /// The configured . - /// - public static HttpClient RespondWith(Func handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Create an that calls the specified delegate to synchronously respond to requests. + /// + /// + /// A delegate that takes an incoming and returns an outgoing . + /// + /// + /// The configured . + /// + public static HttpClient RespondWith(Func handler) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); - return TestHandlers.RespondWith(handler).CreateClient(); - } + return TestHandlers.RespondWith(handler).CreateClient(); + } - /// - /// Create an that calls the specified delegate to asynchronously respond to requests. - /// - /// - /// A delegate that takes an incoming and asynchronously returns an outgoing . - /// - /// - /// The configured . - /// - public static HttpClient AsyncRespondWith(Func> handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Create an that calls the specified delegate to asynchronously respond to requests. + /// + /// + /// A delegate that takes an incoming and asynchronously returns an outgoing . + /// + /// + /// The configured . + /// + public static HttpClient AsyncRespondWith(Func> handler) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); - return TestHandlers.AsyncRespondWith(handler).CreateClient(); - } + return TestHandlers.AsyncRespondWith(handler).CreateClient(); + } - /// - /// Create an that expects an incoming GET request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectGet(Uri expectedRequestUri) - { - return TestHandlers.ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); - } + /// + /// Create an that expects an incoming GET request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectGet(Uri expectedRequestUri) + { + return TestHandlers.ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming GET request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectGet(Uri expectedRequestUri, Action assertion) - { - return TestHandlers.ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming GET request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectGet(Uri expectedRequestUri, Action assertion) + { + return TestHandlers.ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming GET request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectGet(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return TestHandlers.Expect(expectedRequestUri, HttpMethod.Get, responseStatusCode, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming GET request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectGet(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return TestHandlers.Expect(expectedRequestUri, HttpMethod.Get, responseStatusCode, assertion).CreateClient(); + } - /// - /// Create an that expects an incoming POST request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectPost(Uri expectedRequestUri) - { - return TestHandlers.ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); - } + /// + /// Create an that expects an incoming POST request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectPost(Uri expectedRequestUri) + { + return TestHandlers.ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming POST request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectPost(Uri expectedRequestUri, Action assertion) - { - return TestHandlers.ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming POST request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectPost(Uri expectedRequestUri, Action assertion) + { + return TestHandlers.ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming POST request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectPost(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return TestHandlers.Expect(expectedRequestUri, HttpMethod.Post, responseStatusCode, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming POST request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectPost(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return TestHandlers.Expect(expectedRequestUri, HttpMethod.Post, responseStatusCode, assertion).CreateClient(); + } - /// - /// Create an that expects an incoming PUT request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectPut(Uri expectedRequestUri) - { - return TestHandlers.ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); - } + /// + /// Create an that expects an incoming PUT request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectPut(Uri expectedRequestUri) + { + return TestHandlers.ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming PUT request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectPut(Uri expectedRequestUri, Action assertion) - { - return TestHandlers.ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming PUT request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectPut(Uri expectedRequestUri, Action assertion) + { + return TestHandlers.ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming PUT request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectPut(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return TestHandlers.Expect(expectedRequestUri, HttpMethod.Put, responseStatusCode, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming PUT request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectPut(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return TestHandlers.Expect(expectedRequestUri, HttpMethod.Put, responseStatusCode, assertion).CreateClient(); + } - /// - /// Create an that expects an incoming DELETE request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectDelete(Uri expectedRequestUri) - { - return TestHandlers.ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); - } + /// + /// Create an that expects an incoming DELETE request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectDelete(Uri expectedRequestUri) + { + return TestHandlers.ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion: null).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming DELETE request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectDelete(Uri expectedRequestUri, Action assertion) - { - return TestHandlers.ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming DELETE request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectDelete(Uri expectedRequestUri, Action assertion) + { + return TestHandlers.ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming DELETE request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectDelete(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return TestHandlers.Expect(expectedRequestUri, HttpMethod.Delete, responseStatusCode, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming DELETE request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectDelete(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return TestHandlers.Expect(expectedRequestUri, HttpMethod.Delete, responseStatusCode, assertion).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(Action assertion) - { - return TestHandlers.Expect(HttpStatusCode.OK, assertion).CreateClient(); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(Action assertion) + { + return TestHandlers.Expect(HttpStatusCode.OK, assertion).CreateClient(); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(HttpStatusCode responseStatusCode, Action assertion) - { - return - TestHandlers.RespondWith(requestMessage => - { - Assert.NotNull(requestMessage); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(HttpStatusCode responseStatusCode, Action assertion) + { + return + TestHandlers.RespondWith(requestMessage => + { + Assert.NotNull(requestMessage); - assertion?.Invoke(requestMessage); + assertion?.Invoke(requestMessage); - return requestMessage.CreateResponse(responseStatusCode); - }) - .CreateClient(); - } + return requestMessage.CreateResponse(responseStatusCode); + }) + .CreateClient(); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, Action assertion) - { - if (expectedRequestUri == null) - throw new ArgumentNullException(nameof(expectedRequestUri)); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, Action assertion) + { + if (expectedRequestUri == null) + throw new ArgumentNullException(nameof(expectedRequestUri)); - if (expectedRequestMethod == null) - throw new ArgumentNullException(nameof(expectedRequestMethod)); + if (expectedRequestMethod == null) + throw new ArgumentNullException(nameof(expectedRequestMethod)); - return - TestHandlers.Expect(responseStatusCode, requestMessage => - { - Assert.Equal(expectedRequestMethod, requestMessage.Method); - Assert.Equal(expectedRequestUri, requestMessage.RequestUri); + return + TestHandlers.Expect(responseStatusCode, requestMessage => + { + Assert.Equal(expectedRequestMethod, requestMessage.Method); + Assert.Equal(expectedRequestUri, requestMessage.RequestUri); - assertion?.Invoke(requestMessage); - }) - .CreateClient(); - } - } + assertion?.Invoke(requestMessage); + }) + .CreateClient(); + } + } } diff --git a/src/HTTPlease.Testability.Xunit/TestHandlers.cs b/src/HTTPlease.Testability.Xunit/TestHandlers.cs index e9141f5..9f4f1be 100644 --- a/src/HTTPlease.Testability.Xunit/TestHandlers.cs +++ b/src/HTTPlease.Testability.Xunit/TestHandlers.cs @@ -6,382 +6,382 @@ namespace HTTPlease.Testability { - using Mocks; + using Mocks; - /// - /// Factory methods for mocked s used by tests. - /// + /// + /// Factory methods for mocked s used by tests. + /// public static class TestHandlers - { - /// - /// Create an that always responds to requests with the status code. - /// - /// - /// The configured . - /// - public static MockMessageHandler RespondWithOk() - { - return RespondWith(HttpStatusCode.OK); - } + { + /// + /// Create an that always responds to requests with the status code. + /// + /// + /// The configured . + /// + public static MockMessageHandler RespondWithOk() + { + return RespondWith(HttpStatusCode.OK); + } - /// - /// Create an that always responds to requests with the status code. - /// - /// - /// The configured . - /// - public static MockMessageHandler RespondWithBadRequest() - { - return RespondWith(HttpStatusCode.BadRequest); - } + /// + /// Create an that always responds to requests with the status code. + /// + /// + /// The configured . + /// + public static MockMessageHandler RespondWithBadRequest() + { + return RespondWith(HttpStatusCode.BadRequest); + } - /// - /// Create an that always responds to requests with the status code. - /// - /// - /// A string to be used as the response message body. - /// - /// - /// The response media type. - /// - /// - /// The configured . - /// - public static MockMessageHandler RespondWithBadRequest(string responseBody, string responseMediaType) - { - if (String.IsNullOrWhiteSpace(responseMediaType)) - throw new ArgumentException("Must specify a valid media type.", nameof(responseMediaType)); + /// + /// Create an that always responds to requests with the status code. + /// + /// + /// A string to be used as the response message body. + /// + /// + /// The response media type. + /// + /// + /// The configured . + /// + public static MockMessageHandler RespondWithBadRequest(string responseBody, string responseMediaType) + { + if (String.IsNullOrWhiteSpace(responseMediaType)) + throw new ArgumentException("Must specify a valid media type.", nameof(responseMediaType)); - return RespondWith( - request => request.CreateResponse(HttpStatusCode.BadRequest, responseBody, responseMediaType) - ); - } + return RespondWith( + request => request.CreateResponse(HttpStatusCode.BadRequest, responseBody, responseMediaType) + ); + } - /// - /// Create an that always responds to requests with the specified status code. - /// - /// - /// The HTTP status code. - /// - /// - /// The configured . - /// - public static MockMessageHandler RespondWith(HttpStatusCode statusCode) - { - return RespondWith( - request => request.CreateResponse(statusCode) - ); - } + /// + /// Create an that always responds to requests with the specified status code. + /// + /// + /// The HTTP status code. + /// + /// + /// The configured . + /// + public static MockMessageHandler RespondWith(HttpStatusCode statusCode) + { + return RespondWith( + request => request.CreateResponse(statusCode) + ); + } - /// - /// Create an that calls the specified delegate to synchronously respond to requests. - /// - /// - /// A delegate that takes an incoming and returns an outgoing . - /// - /// - /// The configured . - /// - public static MockMessageHandler RespondWith(Func handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Create an that calls the specified delegate to synchronously respond to requests. + /// + /// + /// A delegate that takes an incoming and returns an outgoing . + /// + /// + /// The configured . + /// + public static MockMessageHandler RespondWith(Func handler) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); - return new MockMessageHandler(handler); - } + return new MockMessageHandler(handler); + } - /// - /// Create an that calls the specified delegate to asynchronously respond to requests. - /// - /// - /// A delegate that takes an incoming and asynchronously returns an outgoing . - /// - /// - /// The configured . - /// - public static MockMessageHandler AsyncRespondWith(Func> handler) - { - if (handler == null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Create an that calls the specified delegate to asynchronously respond to requests. + /// + /// + /// A delegate that takes an incoming and asynchronously returns an outgoing . + /// + /// + /// The configured . + /// + public static MockMessageHandler AsyncRespondWith(Func> handler) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); - return new MockMessageHandler(handler); - } + return new MockMessageHandler(handler); + } - /// - /// Create an that expects an incoming GET request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectGet(Uri expectedRequestUri) - { - return ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion: null); - } + /// + /// Create an that expects an incoming GET request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectGet(Uri expectedRequestUri) + { + return ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion: null); + } - /// - /// Create an that performs assertions on an incoming GET request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectGet(Uri expectedRequestUri, Action assertion) - { - return ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion); - } + /// + /// Create an that performs assertions on an incoming GET request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectGet(Uri expectedRequestUri, Action assertion) + { + return ExpectGet(expectedRequestUri, HttpStatusCode.OK, assertion); + } - /// - /// Create an that performs assertions on an incoming GET request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectGet(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return Expect(expectedRequestUri, HttpMethod.Get, responseStatusCode, assertion); - } + /// + /// Create an that performs assertions on an incoming GET request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectGet(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return Expect(expectedRequestUri, HttpMethod.Get, responseStatusCode, assertion); + } - /// - /// Create an that expects an incoming POST request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectPost(Uri expectedRequestUri) - { - return ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion: null); - } + /// + /// Create an that expects an incoming POST request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectPost(Uri expectedRequestUri) + { + return ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion: null); + } - /// - /// Create an that performs assertions on an incoming POST request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectPost(Uri expectedRequestUri, Action assertion) - { - return ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion); - } + /// + /// Create an that performs assertions on an incoming POST request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectPost(Uri expectedRequestUri, Action assertion) + { + return ExpectPost(expectedRequestUri, HttpStatusCode.OK, assertion); + } - /// - /// Create an that performs assertions on an incoming POST request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectPost(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return Expect(expectedRequestUri, HttpMethod.Post, responseStatusCode, assertion); - } + /// + /// Create an that performs assertions on an incoming POST request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectPost(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return Expect(expectedRequestUri, HttpMethod.Post, responseStatusCode, assertion); + } - /// - /// Create an that expects an incoming PUT request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectPut(Uri expectedRequestUri) - { - return ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion: null); - } + /// + /// Create an that expects an incoming PUT request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectPut(Uri expectedRequestUri) + { + return ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion: null); + } - /// - /// Create an that performs assertions on an incoming PUT request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectPut(Uri expectedRequestUri, Action assertion) - { - return ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion); - } + /// + /// Create an that performs assertions on an incoming PUT request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectPut(Uri expectedRequestUri, Action assertion) + { + return ExpectPut(expectedRequestUri, HttpStatusCode.OK, assertion); + } - /// - /// Create an that performs assertions on an incoming PUT request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectPut(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return Expect(expectedRequestUri, HttpMethod.Put, responseStatusCode, assertion); - } + /// + /// Create an that performs assertions on an incoming PUT request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectPut(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return Expect(expectedRequestUri, HttpMethod.Put, responseStatusCode, assertion); + } - /// - /// Create an that expects an incoming DELETE request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectDelete(Uri expectedRequestUri) - { - return ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion: null); - } + /// + /// Create an that expects an incoming DELETE request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectDelete(Uri expectedRequestUri) + { + return ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion: null); + } - /// - /// Create an that performs assertions on an incoming DELETE request message and returns an response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectDelete(Uri expectedRequestUri, Action assertion) - { - return ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion); - } + /// + /// Create an that performs assertions on an incoming DELETE request message and returns an response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectDelete(Uri expectedRequestUri, Action assertion) + { + return ExpectDelete(expectedRequestUri, HttpStatusCode.OK, assertion); + } - /// - /// Create an that performs assertions on an incoming DELETE request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler ExpectDelete(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) - { - return Expect(expectedRequestUri, HttpMethod.Delete, responseStatusCode, assertion); - } + /// + /// Create an that performs assertions on an incoming DELETE request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler ExpectDelete(Uri expectedRequestUri, HttpStatusCode responseStatusCode, Action assertion) + { + return Expect(expectedRequestUri, HttpMethod.Delete, responseStatusCode, assertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler Expect(Action assertion) - { - return Expect(HttpStatusCode.OK, assertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler Expect(Action assertion) + { + return Expect(HttpStatusCode.OK, assertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler Expect(HttpStatusCode responseStatusCode, Action assertion) - { - return RespondWith(requestMessage => - { - Assert.NotNull(requestMessage); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler Expect(HttpStatusCode responseStatusCode, Action assertion) + { + return RespondWith(requestMessage => + { + Assert.NotNull(requestMessage); - assertion?.Invoke(requestMessage); + assertion?.Invoke(requestMessage); - return requestMessage.CreateResponse(responseStatusCode); - }); - } + return requestMessage.CreateResponse(responseStatusCode); + }); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static MockMessageHandler Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, Action assertion) - { - if (expectedRequestUri == null) - throw new ArgumentNullException(nameof(expectedRequestUri)); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static MockMessageHandler Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, Action assertion) + { + if (expectedRequestUri == null) + throw new ArgumentNullException(nameof(expectedRequestUri)); - if (expectedRequestMethod == null) - throw new ArgumentNullException(nameof(expectedRequestMethod)); + if (expectedRequestMethod == null) + throw new ArgumentNullException(nameof(expectedRequestMethod)); - return Expect(responseStatusCode, requestMessage => - { - Assert.Equal(expectedRequestMethod, requestMessage.Method); - Assert.Equal(expectedRequestUri, requestMessage.RequestUri); + return Expect(responseStatusCode, requestMessage => + { + Assert.Equal(expectedRequestMethod, requestMessage.Method); + Assert.Equal(expectedRequestUri, requestMessage.RequestUri); - assertion?.Invoke(requestMessage); - }); - } - } + assertion?.Invoke(requestMessage); + }); + } + } } diff --git a/test/HTTPlease.Core.Tests/BuildMessage/TypedRequest.cs b/test/HTTPlease.Core.Tests/BuildMessage/TypedRequest.cs index b661fe3..e69c8d5 100644 --- a/test/HTTPlease.Core.Tests/BuildMessage/TypedRequest.cs +++ b/test/HTTPlease.Core.Tests/BuildMessage/TypedRequest.cs @@ -4,104 +4,104 @@ namespace HTTPlease.Tests.BuildMessage { - using Testability; + using Testability; - /// - /// Message-building tests for an (). - /// - public class TypedRequest + /// + /// Message-building tests for an (). + /// + public class TypedRequest { - /// - /// The default context for requests used by tests. - /// - static readonly string DefaultContext = "Hello World"; - - /// - /// An empty request. - /// - static readonly HttpRequest EmptyRequest = HttpRequest.Empty; - - /// - /// A request with an absolute URI. - /// - static readonly HttpRequest AbsoluteRequest = HttpRequest.Create("http://localhost:1234"); - - /// - /// A request with a relative URI. - /// - static readonly HttpRequest RelativeRequest = HttpRequest.Create("foo/bar"); - - #region Empty requests - - /// - /// An throws . - /// - [Fact] - public void Empty_Throws() - { - Assert.Throws(() => - { - EmptyRequest.BuildRequestMessage(HttpMethod.Get, DefaultContext); - }); - } - - #endregion Empty requests - - #region Relative URIs - - /// - /// An with a relative URI throws if no base URI is supplied. - /// - [Fact] - public void RelativeUri_NoBaseUri_Throws() - { - Assert.Throws(() => - { - RelativeRequest.BuildRequestMessage(HttpMethod.Get, DefaultContext); - }); - } - - /// - /// An with a relative URI prepends the supplied base URI to the request URI. - /// - [Fact] - public void RelativeUri_BaseUri_PrependsBaseUri() - { - Uri baseUri = new Uri("http://tintoy.io:5678/"); - - RequestAssert.MessageHasUri(RelativeRequest, DefaultContext, baseUri, - expectedUri: new Uri(baseUri, RelativeRequest.Uri) - ); - } - - #endregion // Relative URIs - - #region Absolute URIs - - /// - /// An with an absolute URI ignores the lack of a base URI and uses the request URI. - /// - [Fact] - public void AbsoluteUri_NoBaseUri_UsesRequestUri() - { - RequestAssert.MessageHasUri(AbsoluteRequest, DefaultContext, - expectedUri: AbsoluteRequest.Uri - ); - } - - /// - /// An with an absolute URI ignores the supplied base URI and uses the request URI. - /// - [Fact] - public void AbsoluteUri_BaseUri_UsesRequestUri() - { - Uri baseUri = new Uri("http://tintoy.io:5678/"); - - RequestAssert.MessageHasUri(AbsoluteRequest, DefaultContext, baseUri, - expectedUri: AbsoluteRequest.Uri - ); - } - - #endregion // Absolute URIs - } + /// + /// The default context for requests used by tests. + /// + static readonly string DefaultContext = "Hello World"; + + /// + /// An empty request. + /// + static readonly HttpRequest EmptyRequest = HttpRequest.Empty; + + /// + /// A request with an absolute URI. + /// + static readonly HttpRequest AbsoluteRequest = HttpRequest.Create("http://localhost:1234"); + + /// + /// A request with a relative URI. + /// + static readonly HttpRequest RelativeRequest = HttpRequest.Create("foo/bar"); + + #region Empty requests + + /// + /// An throws . + /// + [Fact] + public void Empty_Throws() + { + Assert.Throws(() => + { + EmptyRequest.BuildRequestMessage(HttpMethod.Get, DefaultContext); + }); + } + + #endregion Empty requests + + #region Relative URIs + + /// + /// An with a relative URI throws if no base URI is supplied. + /// + [Fact] + public void RelativeUri_NoBaseUri_Throws() + { + Assert.Throws(() => + { + RelativeRequest.BuildRequestMessage(HttpMethod.Get, DefaultContext); + }); + } + + /// + /// An with a relative URI prepends the supplied base URI to the request URI. + /// + [Fact] + public void RelativeUri_BaseUri_PrependsBaseUri() + { + Uri baseUri = new Uri("http://tintoy.io:5678/"); + + RequestAssert.MessageHasUri(RelativeRequest, DefaultContext, baseUri, + expectedUri: new Uri(baseUri, RelativeRequest.Uri) + ); + } + + #endregion // Relative URIs + + #region Absolute URIs + + /// + /// An with an absolute URI ignores the lack of a base URI and uses the request URI. + /// + [Fact] + public void AbsoluteUri_NoBaseUri_UsesRequestUri() + { + RequestAssert.MessageHasUri(AbsoluteRequest, DefaultContext, + expectedUri: AbsoluteRequest.Uri + ); + } + + /// + /// An with an absolute URI ignores the supplied base URI and uses the request URI. + /// + [Fact] + public void AbsoluteUri_BaseUri_UsesRequestUri() + { + Uri baseUri = new Uri("http://tintoy.io:5678/"); + + RequestAssert.MessageHasUri(AbsoluteRequest, DefaultContext, baseUri, + expectedUri: AbsoluteRequest.Uri + ); + } + + #endregion // Absolute URIs + } } diff --git a/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs b/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs index baf196b..84b1f23 100644 --- a/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs +++ b/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs @@ -4,320 +4,320 @@ namespace HTTPlease.Tests.BuildMessage { - using Testability; - using Xunit.Abstractions; - - /// - /// Message-building tests for (). - /// - public class UntypedRequest - { - /// - /// A request with an absolute URI. - /// - static readonly HttpRequest AbsoluteRequest = HttpRequest.Create("http://localhost:1234"); - - /// - /// A request with a relative URI. - /// - static readonly HttpRequest RelativeRequest = HttpRequest.Create("foo/bar"); - - public UntypedRequest(ITestOutputHelper testOutput) - { - if (testOutput == null) - throw new ArgumentNullException(nameof(testOutput)); - - TestOutput = testOutput; - } - - ITestOutputHelper TestOutput { get; } - - /// - /// An throws . - /// - [Fact] - public void Empty_Throws() - { - Assert.Throws(() => - { - HttpRequest.Empty.BuildRequestMessage(HttpMethod.Get); - }); - } - - /// - /// An with a relative URI throws if no base URI is supplied. - /// - [Fact] - public void RelativeUri_NoBaseUri_Throws() - { - Assert.Throws(() => - { - RelativeRequest.BuildRequestMessage(HttpMethod.Get); - }); - } - - /// - /// An with a relative URI prepends the supplied base URI to the request URI. - /// - [Fact] - public void RelativeUri_BaseUri_PrependsBaseUri() - { - Uri baseUri = new Uri("http://tintoy.io:5678/"); - - RequestAssert.MessageHasUri(RelativeRequest, baseUri, - expectedUri: new Uri(baseUri, RelativeRequest.Uri) - ); - } - - /// - /// An with an absolute URI ignores the lack of a base URI and uses the request URI. - /// - [Fact] - public void AbsoluteUri_NoBaseUri_UsesRequestUri() - { - RequestAssert.MessageHasUri(AbsoluteRequest, - expectedUri: AbsoluteRequest.Uri - ); - } - - /// - /// An with an absolute URI ignores the supplied base URI and uses the request URI. - /// - [Fact] - public void AbsoluteUri_BaseUri_UsesRequestUri() - { - Uri baseUri = new Uri("http://tintoy.io:5678/"); - - RequestAssert.MessageHasUri(AbsoluteRequest, baseUri, - expectedUri: AbsoluteRequest.Uri - ); - } - - #region Template URIs - - /// - /// An with an absolute template URI, using statically-bound template parameters. - /// - [Fact] - public void Absoluteuri_Template() - { - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}") - .WithTemplateParameter("action", "foo") - .WithTemplateParameter("id", "bar"); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar" - ); - } - - /// - /// An with an absolute template URI, using dynamically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_Template_DeferredValues() - { - string action = "foo"; - string id = "bar"; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}") - .WithTemplateParameter("action", () => action) - .WithTemplateParameter("id", () => id); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar" - ); - - action = "diddly"; - id = "dee"; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/diddly/dee" - ); - } - - /// - /// An with an absolute template URI that includes a query component, using statically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_Template_Query() - { - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}?value={value}") - .WithTemplateParameter("action", "foo") - .WithTemplateParameter("id", "bar") - .WithTemplateParameter("value", true); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?value=True" - ); - } - - /// - /// An with an absolute template URI that includes a query component, using dynamically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_Template_Query_DeferredValues() - { - string action = "foo"; - string id = "bar"; - bool? value = true; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/") - .WithRelativeUri("{action}/{id}?value={value?}") - .WithTemplateParameter("action", () => action) - .WithTemplateParameter("id", () => id) - .WithTemplateParameter("value", () => value); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?value=True" - ); - - action = "diddly"; - id = "dee"; - value = null; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/diddly/dee" - ); - } - - #endregion // Template URIs - - #region Query parameters - - /// - /// An with an absolute URI that adds a query component, using statically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_Query() - { - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("value", true); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?value=True" - ); - } - - /// - /// An with an absolute URI that adds a query component, using dynamically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_AddQuery_DeferredValues() - { - bool? value = true; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("value", () => value); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?value=True" - ); - - value = null; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar" - ); - } - - /// - /// An with an absolute URI that adds a multiple (duplicate) parameter query component, using dynamically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_AddQuery_Multiple_DeferredValues() - { - bool? value1 = true; - bool? value2 = false; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("value", () => value1) - .WithQueryParameter("value", () => value2); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?value=True&value=False" - ); - - value1 = null; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?value=False" - ); - - value2 = null; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar" - ); - } - - /// - /// An with an absolute URI that adds a multiple (duplicate) flag parameter query component, using dynamically-bound template parameters. - /// - [Fact] - public void AbsoluteUri_AddQuery_Flag_Multiple_DeferredValues() - { - string value1 = String.Empty; - string value2 = String.Empty; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("flag", () => value1) - .WithQueryParameter("flag", () => value2); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?flag&flag" - ); - - value1 = null; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?flag" - ); - - value2 = null; - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar" - ); - } - - /// - /// An with an absolute URI that adds a query component, with no additional path component). - /// - [Fact] - public void AbsoluteUri_AddQuery_EmptyPath() - { - HttpRequest request = - AbsoluteRequest.WithRelativeUri("foo/bar") - .WithRelativeUri("?baz=bonk"); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?baz=bonk" - ); - } - - /// - /// An with an absolute URI that adds a query component, with no additional path component). - /// - [Fact] - public void AbsoluteUri_WithQuery_AddQuery_EmptyPath() - { - HttpRequest request = - AbsoluteRequest.WithRelativeUri("foo/bar?baz=bonk") - .WithRelativeUri("?bo=diddly"); - - RequestAssert.MessageHasUri(request, - expectedUri: "http://localhost:1234/foo/bar?baz=bonk&bo=diddly" - ); - } - - #endregion // Query parameters - } + using Testability; + using Xunit.Abstractions; + + /// + /// Message-building tests for (). + /// + public class UntypedRequest + { + /// + /// A request with an absolute URI. + /// + static readonly HttpRequest AbsoluteRequest = HttpRequest.Create("http://localhost:1234"); + + /// + /// A request with a relative URI. + /// + static readonly HttpRequest RelativeRequest = HttpRequest.Create("foo/bar"); + + public UntypedRequest(ITestOutputHelper testOutput) + { + if (testOutput == null) + throw new ArgumentNullException(nameof(testOutput)); + + TestOutput = testOutput; + } + + ITestOutputHelper TestOutput { get; } + + /// + /// An throws . + /// + [Fact] + public void Empty_Throws() + { + Assert.Throws(() => + { + HttpRequest.Empty.BuildRequestMessage(HttpMethod.Get); + }); + } + + /// + /// An with a relative URI throws if no base URI is supplied. + /// + [Fact] + public void RelativeUri_NoBaseUri_Throws() + { + Assert.Throws(() => + { + RelativeRequest.BuildRequestMessage(HttpMethod.Get); + }); + } + + /// + /// An with a relative URI prepends the supplied base URI to the request URI. + /// + [Fact] + public void RelativeUri_BaseUri_PrependsBaseUri() + { + Uri baseUri = new Uri("http://tintoy.io:5678/"); + + RequestAssert.MessageHasUri(RelativeRequest, baseUri, + expectedUri: new Uri(baseUri, RelativeRequest.Uri) + ); + } + + /// + /// An with an absolute URI ignores the lack of a base URI and uses the request URI. + /// + [Fact] + public void AbsoluteUri_NoBaseUri_UsesRequestUri() + { + RequestAssert.MessageHasUri(AbsoluteRequest, + expectedUri: AbsoluteRequest.Uri + ); + } + + /// + /// An with an absolute URI ignores the supplied base URI and uses the request URI. + /// + [Fact] + public void AbsoluteUri_BaseUri_UsesRequestUri() + { + Uri baseUri = new Uri("http://tintoy.io:5678/"); + + RequestAssert.MessageHasUri(AbsoluteRequest, baseUri, + expectedUri: AbsoluteRequest.Uri + ); + } + + #region Template URIs + + /// + /// An with an absolute template URI, using statically-bound template parameters. + /// + [Fact] + public void Absoluteuri_Template() + { + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}") + .WithTemplateParameter("action", "foo") + .WithTemplateParameter("id", "bar"); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + } + + /// + /// An with an absolute template URI, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_Template_DeferredValues() + { + string action = "foo"; + string id = "bar"; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}") + .WithTemplateParameter("action", () => action) + .WithTemplateParameter("id", () => id); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + + action = "diddly"; + id = "dee"; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/diddly/dee" + ); + } + + /// + /// An with an absolute template URI that includes a query component, using statically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_Template_Query() + { + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/{action}/{id}?value={value}") + .WithTemplateParameter("action", "foo") + .WithTemplateParameter("id", "bar") + .WithTemplateParameter("value", true); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=True" + ); + } + + /// + /// An with an absolute template URI that includes a query component, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_Template_Query_DeferredValues() + { + string action = "foo"; + string id = "bar"; + bool? value = true; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/") + .WithRelativeUri("{action}/{id}?value={value?}") + .WithTemplateParameter("action", () => action) + .WithTemplateParameter("id", () => id) + .WithTemplateParameter("value", () => value); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=True" + ); + + action = "diddly"; + id = "dee"; + value = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/diddly/dee" + ); + } + + #endregion // Template URIs + + #region Query parameters + + /// + /// An with an absolute URI that adds a query component, using statically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_Query() + { + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("value", true); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=True" + ); + } + + /// + /// An with an absolute URI that adds a query component, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_AddQuery_DeferredValues() + { + bool? value = true; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("value", () => value); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=True" + ); + + value = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + } + + /// + /// An with an absolute URI that adds a multiple (duplicate) parameter query component, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_AddQuery_Multiple_DeferredValues() + { + bool? value1 = true; + bool? value2 = false; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("value", () => value1) + .WithQueryParameter("value", () => value2); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=True&value=False" + ); + + value1 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?value=False" + ); + + value2 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + } + + /// + /// An with an absolute URI that adds a multiple (duplicate) flag parameter query component, using dynamically-bound template parameters. + /// + [Fact] + public void AbsoluteUri_AddQuery_Flag_Multiple_DeferredValues() + { + string value1 = String.Empty; + string value2 = String.Empty; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("flag", () => value1) + .WithQueryParameter("flag", () => value2); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?flag&flag" + ); + + value1 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?flag" + ); + + value2 = null; + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar" + ); + } + + /// + /// An with an absolute URI that adds a query component, with no additional path component). + /// + [Fact] + public void AbsoluteUri_AddQuery_EmptyPath() + { + HttpRequest request = + AbsoluteRequest.WithRelativeUri("foo/bar") + .WithRelativeUri("?baz=bonk"); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?baz=bonk" + ); + } + + /// + /// An with an absolute URI that adds a query component, with no additional path component). + /// + [Fact] + public void AbsoluteUri_WithQuery_AddQuery_EmptyPath() + { + HttpRequest request = + AbsoluteRequest.WithRelativeUri("foo/bar?baz=bonk") + .WithRelativeUri("?bo=diddly"); + + RequestAssert.MessageHasUri(request, + expectedUri: "http://localhost:1234/foo/bar?baz=bonk&bo=diddly" + ); + } + + #endregion // Query parameters + } } diff --git a/test/HTTPlease.Core.Tests/TypedRequestTests.cs b/test/HTTPlease.Core.Tests/TypedRequestTests.cs index 7e1f82d..99e569b 100644 --- a/test/HTTPlease.Core.Tests/TypedRequestTests.cs +++ b/test/HTTPlease.Core.Tests/TypedRequestTests.cs @@ -5,193 +5,193 @@ namespace HTTPlease.Tests { - using Testability; - - /// - /// Unit-tests for that use a context for resolving deferred parameters. - /// - public class TypedRequestTests - { - /// - /// Verify that a request can build and then invoke request with an absolute and then relative template URI (with query parameters) with deferred values that come from a supplied context value. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Request_RelativeTemplateUriWithQuery_DeferredValues_FromContext_Get() - { - Uri baseUri = new Uri("http://localhost:1234/"); - - TestParameterContext testParameterContext = new TestParameterContext(); - - Uri expectedUri = null; - HttpClient client = TestClients.Expect(requestMessage => - { - Assert.Equal(expectedUri, requestMessage.RequestUri); - }); - using (client) - { - HttpRequest requestBuilder = - HttpRequest.Factory.Create(baseUri) - .WithRelativeUri("{action}/{id}?flag={flag?}") - .WithTemplateParameter("action", context => context.Action) - .WithTemplateParameter("id", context => context.Id) - .WithTemplateParameter("flag", context => context.Flag); - - testParameterContext.Action = "foo"; - testParameterContext.Id = 1; - testParameterContext.Flag = true; - - expectedUri = new Uri(baseUri, "foo/1?flag=True"); - await client.GetAsync(requestBuilder, testParameterContext); - - testParameterContext.Flag = false; - - expectedUri = new Uri(baseUri, "foo/1?flag=False"); - await client.GetAsync(requestBuilder, testParameterContext); - - testParameterContext.Action = "diddly"; - testParameterContext.Id = -17; - testParameterContext.Flag = null; - - expectedUri = new Uri(baseUri, "diddly/-17"); - await client.GetAsync(requestBuilder, testParameterContext); - } - } - - /// - /// Verify that a request can build a request with an absolute and then relative template URI (with query parameters) with deferred values that come from a supplied context value. - /// - [Fact] - public void Request_RelativeTemplateUriWithQuery_DeferredValues_FromContext_Build() - { - Uri baseUri = new Uri("http://localhost:1234/"); - - HttpRequest requestBuilder = - HttpRequest.Factory.Create(baseUri) - .WithRelativeUri("{action}/{id}?flag={flag?}") - .WithTemplateParameter("action", context => context.Action) - .WithTemplateParameter("id", context => context.Id) - .WithTemplateParameter("flag", context => context.Flag); - - TestParameterContext testParameterContext = new TestParameterContext - { - Action = "foo", - Id = 1, - Flag = true - }; - using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) - { - Assert.Equal( - new Uri(baseUri, "foo/1?flag=True"), - requestMessage.RequestUri - ); - } - - testParameterContext.Flag = false; - using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) - { - Assert.Equal( - new Uri(baseUri, "foo/1?flag=False"), - requestMessage.RequestUri - ); - } - - testParameterContext.Action = "diddly"; - testParameterContext.Id = -17; - testParameterContext.Flag = null; - using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) - { - Assert.Equal( - new Uri(baseUri, "diddly/-17"), - requestMessage.RequestUri - ); - } - } - - /// - /// Verify that a request can build a request with an absolute and then relative template URI (with query parameters) with deferred values that come from the request's default (intrinsic) context. - /// - [Fact] - public void Request_RelativeTemplateUriWithQuery_DeferredValues_FromDefaultContext_Build() - { - Uri baseUri = new Uri("http://localhost:1234/"); - - TestParameterContext testParameterContext = new TestParameterContext - { - Action = "foo", - Id = 1, - Flag = true - }; - - HttpRequest requestBuilder = - HttpRequest.Factory.Create(baseUri) - .WithRelativeUri("{action}/{id}?flag={flag?}") - .WithTemplateParameter("action", context => context.Action) - .WithTemplateParameter("id", context => context.Id) - .WithTemplateParameter("flag", context => context.Flag); - - using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) - { - Assert.Equal( - new Uri(baseUri, "foo/1?flag=True"), - requestMessage.RequestUri - ); - } - - testParameterContext.Flag = false; - using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) - { - Assert.Equal( - new Uri(baseUri, "foo/1?flag=False"), - requestMessage.RequestUri - ); - } - - testParameterContext.Action = "diddly"; - testParameterContext.Id = -17; - testParameterContext.Flag = null; - using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) - { - Assert.Equal( - new Uri(baseUri, "diddly/-17"), - requestMessage.RequestUri - ); - } - } - - /// - /// A parameter-resolution context class used for tests. - /// - class TestParameterContext - { - /// - /// The "Action" parameter. - /// - public string Action - { - get; - set; - } - - /// - /// The "Id" parameter. - /// - public int Id - { - get; - set; - } - - /// - /// The "Flag" parameter. - /// - public bool? Flag - { - get; - set; - } - } - } + using Testability; + + /// + /// Unit-tests for that use a context for resolving deferred parameters. + /// + public class TypedRequestTests + { + /// + /// Verify that a request can build and then invoke request with an absolute and then relative template URI (with query parameters) with deferred values that come from a supplied context value. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Request_RelativeTemplateUriWithQuery_DeferredValues_FromContext_Get() + { + Uri baseUri = new Uri("http://localhost:1234/"); + + TestParameterContext testParameterContext = new TestParameterContext(); + + Uri expectedUri = null; + HttpClient client = TestClients.Expect(requestMessage => + { + Assert.Equal(expectedUri, requestMessage.RequestUri); + }); + using (client) + { + HttpRequest requestBuilder = + HttpRequest.Factory.Create(baseUri) + .WithRelativeUri("{action}/{id}?flag={flag?}") + .WithTemplateParameter("action", context => context.Action) + .WithTemplateParameter("id", context => context.Id) + .WithTemplateParameter("flag", context => context.Flag); + + testParameterContext.Action = "foo"; + testParameterContext.Id = 1; + testParameterContext.Flag = true; + + expectedUri = new Uri(baseUri, "foo/1?flag=True"); + await client.GetAsync(requestBuilder, testParameterContext); + + testParameterContext.Flag = false; + + expectedUri = new Uri(baseUri, "foo/1?flag=False"); + await client.GetAsync(requestBuilder, testParameterContext); + + testParameterContext.Action = "diddly"; + testParameterContext.Id = -17; + testParameterContext.Flag = null; + + expectedUri = new Uri(baseUri, "diddly/-17"); + await client.GetAsync(requestBuilder, testParameterContext); + } + } + + /// + /// Verify that a request can build a request with an absolute and then relative template URI (with query parameters) with deferred values that come from a supplied context value. + /// + [Fact] + public void Request_RelativeTemplateUriWithQuery_DeferredValues_FromContext_Build() + { + Uri baseUri = new Uri("http://localhost:1234/"); + + HttpRequest requestBuilder = + HttpRequest.Factory.Create(baseUri) + .WithRelativeUri("{action}/{id}?flag={flag?}") + .WithTemplateParameter("action", context => context.Action) + .WithTemplateParameter("id", context => context.Id) + .WithTemplateParameter("flag", context => context.Flag); + + TestParameterContext testParameterContext = new TestParameterContext + { + Action = "foo", + Id = 1, + Flag = true + }; + using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) + { + Assert.Equal( + new Uri(baseUri, "foo/1?flag=True"), + requestMessage.RequestUri + ); + } + + testParameterContext.Flag = false; + using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) + { + Assert.Equal( + new Uri(baseUri, "foo/1?flag=False"), + requestMessage.RequestUri + ); + } + + testParameterContext.Action = "diddly"; + testParameterContext.Id = -17; + testParameterContext.Flag = null; + using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) + { + Assert.Equal( + new Uri(baseUri, "diddly/-17"), + requestMessage.RequestUri + ); + } + } + + /// + /// Verify that a request can build a request with an absolute and then relative template URI (with query parameters) with deferred values that come from the request's default (intrinsic) context. + /// + [Fact] + public void Request_RelativeTemplateUriWithQuery_DeferredValues_FromDefaultContext_Build() + { + Uri baseUri = new Uri("http://localhost:1234/"); + + TestParameterContext testParameterContext = new TestParameterContext + { + Action = "foo", + Id = 1, + Flag = true + }; + + HttpRequest requestBuilder = + HttpRequest.Factory.Create(baseUri) + .WithRelativeUri("{action}/{id}?flag={flag?}") + .WithTemplateParameter("action", context => context.Action) + .WithTemplateParameter("id", context => context.Id) + .WithTemplateParameter("flag", context => context.Flag); + + using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) + { + Assert.Equal( + new Uri(baseUri, "foo/1?flag=True"), + requestMessage.RequestUri + ); + } + + testParameterContext.Flag = false; + using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) + { + Assert.Equal( + new Uri(baseUri, "foo/1?flag=False"), + requestMessage.RequestUri + ); + } + + testParameterContext.Action = "diddly"; + testParameterContext.Id = -17; + testParameterContext.Flag = null; + using (HttpRequestMessage requestMessage = requestBuilder.BuildRequestMessage(HttpMethod.Get, testParameterContext)) + { + Assert.Equal( + new Uri(baseUri, "diddly/-17"), + requestMessage.RequestUri + ); + } + } + + /// + /// A parameter-resolution context class used for tests. + /// + class TestParameterContext + { + /// + /// The "Action" parameter. + /// + public string Action + { + get; + set; + } + + /// + /// The "Id" parameter. + /// + public int Id + { + get; + set; + } + + /// + /// The "Flag" parameter. + /// + public bool? Flag + { + get; + set; + } + } + } } diff --git a/test/HTTPlease.Core.Tests/UnitTestBase.cs b/test/HTTPlease.Core.Tests/UnitTestBase.cs index 33d8440..9865b36 100644 --- a/test/HTTPlease.Core.Tests/UnitTestBase.cs +++ b/test/HTTPlease.Core.Tests/UnitTestBase.cs @@ -1,15 +1,15 @@ namespace HTTPlease.Tests { - /// - /// Base class for unit-test suites. - /// - public abstract class UnitTestBase - { - /// - /// Initialise . - /// - protected UnitTestBase() - { - } - } + /// + /// Base class for unit-test suites. + /// + public abstract class UnitTestBase + { + /// + /// Initialise . + /// + protected UnitTestBase() + { + } + } } diff --git a/test/HTTPlease.Core.Tests/UriTemplateTests.cs b/test/HTTPlease.Core.Tests/UriTemplateTests.cs index c0eabdb..a755870 100644 --- a/test/HTTPlease.Core.Tests/UriTemplateTests.cs +++ b/test/HTTPlease.Core.Tests/UriTemplateTests.cs @@ -4,131 +4,131 @@ namespace HTTPlease.Tests { - using Core.Templates; - - /// - /// Unit-tests for URI templating functionality. - /// - public sealed class UriTemplateTests - : UnitTestBase - { - /// - /// Create a new URI templating unit-test suite. - /// - public UriTemplateTests() - { - } - - /// - /// Verify that template segments can be parsed from a URI. - /// - [Fact] - public void Can_Parse_TemplateSegments_From_Uri() - { - IReadOnlyList segments = TemplateSegment.Parse( - "api/{controller}/{action}/{id?}/properties" - ); - - Assert.Equal(6, segments.Count); - Assert.IsAssignableFrom(segments[0]); - - LiteralUriSegment apiSegment = Assert.IsAssignableFrom(segments[1]); - Assert.Equal("api", apiSegment.Value); - - ParameterizedUriSegment controllerSegment = Assert.IsAssignableFrom(segments[2]); - Assert.True(controllerSegment.IsDirectory); - Assert.Equal("controller", controllerSegment.TemplateParameterName); - Assert.False(controllerSegment.IsOptional); - - ParameterizedUriSegment actionSegment = Assert.IsAssignableFrom(segments[3]); - Assert.True(actionSegment.IsDirectory); - Assert.Equal("action", actionSegment.TemplateParameterName); - Assert.False(actionSegment.IsOptional); - - ParameterizedUriSegment idSegment = Assert.IsAssignableFrom(segments[4]); - Assert.True(idSegment.IsDirectory); - Assert.Equal("id", idSegment.TemplateParameterName); - Assert.True(idSegment.IsOptional); - - LiteralUriSegment propertiesSegment = Assert.IsAssignableFrom(segments[5]); - Assert.False(propertiesSegment.IsDirectory); - Assert.Equal("properties", propertiesSegment.Value); - } - - /// - /// Verify that template segments can be parsed from a URI with a query component. - /// - [Fact] - public void Can_Parse_TemplateSegments_From_Uri_WithQuery() - { - IReadOnlyList segments = TemplateSegment.Parse( - "api/{controller}/{action}/{id?}/properties?propertyIds={propertyGroupIds}&diddly={dee?}&foo=bar" - ); - - Assert.Equal(9, segments.Count); - Assert.IsAssignableFrom(segments[0]); - - LiteralUriSegment apiSegment = Assert.IsAssignableFrom(segments[1]); - Assert.Equal("api", apiSegment.Value); - - ParameterizedUriSegment controllerSegment = Assert.IsAssignableFrom(segments[2]); - Assert.True(controllerSegment.IsDirectory); - Assert.Equal("controller", controllerSegment.TemplateParameterName); - Assert.False(controllerSegment.IsOptional); - - ParameterizedUriSegment actionSegment = Assert.IsAssignableFrom(segments[3]); - Assert.True(actionSegment.IsDirectory); - Assert.Equal("action", actionSegment.TemplateParameterName); - Assert.False(actionSegment.IsOptional); - - ParameterizedUriSegment idSegment = Assert.IsAssignableFrom(segments[4]); - Assert.True(idSegment.IsDirectory); - Assert.Equal("id", idSegment.TemplateParameterName); - Assert.True(idSegment.IsOptional); - - LiteralUriSegment propertiesSegment = Assert.IsAssignableFrom(segments[5]); - Assert.False(propertiesSegment.IsDirectory); - Assert.Equal("properties", propertiesSegment.Value); - - ParameterizedQuerySegment propertyIdsSegment = Assert.IsAssignableFrom(segments[6]); - Assert.Equal("propertyIds", propertyIdsSegment.QueryParameterName); - Assert.Equal("propertyGroupIds", propertyIdsSegment.TemplateParameterName); - Assert.False(propertyIdsSegment.IsOptional); - - ParameterizedQuerySegment diddlySegment = Assert.IsAssignableFrom(segments[7]); - Assert.Equal("diddly", diddlySegment.QueryParameterName); - Assert.Equal("dee", diddlySegment.TemplateParameterName); - Assert.True(diddlySegment.IsOptional); - - LiteralQuerySegment fooSegment = Assert.IsAssignableFrom(segments[8]); - Assert.Equal("foo", fooSegment.QueryParameterName); - Assert.Equal("bar", fooSegment.QueryParameterValue); - } - - /// - /// Verify that template with a query component can be populated. - /// - [Fact] - public void Can_Populate_Template_WithQuery() - { - UriTemplate template = new UriTemplate( - "api/{controller}/{action}/{id?}/properties?propertyIds={propertyGroupIds}&diddly={dee?}&foo=bar" - ); - - Uri generatedUri = template.Populate( - baseUri: new Uri("http://test-host/"), - templateParameters: new Dictionary - { - { "controller", "organizations" }, - { "action", "distinct" }, - { "propertyGroupIds", "System.OrganizationCommercial;EnterpriseMobility.OrganizationAirwatch" } - } - ); - - Assert.Equal( - "http://test-host/api/organizations/distinct/properties?propertyIds=System.OrganizationCommercial%3BEnterpriseMobility.OrganizationAirwatch&foo=bar", - generatedUri.AbsoluteUri - ); - } - } + using Core.Templates; + + /// + /// Unit-tests for URI templating functionality. + /// + public sealed class UriTemplateTests + : UnitTestBase + { + /// + /// Create a new URI templating unit-test suite. + /// + public UriTemplateTests() + { + } + + /// + /// Verify that template segments can be parsed from a URI. + /// + [Fact] + public void Can_Parse_TemplateSegments_From_Uri() + { + IReadOnlyList segments = TemplateSegment.Parse( + "api/{controller}/{action}/{id?}/properties" + ); + + Assert.Equal(6, segments.Count); + Assert.IsAssignableFrom(segments[0]); + + LiteralUriSegment apiSegment = Assert.IsAssignableFrom(segments[1]); + Assert.Equal("api", apiSegment.Value); + + ParameterizedUriSegment controllerSegment = Assert.IsAssignableFrom(segments[2]); + Assert.True(controllerSegment.IsDirectory); + Assert.Equal("controller", controllerSegment.TemplateParameterName); + Assert.False(controllerSegment.IsOptional); + + ParameterizedUriSegment actionSegment = Assert.IsAssignableFrom(segments[3]); + Assert.True(actionSegment.IsDirectory); + Assert.Equal("action", actionSegment.TemplateParameterName); + Assert.False(actionSegment.IsOptional); + + ParameterizedUriSegment idSegment = Assert.IsAssignableFrom(segments[4]); + Assert.True(idSegment.IsDirectory); + Assert.Equal("id", idSegment.TemplateParameterName); + Assert.True(idSegment.IsOptional); + + LiteralUriSegment propertiesSegment = Assert.IsAssignableFrom(segments[5]); + Assert.False(propertiesSegment.IsDirectory); + Assert.Equal("properties", propertiesSegment.Value); + } + + /// + /// Verify that template segments can be parsed from a URI with a query component. + /// + [Fact] + public void Can_Parse_TemplateSegments_From_Uri_WithQuery() + { + IReadOnlyList segments = TemplateSegment.Parse( + "api/{controller}/{action}/{id?}/properties?propertyIds={propertyGroupIds}&diddly={dee?}&foo=bar" + ); + + Assert.Equal(9, segments.Count); + Assert.IsAssignableFrom(segments[0]); + + LiteralUriSegment apiSegment = Assert.IsAssignableFrom(segments[1]); + Assert.Equal("api", apiSegment.Value); + + ParameterizedUriSegment controllerSegment = Assert.IsAssignableFrom(segments[2]); + Assert.True(controllerSegment.IsDirectory); + Assert.Equal("controller", controllerSegment.TemplateParameterName); + Assert.False(controllerSegment.IsOptional); + + ParameterizedUriSegment actionSegment = Assert.IsAssignableFrom(segments[3]); + Assert.True(actionSegment.IsDirectory); + Assert.Equal("action", actionSegment.TemplateParameterName); + Assert.False(actionSegment.IsOptional); + + ParameterizedUriSegment idSegment = Assert.IsAssignableFrom(segments[4]); + Assert.True(idSegment.IsDirectory); + Assert.Equal("id", idSegment.TemplateParameterName); + Assert.True(idSegment.IsOptional); + + LiteralUriSegment propertiesSegment = Assert.IsAssignableFrom(segments[5]); + Assert.False(propertiesSegment.IsDirectory); + Assert.Equal("properties", propertiesSegment.Value); + + ParameterizedQuerySegment propertyIdsSegment = Assert.IsAssignableFrom(segments[6]); + Assert.Equal("propertyIds", propertyIdsSegment.QueryParameterName); + Assert.Equal("propertyGroupIds", propertyIdsSegment.TemplateParameterName); + Assert.False(propertyIdsSegment.IsOptional); + + ParameterizedQuerySegment diddlySegment = Assert.IsAssignableFrom(segments[7]); + Assert.Equal("diddly", diddlySegment.QueryParameterName); + Assert.Equal("dee", diddlySegment.TemplateParameterName); + Assert.True(diddlySegment.IsOptional); + + LiteralQuerySegment fooSegment = Assert.IsAssignableFrom(segments[8]); + Assert.Equal("foo", fooSegment.QueryParameterName); + Assert.Equal("bar", fooSegment.QueryParameterValue); + } + + /// + /// Verify that template with a query component can be populated. + /// + [Fact] + public void Can_Populate_Template_WithQuery() + { + UriTemplate template = new UriTemplate( + "api/{controller}/{action}/{id?}/properties?propertyIds={propertyGroupIds}&diddly={dee?}&foo=bar" + ); + + Uri generatedUri = template.Populate( + baseUri: new Uri("http://test-host/"), + templateParameters: new Dictionary + { + { "controller", "organizations" }, + { "action", "distinct" }, + { "propertyGroupIds", "System.OrganizationCommercial;EnterpriseMobility.OrganizationAirwatch" } + } + ); + + Assert.Equal( + "http://test-host/api/organizations/distinct/properties?propertyIds=System.OrganizationCommercial%3BEnterpriseMobility.OrganizationAirwatch&foo=bar", + generatedUri.AbsoluteUri + ); + } + } } diff --git a/test/HTTPlease.Diagnostics.Tests/LogEntry.cs b/test/HTTPlease.Diagnostics.Tests/LogEntry.cs index 0e51598..9c2668d 100644 --- a/test/HTTPlease.Diagnostics.Tests/LogEntry.cs +++ b/test/HTTPlease.Diagnostics.Tests/LogEntry.cs @@ -6,76 +6,76 @@ namespace HTTPlease.Diagnostics.Tests { - /// - /// Represents a log entry captured and republished by a . - /// - public class LogEntry - { - /// - /// Create a new . - /// - /// - /// The log entry's level (severity). - /// - /// - /// The log entry's event Id. - /// - /// - /// The log message. - /// - /// - /// The log entry's associated exception (if any). - /// - /// - /// State data associated with the log entry. - /// - /// - /// Properties (if any) associated with the log entry. - /// - public LogEntry(LogLevel level, EventId eventId, string message, Exception exception, object state, ImmutableDictionary properties) - { - if (message == null) - throw new ArgumentNullException(nameof(message)); + /// + /// Represents a log entry captured and republished by a . + /// + public class LogEntry + { + /// + /// Create a new . + /// + /// + /// The log entry's level (severity). + /// + /// + /// The log entry's event Id. + /// + /// + /// The log message. + /// + /// + /// The log entry's associated exception (if any). + /// + /// + /// State data associated with the log entry. + /// + /// + /// Properties (if any) associated with the log entry. + /// + public LogEntry(LogLevel level, EventId eventId, string message, Exception exception, object state, ImmutableDictionary properties) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); - if (properties == null) - throw new ArgumentNullException(nameof(properties)); + if (properties == null) + throw new ArgumentNullException(nameof(properties)); - Level = level; - EventId = eventId; - Message = message; - Exception = exception; - State = state; - Properties = properties; - } + Level = level; + EventId = eventId; + Message = message; + Exception = exception; + State = state; + Properties = properties; + } - /// - /// The log entry's level (severity). - /// - public LogLevel Level { get; } + /// + /// The log entry's level (severity). + /// + public LogLevel Level { get; } - /// - /// The log entry's event Id. - /// - public EventId EventId { get; } + /// + /// The log entry's event Id. + /// + public EventId EventId { get; } - /// - /// The log message. - /// - public string Message { get; } + /// + /// The log message. + /// + public string Message { get; } - /// - /// The log entry's associated exception (if any). - /// - public Exception Exception { get; } + /// + /// The log entry's associated exception (if any). + /// + public Exception Exception { get; } - /// - /// State data associated with the log entry. - /// - public object State { get; } + /// + /// State data associated with the log entry. + /// + public object State { get; } - /// - /// Properties associated with the log entry (if any). - /// - public ImmutableDictionary Properties { get; } - } + /// + /// Properties associated with the log entry (if any). + /// + public ImmutableDictionary Properties { get; } + } } \ No newline at end of file diff --git a/test/HTTPlease.Diagnostics.Tests/LoggingTests.cs b/test/HTTPlease.Diagnostics.Tests/LoggingTests.cs index d49582e..8817533 100644 --- a/test/HTTPlease.Diagnostics.Tests/LoggingTests.cs +++ b/test/HTTPlease.Diagnostics.Tests/LoggingTests.cs @@ -11,136 +11,136 @@ namespace HTTPlease.Diagnostics.Tests { using Formatters; using Testability; - using Testability.Mocks; + using Testability.Mocks; - /// - /// Tests for the HTTPlease.Diagnostics logging facility. - /// - public sealed class LoggingTests + /// + /// Tests for the HTTPlease.Diagnostics logging facility. + /// + public sealed class LoggingTests { - /// - /// Create a new logging test suite. - /// - public LoggingTests() - { - } - - /// - /// Verify that BeginRequest / EndRequest log entries are emitted for a successful HTTP GET request. - /// - [Fact(DisplayName = "Emit BeginRequest / EndRequest log entries for successful HTTP GET")] - public async Task Post_Request_Emits_LogEntries() - { - string expectedResponseBody = JsonConvert.ToString("hello test"); - - var logEntries = new List(); - - TestLogger logger = new TestLogger(LogLevel.Debug); - logger.LogEntries.Subscribe( - logEntry => logEntries.Add(logEntry) - ); - - ClientBuilder clientBuilder = new ClientBuilder() - .WithLogging(logger, - responseComponents: LogMessageComponents.Basic | LogMessageComponents.Body - ); - - HttpClient client = clientBuilder.CreateClient("http://localhost:1234", new MockMessageHandler( - request => request.CreateResponse(HttpStatusCode.OK, + /// + /// Create a new logging test suite. + /// + public LoggingTests() + { + } + + /// + /// Verify that BeginRequest / EndRequest log entries are emitted for a successful HTTP GET request. + /// + [Fact(DisplayName = "Emit BeginRequest / EndRequest log entries for successful HTTP GET")] + public async Task Post_Request_Emits_LogEntries() + { + string expectedResponseBody = JsonConvert.ToString("hello test"); + + var logEntries = new List(); + + TestLogger logger = new TestLogger(LogLevel.Debug); + logger.LogEntries.Subscribe( + logEntry => logEntries.Add(logEntry) + ); + + ClientBuilder clientBuilder = new ClientBuilder() + .WithLogging(logger, + responseComponents: LogMessageComponents.Basic | LogMessageComponents.Body + ); + + HttpClient client = clientBuilder.CreateClient("http://localhost:1234", new MockMessageHandler( + request => request.CreateResponse(HttpStatusCode.OK, responseBody: expectedResponseBody, - mediaType: WellKnownMediaTypes.Json - ) - )); - using (client) - using (HttpResponseMessage response = await client.GetAsync("/test")) - { - response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - Assert.Equal(expectedResponseBody, responseBody); - } - - Assert.Equal(3, logEntries.Count); - - LogEntry beginRequestEntry = logEntries[0]; - Assert.Equal(LogEventIds.BeginRequest, beginRequestEntry.EventId); - Assert.Equal("Performing GET request to 'http://localhost:1234/test'.", - beginRequestEntry.Message - ); - Assert.Equal("GET", - beginRequestEntry.Properties["Method"] - ); - Assert.Equal(new Uri("http://localhost:1234/test"), - beginRequestEntry.Properties["RequestUri"] - ); - - LogEntry responseBodyEntry = logEntries[1]; - Assert.Equal(LogEventIds.ResponseBody, responseBodyEntry.EventId); - Assert.Equal($"Receive response body for GET request to 'http://localhost:1234/test' (OK):\n{expectedResponseBody}", - responseBodyEntry.Message - ); - Assert.Equal("GET", - responseBodyEntry.Properties["Method"] - ); - Assert.Equal(new Uri("http://localhost:1234/test"), - responseBodyEntry.Properties["RequestUri"] - ); - Assert.Equal(expectedResponseBody, - responseBodyEntry.Properties["Body"] - ); - Assert.Equal(HttpStatusCode.OK, - responseBodyEntry.Properties["StatusCode"] - ); - - LogEntry endRequestEntry = logEntries[2]; - Assert.Equal(LogEventIds.EndRequest, endRequestEntry.EventId); - Assert.Equal("Completed GET request to 'http://localhost:1234/test' (OK).", - endRequestEntry.Message - ); - Assert.Equal("GET", - endRequestEntry.Properties["Method"] - ); - Assert.Equal(new Uri("http://localhost:1234/test"), - endRequestEntry.Properties["RequestUri"] - ); - Assert.Equal(HttpStatusCode.OK, - endRequestEntry.Properties["StatusCode"] - ); - } - - /// - /// Verify that can emit an informational log entry. - /// - [Fact(DisplayName = "TestLogger.LogInformation succeeds")] - public void TestLogger_LogInformation_Success() - { - const string name = "World"; - const LogLevel expectedLogLevel = LogLevel.Information; - - var logEntries = new List(); - - TestLogger logger = new TestLogger(LogLevel.Information); - logger.LogEntries.Subscribe( - logEntry => logEntries.Add(logEntry) - ); - - logger.LogInformation("Hello, {Name}!", name); - - Assert.Single(logEntries, logEntry => - { - Assert.Equal(new EventId(), logEntry.EventId); - Assert.Null(logEntry.EventId.Name); - - Assert.Equal(expectedLogLevel, logEntry.Level); - - Assert.Equal($"Hello, {name}!", logEntry.Message); - Assert.True( - logEntry.Properties.ContainsKey("Name") - ); - Assert.Equal(name, logEntry.Properties["Name"]); - - return true; - }); - } + mediaType: WellKnownMediaTypes.Json + ) + )); + using (client) + using (HttpResponseMessage response = await client.GetAsync("/test")) + { + response.EnsureSuccessStatusCode(); + + string responseBody = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedResponseBody, responseBody); + } + + Assert.Equal(3, logEntries.Count); + + LogEntry beginRequestEntry = logEntries[0]; + Assert.Equal(LogEventIds.BeginRequest, beginRequestEntry.EventId); + Assert.Equal("Performing GET request to 'http://localhost:1234/test'.", + beginRequestEntry.Message + ); + Assert.Equal("GET", + beginRequestEntry.Properties["Method"] + ); + Assert.Equal(new Uri("http://localhost:1234/test"), + beginRequestEntry.Properties["RequestUri"] + ); + + LogEntry responseBodyEntry = logEntries[1]; + Assert.Equal(LogEventIds.ResponseBody, responseBodyEntry.EventId); + Assert.Equal($"Receive response body for GET request to 'http://localhost:1234/test' (OK):\n{expectedResponseBody}", + responseBodyEntry.Message + ); + Assert.Equal("GET", + responseBodyEntry.Properties["Method"] + ); + Assert.Equal(new Uri("http://localhost:1234/test"), + responseBodyEntry.Properties["RequestUri"] + ); + Assert.Equal(expectedResponseBody, + responseBodyEntry.Properties["Body"] + ); + Assert.Equal(HttpStatusCode.OK, + responseBodyEntry.Properties["StatusCode"] + ); + + LogEntry endRequestEntry = logEntries[2]; + Assert.Equal(LogEventIds.EndRequest, endRequestEntry.EventId); + Assert.Equal("Completed GET request to 'http://localhost:1234/test' (OK).", + endRequestEntry.Message + ); + Assert.Equal("GET", + endRequestEntry.Properties["Method"] + ); + Assert.Equal(new Uri("http://localhost:1234/test"), + endRequestEntry.Properties["RequestUri"] + ); + Assert.Equal(HttpStatusCode.OK, + endRequestEntry.Properties["StatusCode"] + ); + } + + /// + /// Verify that can emit an informational log entry. + /// + [Fact(DisplayName = "TestLogger.LogInformation succeeds")] + public void TestLogger_LogInformation_Success() + { + const string name = "World"; + const LogLevel expectedLogLevel = LogLevel.Information; + + var logEntries = new List(); + + TestLogger logger = new TestLogger(LogLevel.Information); + logger.LogEntries.Subscribe( + logEntry => logEntries.Add(logEntry) + ); + + logger.LogInformation("Hello, {Name}!", name); + + Assert.Single(logEntries, logEntry => + { + Assert.Equal(new EventId(), logEntry.EventId); + Assert.Null(logEntry.EventId.Name); + + Assert.Equal(expectedLogLevel, logEntry.Level); + + Assert.Equal($"Hello, {name}!", logEntry.Message); + Assert.True( + logEntry.Properties.ContainsKey("Name") + ); + Assert.Equal(name, logEntry.Properties["Name"]); + + return true; + }); + } } } diff --git a/test/HTTPlease.Diagnostics.Tests/TestLogger.cs b/test/HTTPlease.Diagnostics.Tests/TestLogger.cs index 7b6986c..b47b792 100644 --- a/test/HTTPlease.Diagnostics.Tests/TestLogger.cs +++ b/test/HTTPlease.Diagnostics.Tests/TestLogger.cs @@ -7,98 +7,98 @@ namespace HTTPlease.Diagnostics.Tests { - /// - /// A stub implementation of for use in tests. - /// - public sealed class TestLogger - : ILogger, IDisposable - { - readonly Subject _logEntries = new Subject(); + /// + /// A stub implementation of for use in tests. + /// + public sealed class TestLogger + : ILogger, IDisposable + { + readonly Subject _logEntries = new Subject(); - /// - /// Create a new . - /// - /// - /// The logger's minimum log level. - /// - public TestLogger(LogLevel logLevel) - { - LogLevel = logLevel; - } + /// + /// Create a new . + /// + /// + /// The logger's minimum log level. + /// + public TestLogger(LogLevel logLevel) + { + LogLevel = logLevel; + } - /// - /// Dispose of resources being used by the . - /// - public void Dispose() => _logEntries.Dispose(); + /// + /// Dispose of resources being used by the . + /// + public void Dispose() => _logEntries.Dispose(); - /// - /// The logger's minimum log level. - /// - public LogLevel LogLevel { get; } + /// + /// The logger's minimum log level. + /// + public LogLevel LogLevel { get; } - /// - /// An observable sequence of log entries emitted by the . - /// - public IObservable LogEntries => _logEntries; + /// + /// An observable sequence of log entries emitted by the . + /// + public IObservable LogEntries => _logEntries; - /// - /// Emit a log entry. + /// + /// Emit a log entry. /// /// - /// The log entry's level. - /// + /// The log entry's level. + /// /// - /// The log entry's associated event Id. - /// + /// The log entry's associated event Id. + /// /// - /// The log entry to be written. Can be also an object. - /// + /// The log entry to be written. Can be also an object. + /// /// - /// The exception (if any) related to the log entry. - /// + /// The exception (if any) related to the log entry. + /// /// - /// A function that creates a string log message from the and . - /// + /// A function that creates a string log message from the and . + /// public void Log(LogLevel level, EventId eventId, TState state, Exception exception, Func formatter) - { - if (formatter == null) - throw new ArgumentNullException(nameof(formatter)); + { + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); - ImmutableDictionary properties = - (state is FormattedLogValues formattedLogValues) - ? ImmutableDictionary.CreateRange(formattedLogValues) - : ImmutableDictionary.Empty; + ImmutableDictionary properties = + (state is FormattedLogValues formattedLogValues) + ? ImmutableDictionary.CreateRange(formattedLogValues) + : ImmutableDictionary.Empty; - _logEntries.OnNext(new LogEntry( - level, - eventId, - formatter(state, exception), - exception, - state, - properties - )); - } + _logEntries.OnNext(new LogEntry( + level, + eventId, + formatter(state, exception), + exception, + state, + properties + )); + } /// - /// Check if the given is enabled. + /// Check if the given is enabled. /// /// - /// The level to be checked. - /// + /// The level to be checked. + /// /// - /// true if enabled; otherwise, false. - /// + /// true if enabled; otherwise, false. + /// public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel; /// - /// Begin a logical operation scope. + /// Begin a logical operation scope. /// /// - /// An identifier for the scope. - /// + /// An identifier for the scope. + /// /// - /// An that ends the logical operation scope when disposed. - /// - public IDisposable BeginScope(TState state) => Disposable.Empty; - } + /// An that ends the logical operation scope when disposed. + /// + public IDisposable BeginScope(TState state) => Disposable.Empty; + } } diff --git a/test/HTTPlease.Formatters.FunctionalTests/FormattedRequestTests.cs b/test/HTTPlease.Formatters.FunctionalTests/FormattedRequestTests.cs index 3abce15..67610cb 100644 --- a/test/HTTPlease.Formatters.FunctionalTests/FormattedRequestTests.cs +++ b/test/HTTPlease.Formatters.FunctionalTests/FormattedRequestTests.cs @@ -5,138 +5,138 @@ namespace HTTPlease.Formatters.FunctionalTests { - using Testability; - using Tests; + using Testability; + using Tests; - /// - /// Tests for HTTP requests using content formatters. - /// - public class FormattedRequestTests - { - /// - /// The base URI for requests used by tests. - /// - static readonly Uri BaseUri = new Uri("http://localhost:1234/"); + /// + /// Tests for HTTP requests using content formatters. + /// + public class FormattedRequestTests + { + /// + /// The base URI for requests used by tests. + /// + static readonly Uri BaseUri = new Uri("http://localhost:1234/"); - /// - /// The base definition used by tests. - /// - static readonly HttpRequest BaseRequest = HttpRequest.Factory.Create(BaseUri); + /// + /// The base definition used by tests. + /// + static readonly HttpRequest BaseRequest = HttpRequest.Factory.Create(BaseUri); - /// - /// Verify that a request builder can build a request with an absolute and then relative template URI, then perform an HTTP GET request. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Request_Get_RelativeTemplateUri() - { - HttpClient client = JsonTestClients.ExpectJson( - new Uri(BaseUri, "foo/1234/bar?diddly=bonk"), HttpMethod.Get, - responseBody: "Success!" - ); - using (client) - { - HttpRequest request = - BaseRequest.WithRelativeUri("foo/{variable}/bar") - .WithQueryParameter("diddly", "bonk") - .WithTemplateParameter("variable", 1234) - .WithTemplateParameter("diddly", "bonk") - .UseJson().ExpectJson(); + /// + /// Verify that a request builder can build a request with an absolute and then relative template URI, then perform an HTTP GET request. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Request_Get_RelativeTemplateUri() + { + HttpClient client = JsonTestClients.ExpectJson( + new Uri(BaseUri, "foo/1234/bar?diddly=bonk"), HttpMethod.Get, + responseBody: "Success!" + ); + using (client) + { + HttpRequest request = + BaseRequest.WithRelativeUri("foo/{variable}/bar") + .WithQueryParameter("diddly", "bonk") + .WithTemplateParameter("variable", 1234) + .WithTemplateParameter("diddly", "bonk") + .UseJson().ExpectJson(); - using (HttpResponseMessage response = await client.GetAsync(request)) - { - Assert.True(response.IsSuccessStatusCode); - Assert.NotNull(response.Content); - Assert.Equal(WellKnownMediaTypes.Json, response.Content.Headers.ContentType.MediaType); + using (HttpResponseMessage response = await client.GetAsync(request)) + { + Assert.True(response.IsSuccessStatusCode); + Assert.NotNull(response.Content); + Assert.Equal(WellKnownMediaTypes.Json, response.Content.Headers.ContentType.MediaType); - string responseBody = await response.ReadContentAsAsync(); - Assert.Equal("Success!", responseBody); - } - } - } + string responseBody = await response.ReadContentAsAsync(); + Assert.Equal("Success!", responseBody); + } + } + } - /// - /// Verify that a request builder can build a request with an absolute and then relative URI, expecting a JSON response, and then perform an HTTP POST request. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Request_Post_RelativeUri_ExpectJson() - { - HttpClient client = JsonTestClients.ExpectJson( - new Uri(BaseUri, "foo/bar"), HttpMethod.Post, - responseBody: "Success!", - assertion: async request => - { - MessageAssert.AcceptsMediaType(request, WellKnownMediaTypes.Json); + /// + /// Verify that a request builder can build a request with an absolute and then relative URI, expecting a JSON response, and then perform an HTTP POST request. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Request_Post_RelativeUri_ExpectJson() + { + HttpClient client = JsonTestClients.ExpectJson( + new Uri(BaseUri, "foo/bar"), HttpMethod.Post, + responseBody: "Success!", + assertion: async request => + { + MessageAssert.AcceptsMediaType(request, WellKnownMediaTypes.Json); - await MessageAssert.BodyIsAsync(request, - "{\"Foo\":\"Bar\",\"Baz\":1234}" - ); - } - ); - using (client) - { - HttpRequest request = - BaseRequest.WithRelativeUri("foo/bar") - .UseJson().ExpectJson(); + await MessageAssert.BodyIsAsync(request, + "{\"Foo\":\"Bar\",\"Baz\":1234}" + ); + } + ); + using (client) + { + HttpRequest request = + BaseRequest.WithRelativeUri("foo/bar") + .UseJson().ExpectJson(); - HttpResponseMessage response = await - client.PostAsJsonAsync(request, - postBody: new - { - Foo = "Bar", - Baz = 1234 - } - ); + HttpResponseMessage response = await + client.PostAsJsonAsync(request, + postBody: new + { + Foo = "Bar", + Baz = 1234 + } + ); - using (response) - { - Assert.True(response.IsSuccessStatusCode); - Assert.NotNull(response.Content?.Headers?.ContentType); - Assert.Equal(WellKnownMediaTypes.Json, response.Content.Headers.ContentType.MediaType); + using (response) + { + Assert.True(response.IsSuccessStatusCode); + Assert.NotNull(response.Content?.Headers?.ContentType); + Assert.Equal(WellKnownMediaTypes.Json, response.Content.Headers.ContentType.MediaType); - string responseBody = await response.ReadContentAsAsync(); - Assert.Equal("Success!", responseBody); - } - } - } + string responseBody = await response.ReadContentAsAsync(); + Assert.Equal("Success!", responseBody); + } + } + } - /// - /// Verify that a request builder can build a request with an absolute and then relative URI, and then perform a JSON-formatted HTTP POST request. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Request_PostAsJson_RelativeUri() - { - HttpClient client = JsonTestClients.ExpectJson( - new Uri(BaseUri, "foo/bar"), HttpMethod.Post, responseBody: 1234, - assertion: async request => - { - MessageAssert.AcceptsMediaType(request, WellKnownMediaTypes.Json); + /// + /// Verify that a request builder can build a request with an absolute and then relative URI, and then perform a JSON-formatted HTTP POST request. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Request_PostAsJson_RelativeUri() + { + HttpClient client = JsonTestClients.ExpectJson( + new Uri(BaseUri, "foo/bar"), HttpMethod.Post, responseBody: 1234, + assertion: async request => + { + MessageAssert.AcceptsMediaType(request, WellKnownMediaTypes.Json); - await MessageAssert.BodyIsAsync(request, "\"1234\""); - } - ); - using (client) - { - HttpRequest request = - BaseRequest.WithRelativeUri("foo/bar") - .UseJson().ExpectJson(); + await MessageAssert.BodyIsAsync(request, "\"1234\""); + } + ); + using (client) + { + HttpRequest request = + BaseRequest.WithRelativeUri("foo/bar") + .UseJson().ExpectJson(); - int responseBody = await - client.PostAsJsonAsync(request, - postBody: 1234.ToString() - ) - .ReadContentAsAsync(); + int responseBody = await + client.PostAsJsonAsync(request, + postBody: 1234.ToString() + ) + .ReadContentAsAsync(); - Assert.Equal(1234, responseBody); - } - } - } + Assert.Equal(1234, responseBody); + } + } + } } diff --git a/test/HTTPlease.Formatters.Tests/JsonFormattedRequestTests.cs b/test/HTTPlease.Formatters.Tests/JsonFormattedRequestTests.cs index b03bd66..83bb5c8 100644 --- a/test/HTTPlease.Formatters.Tests/JsonFormattedRequestTests.cs +++ b/test/HTTPlease.Formatters.Tests/JsonFormattedRequestTests.cs @@ -3,45 +3,45 @@ namespace HTTPlease.Formatters.Tests { - using Testability; + using Testability; - /// - /// Tests for JSON-formatted HTTP requests. - /// + /// + /// Tests for JSON-formatted HTTP requests. + /// public class JsonFormattedRequestTests { - /// - /// The base request used for tests. - /// - static readonly HttpRequest BaseRequest = HttpRequest.Factory.Create("http://localhost/"); + /// + /// The base request used for tests. + /// + static readonly HttpRequest BaseRequest = HttpRequest.Factory.Create("http://localhost/"); - /// - /// The base typed request used for tests. - /// - static readonly HttpRequest TypedBaseRequest = HttpRequest.Factory.Create("http://localhost/"); + /// + /// The base typed request used for tests. + /// + static readonly HttpRequest TypedBaseRequest = HttpRequest.Factory.Create("http://localhost/"); - /// - /// Verify that the ExpectJson extension method for adds the "application/json" JSON media type to the request's Accept header. - /// - [Fact] - public void Request_ExpectJson_Sets_AcceptHeader() - { - RequestAssert.Message(BaseRequest.ExpectJson(), HttpMethod.Get, requestMessage => - { - MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Json); - }); - } + /// + /// Verify that the ExpectJson extension method for adds the "application/json" JSON media type to the request's Accept header. + /// + [Fact] + public void Request_ExpectJson_Sets_AcceptHeader() + { + RequestAssert.Message(BaseRequest.ExpectJson(), HttpMethod.Get, requestMessage => + { + MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Json); + }); + } - /// - /// Verify that the ExpectJson extension method for adds the "application/json" JSON media type to the request's Accept header. - /// - [Fact] - public void TypedRequest_ExpectJson_Sets_AcceptHeader() - { - RequestAssert.Message(TypedBaseRequest.ExpectJson(), HttpMethod.Get, requestMessage => - { - MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Json); - }); - } - } + /// + /// Verify that the ExpectJson extension method for adds the "application/json" JSON media type to the request's Accept header. + /// + [Fact] + public void TypedRequest_ExpectJson_Sets_AcceptHeader() + { + RequestAssert.Message(TypedBaseRequest.ExpectJson(), HttpMethod.Get, requestMessage => + { + MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Json); + }); + } + } } diff --git a/test/HTTPlease.Formatters.Tests/JsonTestClients.cs b/test/HTTPlease.Formatters.Tests/JsonTestClients.cs index 7b82ef8..04b5f55 100644 --- a/test/HTTPlease.Formatters.Tests/JsonTestClients.cs +++ b/test/HTTPlease.Formatters.Tests/JsonTestClients.cs @@ -5,291 +5,291 @@ namespace HTTPlease.Formatters.Tests { - using Json; - using Testability; - - /// - /// Factory methods for JSON-formatted mocked s used by tests. - /// - public static class JsonTestClients + using Json; + using Testability; + + /// + /// Factory methods for JSON-formatted mocked s used by tests. + /// + public static class JsonTestClients { - /// - /// Create an that always responds to requests with the specified status code. - /// - /// - /// The response body type. - /// - /// - /// The HTTP status code. - /// - /// - /// The response body (will be serialised as JSON). - /// - /// - /// The configured . - /// - public static HttpClient RespondWith(HttpStatusCode statusCode, TBody body) - { - return TestClients.RespondWith( - request => request.CreateResponse(statusCode, body, WellKnownMediaTypes.Json, new JsonFormatter()) - ); - } + /// + /// Create an that always responds to requests with the specified status code. + /// + /// + /// The response body type. + /// + /// + /// The HTTP status code. + /// + /// + /// The response body (will be serialised as JSON). + /// + /// + /// The configured . + /// + public static HttpClient RespondWith(HttpStatusCode statusCode, TBody body) + { + return TestClients.RespondWith( + request => request.CreateResponse(statusCode, body, WellKnownMediaTypes.Json, new JsonFormatter()) + ); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody) - { - return ExpectJson(expectedRequestUri, expectedRequestMethod, responseBody, assertion: null); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody) + { + return ExpectJson(expectedRequestUri, expectedRequestMethod, responseBody, assertion: null); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Action assertion) - { - return ExpectJson(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, assertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Action assertion) + { + return ExpectJson(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, assertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Action assertion) - { - return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Json, new JsonFormatter(), assertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Action assertion) + { + return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Json, new JsonFormatter(), assertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// The media type for the ougoing response message body. - /// - /// - /// The used to serialise the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Action assertion) - { - if (expectedRequestUri == null) - throw new ArgumentNullException(nameof(expectedRequestUri)); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// The media type for the ougoing response message body. + /// + /// + /// The used to serialise the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Action assertion) + { + if (expectedRequestUri == null) + throw new ArgumentNullException(nameof(expectedRequestUri)); - if (expectedRequestMethod == null) - throw new ArgumentNullException(nameof(expectedRequestMethod)); + if (expectedRequestMethod == null) + throw new ArgumentNullException(nameof(expectedRequestMethod)); - if (String.IsNullOrWhiteSpace(responseMediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); + if (String.IsNullOrWhiteSpace(responseMediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); - if (responseFormatter == null) - throw new ArgumentNullException(nameof(responseFormatter)); + if (responseFormatter == null) + throw new ArgumentNullException(nameof(responseFormatter)); - return TestClients.RespondWith(requestMessage => - { - Xunit.Assert.NotNull(requestMessage); - Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); - Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); + return TestClients.RespondWith(requestMessage => + { + Xunit.Assert.NotNull(requestMessage); + Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); + Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); - assertion?.Invoke(requestMessage); + assertion?.Invoke(requestMessage); - return requestMessage.CreateResponse( - responseStatusCode, - responseBody, - responseMediaType, - new JsonFormatter() - ); - }); - } + return requestMessage.CreateResponse( + responseStatusCode, + responseBody, + responseMediaType, + new JsonFormatter() + ); + }); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Func asyncAssertion) - { - return ExpectJson(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, asyncAssertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Func asyncAssertion) + { + return ExpectJson(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, asyncAssertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Func asyncAssertion) - { - return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Json, new JsonFormatter(), asyncAssertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (JSON-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectJson(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Func asyncAssertion) + { + return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Json, new JsonFormatter(), asyncAssertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// The media type for the ougoing response message body. - /// - /// - /// The used to serialise the outgoing response message. - /// - /// - /// An asynchronous delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Func asyncAssertion) - { - if (expectedRequestUri == null) - throw new ArgumentNullException(nameof(expectedRequestUri)); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// The media type for the ougoing response message body. + /// + /// + /// The used to serialise the outgoing response message. + /// + /// + /// An asynchronous delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Func asyncAssertion) + { + if (expectedRequestUri == null) + throw new ArgumentNullException(nameof(expectedRequestUri)); - if (expectedRequestMethod == null) - throw new ArgumentNullException(nameof(expectedRequestMethod)); + if (expectedRequestMethod == null) + throw new ArgumentNullException(nameof(expectedRequestMethod)); - if (String.IsNullOrWhiteSpace(responseMediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); + if (String.IsNullOrWhiteSpace(responseMediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); - if (responseFormatter == null) - throw new ArgumentNullException(nameof(responseFormatter)); + if (responseFormatter == null) + throw new ArgumentNullException(nameof(responseFormatter)); - return TestClients.AsyncRespondWith(async requestMessage => - { - Xunit.Assert.NotNull(requestMessage); - Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); - Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); + return TestClients.AsyncRespondWith(async requestMessage => + { + Xunit.Assert.NotNull(requestMessage); + Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); + Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); - Task assertionTask = asyncAssertion?.Invoke(requestMessage); - if (assertionTask != null) - await assertionTask; + Task assertionTask = asyncAssertion?.Invoke(requestMessage); + if (assertionTask != null) + await assertionTask; - return requestMessage.CreateResponse( - responseStatusCode, - responseBody, - responseMediaType, - new JsonFormatter() - ); - }); - } - } + return requestMessage.CreateResponse( + responseStatusCode, + responseBody, + responseMediaType, + new JsonFormatter() + ); + }); + } + } } diff --git a/test/HTTPlease.Formatters.Tests/MessageExtensions.cs b/test/HTTPlease.Formatters.Tests/MessageExtensions.cs index 7ae4f81..0a87920 100644 --- a/test/HTTPlease.Formatters.Tests/MessageExtensions.cs +++ b/test/HTTPlease.Formatters.Tests/MessageExtensions.cs @@ -4,64 +4,64 @@ namespace HTTPlease.Formatters.Tests { - using Testability; + using Testability; - /// - /// Formatter-related extension methods for / . - /// - public static class MessageExtensions - { - /// - /// Create a response message. - /// - /// - /// The type of object that represents the response body. - /// - /// - /// The response message. - /// - /// - /// The response status code. - /// - /// - /// The response body. - /// - /// - /// The target media type for the response body. - /// - /// - /// The content formatter that will serialise the response body. - /// - /// - /// The configured response message. - /// - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, TBody body, string mediaType, IOutputFormatter formatter) - { - if (request == null) - throw new ArgumentNullException(nameof(request)); + /// + /// Formatter-related extension methods for / . + /// + public static class MessageExtensions + { + /// + /// Create a response message. + /// + /// + /// The type of object that represents the response body. + /// + /// + /// The response message. + /// + /// + /// The response status code. + /// + /// + /// The response body. + /// + /// + /// The target media type for the response body. + /// + /// + /// The content formatter that will serialise the response body. + /// + /// + /// The configured response message. + /// + public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, TBody body, string mediaType, IOutputFormatter formatter) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); - if (String.IsNullOrWhiteSpace(mediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); + if (String.IsNullOrWhiteSpace(mediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'mediaType'.", nameof(mediaType)); - HttpResponseMessage response = request.CreateResponse(statusCode); - try - { - response.Content = new FormattedObjectContent( - formatter, - typeof(TBody), - body, - mediaType - ); - } - catch - { - using (response) - { - throw; - } - } + HttpResponseMessage response = request.CreateResponse(statusCode); + try + { + response.Content = new FormattedObjectContent( + formatter, + typeof(TBody), + body, + mediaType + ); + } + catch + { + using (response) + { + throw; + } + } - return response; - } - } + return response; + } + } } diff --git a/test/HTTPlease.Formatters.Tests/ReadResponseTests.cs b/test/HTTPlease.Formatters.Tests/ReadResponseTests.cs index d747ba9..5e23c41 100644 --- a/test/HTTPlease.Formatters.Tests/ReadResponseTests.cs +++ b/test/HTTPlease.Formatters.Tests/ReadResponseTests.cs @@ -5,237 +5,237 @@ namespace HTTPlease.Formatters.Tests { - using Json; - using Xml; + using Json; + using Xml; - /// - /// Tests for reading responses from invoked messages. - /// - public class ReadResponseTests + /// + /// Tests for reading responses from invoked messages. + /// + public class ReadResponseTests { - /// - /// The default request used for tests. - /// - static readonly HttpRequest DefaultRequest = HttpRequest.Factory.Create("http://localhost/"); - - /// - /// Create a new response-read test suite. - /// - public ReadResponseTests() - { - } - - #region JSON - - /// - /// Verify that the body of an 's response (with a status code that indicates success) can be read using the JSON formatter. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Response_Read_Json_Success() - { - TestBody expectedBody = new TestBody - { - StringProperty = "This is a test", - IntProperty = 123 - }; - using (HttpClient client = JsonTestClients.RespondWith(HttpStatusCode.OK, expectedBody)) - { - TestBody actualBody = await client - .GetAsync(DefaultRequest) - .ReadContentAsAsync(new JsonFormatter()); - - Assert.NotNull(actualBody); - Assert.NotSame(expectedBody, actualBody); - Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); - Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); - } - } - - /// - /// Verify that the body of an 's response (with a status code that has been declared as indicating success) can be read using the JSON formatter. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Response_Read_Json_TreatAsSuccess() - { - TestBody expectedBody = new TestBody - { - StringProperty = "This is a test", - IntProperty = 123 - }; - using (HttpClient client = JsonTestClients.RespondWith(HttpStatusCode.BadRequest, expectedBody)) - { - TestBody actualBody = await client - .GetAsync(DefaultRequest) - .ReadContentAsAsync(new JsonFormatter(), HttpStatusCode.OK, HttpStatusCode.BadRequest); - - Assert.NotNull(actualBody); - Assert.NotSame(expectedBody, actualBody); - Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); - Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); - } - } - - /// - /// Verify that the body of an 's response (with a status code that indicates failure) can be transformed by the onFailureResponse handler into a valid response body. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Response_Read_Json_Failure_Transformed() - { - TestBody expectedBody = new TestBody - { - StringProperty = "This is a failure response", - IntProperty = 456 - }; - - TestBody responseBody = new TestBody - { - StringProperty = "This is a test", - IntProperty = 123 - }; - using (HttpClient client = JsonTestClients.RespondWith(HttpStatusCode.BadRequest, responseBody)) - { - TestBody actualBody = await client - .GetAsync(DefaultRequest) - .ReadContentAsAsync(new JsonFormatter(), - onFailureResponse: () => new TestBody - { - StringProperty = expectedBody.StringProperty, - IntProperty = expectedBody.IntProperty - } - ); - - Assert.NotNull(actualBody); - Assert.NotSame(expectedBody, actualBody); - Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); - Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); - } - } - - #endregion // JSON - - #region XML - - /// - /// Verify that the body of an 's response (with a status code that indicates success) can be read using the XML formatter. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Response_Read_Xml_Success() - { - TestBody expectedBody = new TestBody - { - StringProperty = "This is a test", - IntProperty = 123 - }; - using (HttpClient client = XmlTestClients.RespondWith(HttpStatusCode.OK, expectedBody)) - { - TestBody actualBody = await client - .GetAsync(DefaultRequest) - .ReadContentAsAsync(new XmlFormatter()); - - Assert.NotNull(actualBody); - Assert.NotSame(expectedBody, actualBody); - Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); - Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); - } - } - - /// - /// Verify that the body of an 's response (with a status code that has been declared as indicating success) can be read using the XML formatter. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Response_Read_Xml_TreatAsSuccess() - { - TestBody expectedBody = new TestBody - { - StringProperty = "This is a test", - IntProperty = 123 - }; - using (HttpClient client = XmlTestClients.RespondWith(HttpStatusCode.BadRequest, expectedBody)) - { - TestBody actualBody = await client - .GetAsync(DefaultRequest) - .ReadContentAsAsync(new XmlFormatter(), HttpStatusCode.OK, HttpStatusCode.BadRequest); - - Assert.NotNull(actualBody); - Assert.NotSame(expectedBody, actualBody); - Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); - Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); - } - } - - /// - /// Verify that the body of an 's response (with a status code that indicates failure) can be transformed by the onFailureResponse handler into a valid response body. - /// - /// - /// A representing asynchronous test execution. - /// - [Fact] - public async Task Response_Read_Xml_Failure_Transformed() - { - TestBody expectedBody = new TestBody - { - StringProperty = "This is a failure response", - IntProperty = 456 - }; - - TestBody responseBody = new TestBody - { - StringProperty = "This is a test", - IntProperty = 123 - }; - using (HttpClient client = XmlTestClients.RespondWith(HttpStatusCode.BadRequest, responseBody)) - { - TestBody actualBody = await client - .GetAsync(DefaultRequest) - .ReadContentAsAsync(new XmlFormatter(), - onFailureResponse: () => new TestBody - { - StringProperty = expectedBody.StringProperty, - IntProperty = expectedBody.IntProperty - } - ); - - Assert.NotNull(actualBody); - Assert.NotSame(expectedBody, actualBody); - Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); - Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); - } - } - - #endregion // XML - - /// - /// A simple data-type to be deserialised from the response. - /// - /// - /// Must be public to support XML serialisation (or have the DataContract attribute applied to it). - /// - public class TestBody - { - /// - /// A string property. - /// - public string StringProperty { get; set; } - - /// - /// An integer property. - /// - public int IntProperty { get; set; } - } + /// + /// The default request used for tests. + /// + static readonly HttpRequest DefaultRequest = HttpRequest.Factory.Create("http://localhost/"); + + /// + /// Create a new response-read test suite. + /// + public ReadResponseTests() + { + } + + #region JSON + + /// + /// Verify that the body of an 's response (with a status code that indicates success) can be read using the JSON formatter. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Response_Read_Json_Success() + { + TestBody expectedBody = new TestBody + { + StringProperty = "This is a test", + IntProperty = 123 + }; + using (HttpClient client = JsonTestClients.RespondWith(HttpStatusCode.OK, expectedBody)) + { + TestBody actualBody = await client + .GetAsync(DefaultRequest) + .ReadContentAsAsync(new JsonFormatter()); + + Assert.NotNull(actualBody); + Assert.NotSame(expectedBody, actualBody); + Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); + Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); + } + } + + /// + /// Verify that the body of an 's response (with a status code that has been declared as indicating success) can be read using the JSON formatter. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Response_Read_Json_TreatAsSuccess() + { + TestBody expectedBody = new TestBody + { + StringProperty = "This is a test", + IntProperty = 123 + }; + using (HttpClient client = JsonTestClients.RespondWith(HttpStatusCode.BadRequest, expectedBody)) + { + TestBody actualBody = await client + .GetAsync(DefaultRequest) + .ReadContentAsAsync(new JsonFormatter(), HttpStatusCode.OK, HttpStatusCode.BadRequest); + + Assert.NotNull(actualBody); + Assert.NotSame(expectedBody, actualBody); + Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); + Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); + } + } + + /// + /// Verify that the body of an 's response (with a status code that indicates failure) can be transformed by the onFailureResponse handler into a valid response body. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Response_Read_Json_Failure_Transformed() + { + TestBody expectedBody = new TestBody + { + StringProperty = "This is a failure response", + IntProperty = 456 + }; + + TestBody responseBody = new TestBody + { + StringProperty = "This is a test", + IntProperty = 123 + }; + using (HttpClient client = JsonTestClients.RespondWith(HttpStatusCode.BadRequest, responseBody)) + { + TestBody actualBody = await client + .GetAsync(DefaultRequest) + .ReadContentAsAsync(new JsonFormatter(), + onFailureResponse: () => new TestBody + { + StringProperty = expectedBody.StringProperty, + IntProperty = expectedBody.IntProperty + } + ); + + Assert.NotNull(actualBody); + Assert.NotSame(expectedBody, actualBody); + Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); + Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); + } + } + + #endregion // JSON + + #region XML + + /// + /// Verify that the body of an 's response (with a status code that indicates success) can be read using the XML formatter. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Response_Read_Xml_Success() + { + TestBody expectedBody = new TestBody + { + StringProperty = "This is a test", + IntProperty = 123 + }; + using (HttpClient client = XmlTestClients.RespondWith(HttpStatusCode.OK, expectedBody)) + { + TestBody actualBody = await client + .GetAsync(DefaultRequest) + .ReadContentAsAsync(new XmlFormatter()); + + Assert.NotNull(actualBody); + Assert.NotSame(expectedBody, actualBody); + Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); + Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); + } + } + + /// + /// Verify that the body of an 's response (with a status code that has been declared as indicating success) can be read using the XML formatter. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Response_Read_Xml_TreatAsSuccess() + { + TestBody expectedBody = new TestBody + { + StringProperty = "This is a test", + IntProperty = 123 + }; + using (HttpClient client = XmlTestClients.RespondWith(HttpStatusCode.BadRequest, expectedBody)) + { + TestBody actualBody = await client + .GetAsync(DefaultRequest) + .ReadContentAsAsync(new XmlFormatter(), HttpStatusCode.OK, HttpStatusCode.BadRequest); + + Assert.NotNull(actualBody); + Assert.NotSame(expectedBody, actualBody); + Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); + Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); + } + } + + /// + /// Verify that the body of an 's response (with a status code that indicates failure) can be transformed by the onFailureResponse handler into a valid response body. + /// + /// + /// A representing asynchronous test execution. + /// + [Fact] + public async Task Response_Read_Xml_Failure_Transformed() + { + TestBody expectedBody = new TestBody + { + StringProperty = "This is a failure response", + IntProperty = 456 + }; + + TestBody responseBody = new TestBody + { + StringProperty = "This is a test", + IntProperty = 123 + }; + using (HttpClient client = XmlTestClients.RespondWith(HttpStatusCode.BadRequest, responseBody)) + { + TestBody actualBody = await client + .GetAsync(DefaultRequest) + .ReadContentAsAsync(new XmlFormatter(), + onFailureResponse: () => new TestBody + { + StringProperty = expectedBody.StringProperty, + IntProperty = expectedBody.IntProperty + } + ); + + Assert.NotNull(actualBody); + Assert.NotSame(expectedBody, actualBody); + Assert.Equal(expectedBody.StringProperty, actualBody.StringProperty); + Assert.Equal(expectedBody.IntProperty, actualBody.IntProperty); + } + } + + #endregion // XML + + /// + /// A simple data-type to be deserialised from the response. + /// + /// + /// Must be public to support XML serialisation (or have the DataContract attribute applied to it). + /// + public class TestBody + { + /// + /// A string property. + /// + public string StringProperty { get; set; } + + /// + /// An integer property. + /// + public int IntProperty { get; set; } + } } } diff --git a/test/HTTPlease.Formatters.Tests/XmlFormattedRequestTests.cs b/test/HTTPlease.Formatters.Tests/XmlFormattedRequestTests.cs index a065f21..bece64d 100644 --- a/test/HTTPlease.Formatters.Tests/XmlFormattedRequestTests.cs +++ b/test/HTTPlease.Formatters.Tests/XmlFormattedRequestTests.cs @@ -3,45 +3,45 @@ namespace HTTPlease.Formatters.Tests { - using Testability; + using Testability; - /// - /// Tests for XML-formatted HTTP requests. - /// + /// + /// Tests for XML-formatted HTTP requests. + /// public class XmlFormattedRequestTests { - /// - /// The base request used for tests. - /// - static readonly HttpRequest BaseRequest = HttpRequest.Factory.Create("http://localhost/"); + /// + /// The base request used for tests. + /// + static readonly HttpRequest BaseRequest = HttpRequest.Factory.Create("http://localhost/"); - /// - /// The base typed request used for tests. - /// - static readonly HttpRequest TypedBaseRequest = HttpRequest.Factory.Create("http://localhost/"); + /// + /// The base typed request used for tests. + /// + static readonly HttpRequest TypedBaseRequest = HttpRequest.Factory.Create("http://localhost/"); - /// - /// Verify that the ExpectXml extension method for adds the "application/json" XML media type to the request's Accept header. - /// - [Fact] - public void Request_ExpectXml_Sets_AcceptHeader() - { - RequestAssert.Message(BaseRequest.ExpectXml(), HttpMethod.Get, requestMessage => - { - MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Xml); - }); - } + /// + /// Verify that the ExpectXml extension method for adds the "application/json" XML media type to the request's Accept header. + /// + [Fact] + public void Request_ExpectXml_Sets_AcceptHeader() + { + RequestAssert.Message(BaseRequest.ExpectXml(), HttpMethod.Get, requestMessage => + { + MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Xml); + }); + } - /// - /// Verify that the ExpectXml extension method for adds the "application/json" XML media type to the request's Accept header. - /// - [Fact] - public void TypedRequest_ExpectXml_Sets_AcceptHeader() - { - RequestAssert.Message(TypedBaseRequest.ExpectXml(), HttpMethod.Get, requestMessage => - { - MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Xml); - }); - } - } + /// + /// Verify that the ExpectXml extension method for adds the "application/json" XML media type to the request's Accept header. + /// + [Fact] + public void TypedRequest_ExpectXml_Sets_AcceptHeader() + { + RequestAssert.Message(TypedBaseRequest.ExpectXml(), HttpMethod.Get, requestMessage => + { + MessageAssert.AcceptsMediaType(requestMessage, WellKnownMediaTypes.Xml); + }); + } + } } diff --git a/test/HTTPlease.Formatters.Tests/XmlTestClients.cs b/test/HTTPlease.Formatters.Tests/XmlTestClients.cs index 286fe33..41cc542 100644 --- a/test/HTTPlease.Formatters.Tests/XmlTestClients.cs +++ b/test/HTTPlease.Formatters.Tests/XmlTestClients.cs @@ -5,291 +5,291 @@ namespace HTTPlease.Formatters.Tests { - using Xml; - using Testability; - - /// - /// Factory methods for XML-formatted mocked s used by tests. - /// - public static class XmlTestClients + using Xml; + using Testability; + + /// + /// Factory methods for XML-formatted mocked s used by tests. + /// + public static class XmlTestClients { - /// - /// Create an that always responds to requests with the specified status code. - /// - /// - /// The response body type. - /// - /// - /// The HTTP status code. - /// - /// - /// The response body (will be serialised as XML). - /// - /// - /// The configured . - /// - public static HttpClient RespondWith(HttpStatusCode statusCode, TBody body) - { - return TestClients.RespondWith( - request => request.CreateResponse(statusCode, body, WellKnownMediaTypes.Xml, new XmlFormatter()) - ); - } + /// + /// Create an that always responds to requests with the specified status code. + /// + /// + /// The response body type. + /// + /// + /// The HTTP status code. + /// + /// + /// The response body (will be serialised as XML). + /// + /// + /// The configured . + /// + public static HttpClient RespondWith(HttpStatusCode statusCode, TBody body) + { + return TestClients.RespondWith( + request => request.CreateResponse(statusCode, body, WellKnownMediaTypes.Xml, new XmlFormatter()) + ); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody) - { - return ExpectXml(expectedRequestUri, expectedRequestMethod, responseBody, assertion: null); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody) + { + return ExpectXml(expectedRequestUri, expectedRequestMethod, responseBody, assertion: null); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Action assertion) - { - return ExpectXml(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, assertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Action assertion) + { + return ExpectXml(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, assertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Action assertion) - { - return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Xml, new XmlFormatter(), assertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Action assertion) + { + return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Xml, new XmlFormatter(), assertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// The media type for the ougoing response message body. - /// - /// - /// The used to serialise the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Action assertion) - { - if (expectedRequestUri == null) - throw new ArgumentNullException(nameof(expectedRequestUri)); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// The media type for the ougoing response message body. + /// + /// + /// The used to serialise the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Action assertion) + { + if (expectedRequestUri == null) + throw new ArgumentNullException(nameof(expectedRequestUri)); - if (expectedRequestMethod == null) - throw new ArgumentNullException(nameof(expectedRequestMethod)); + if (expectedRequestMethod == null) + throw new ArgumentNullException(nameof(expectedRequestMethod)); - if (String.IsNullOrWhiteSpace(responseMediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); + if (String.IsNullOrWhiteSpace(responseMediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); - if (responseFormatter == null) - throw new ArgumentNullException(nameof(responseFormatter)); + if (responseFormatter == null) + throw new ArgumentNullException(nameof(responseFormatter)); - return TestClients.RespondWith(requestMessage => - { - Xunit.Assert.NotNull(requestMessage); - Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); - Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); + return TestClients.RespondWith(requestMessage => + { + Xunit.Assert.NotNull(requestMessage); + Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); + Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); - assertion?.Invoke(requestMessage); + assertion?.Invoke(requestMessage); - return requestMessage.CreateResponse( - responseStatusCode, - responseBody, - responseMediaType, - new XmlFormatter() - ); - }); - } + return requestMessage.CreateResponse( + responseStatusCode, + responseBody, + responseMediaType, + new XmlFormatter() + ); + }); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Func asyncAssertion) - { - return ExpectXml(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, asyncAssertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, TResponseBody responseBody, Func asyncAssertion) + { + return ExpectXml(expectedRequestUri, expectedRequestMethod, HttpStatusCode.OK, responseBody, asyncAssertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// A delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Func asyncAssertion) - { - return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Xml, new XmlFormatter(), asyncAssertion); - } + /// + /// Create an that performs assertions on an incoming request message and returns a predefined (XML-formatted) response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// A delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient ExpectXml(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, Func asyncAssertion) + { + return Expect(expectedRequestUri, expectedRequestMethod, responseStatusCode, responseBody, WellKnownMediaTypes.Xml, new XmlFormatter(), asyncAssertion); + } - /// - /// Create an that performs assertions on an incoming request message and returns a predefined response. - /// - /// - /// The type to be sent as a response body. - /// - /// - /// The expected URI for the incoming request message. - /// - /// - /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. - /// - /// - /// The HTTP status code for the outgoing response message. - /// - /// - /// The instance to be serialised into the outgoing response message. - /// - /// - /// The media type for the ougoing response message body. - /// - /// - /// The used to serialise the outgoing response message. - /// - /// - /// An asynchronous delegate that makes assertions about the incoming request message. - /// - /// - /// The configured . - /// - public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Func asyncAssertion) - { - if (expectedRequestUri == null) - throw new ArgumentNullException(nameof(expectedRequestUri)); + /// + /// Create an that performs assertions on an incoming request message and returns a predefined response. + /// + /// + /// The type to be sent as a response body. + /// + /// + /// The expected URI for the incoming request message. + /// + /// + /// The expected HTTP method (e.g. GET / POST / PUT) for the incoming request message. + /// + /// + /// The HTTP status code for the outgoing response message. + /// + /// + /// The instance to be serialised into the outgoing response message. + /// + /// + /// The media type for the ougoing response message body. + /// + /// + /// The used to serialise the outgoing response message. + /// + /// + /// An asynchronous delegate that makes assertions about the incoming request message. + /// + /// + /// The configured . + /// + public static HttpClient Expect(Uri expectedRequestUri, HttpMethod expectedRequestMethod, HttpStatusCode responseStatusCode, TResponseBody responseBody, string responseMediaType, IOutputFormatter responseFormatter, Func asyncAssertion) + { + if (expectedRequestUri == null) + throw new ArgumentNullException(nameof(expectedRequestUri)); - if (expectedRequestMethod == null) - throw new ArgumentNullException(nameof(expectedRequestMethod)); + if (expectedRequestMethod == null) + throw new ArgumentNullException(nameof(expectedRequestMethod)); - if (String.IsNullOrWhiteSpace(responseMediaType)) - throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); + if (String.IsNullOrWhiteSpace(responseMediaType)) + throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'responseMediaType'.", nameof(responseMediaType)); - if (responseFormatter == null) - throw new ArgumentNullException(nameof(responseFormatter)); + if (responseFormatter == null) + throw new ArgumentNullException(nameof(responseFormatter)); - return TestClients.AsyncRespondWith(async requestMessage => - { - Xunit.Assert.NotNull(requestMessage); - Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); - Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); + return TestClients.AsyncRespondWith(async requestMessage => + { + Xunit.Assert.NotNull(requestMessage); + Xunit.Assert.Equal(expectedRequestMethod, requestMessage.Method); + Xunit.Assert.Equal(expectedRequestUri, requestMessage.RequestUri); - Task assertionTask = asyncAssertion?.Invoke(requestMessage); - if (assertionTask != null) - await assertionTask; + Task assertionTask = asyncAssertion?.Invoke(requestMessage); + if (assertionTask != null) + await assertionTask; - return requestMessage.CreateResponse( - responseStatusCode, - responseBody, - responseMediaType, - new XmlFormatter() - ); - }); - } - } + return requestMessage.CreateResponse( + responseStatusCode, + responseBody, + responseMediaType, + new XmlFormatter() + ); + }); + } + } } diff --git a/test/HTTPlease.TestHarness/Program.cs b/test/HTTPlease.TestHarness/Program.cs index a7f4039..ad7ec2b 100644 --- a/test/HTTPlease.TestHarness/Program.cs +++ b/test/HTTPlease.TestHarness/Program.cs @@ -4,50 +4,50 @@ namespace HTTPlease.TestHarness { - /// - /// Quick-and-dirty test harness for use when Visual Studio refuses to debug unit tests. - /// - static class Program - { - static void Main() - { - Trace.Listeners.Add( - new TextWriterTraceListener(Console.Out) - ); - - try - { - string value1 = String.Empty; - string value2 = String.Empty; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("flag", () => value1) - .WithQueryParameter("flag", () => value2); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) - { - Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); - } - - value1 = null; - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) - { - Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); - } - - value2 = null; - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) - { - Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); - } - } - catch (Exception unexpectedError) - { - Console.WriteLine(unexpectedError); - } - } - } + /// + /// Quick-and-dirty test harness for use when Visual Studio refuses to debug unit tests. + /// + static class Program + { + static void Main() + { + Trace.Listeners.Add( + new TextWriterTraceListener(Console.Out) + ); + + try + { + string value1 = String.Empty; + string value2 = String.Empty; + + HttpRequest request = + HttpRequest.Factory.Create("http://localhost:1234/foo/bar") + .WithQueryParameter("flag", () => value1) + .WithQueryParameter("flag", () => value2); + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) + { + Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); + } + + value1 = null; + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) + { + Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); + } + + value2 = null; + + using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) + { + Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); + } + } + catch (Exception unexpectedError) + { + Console.WriteLine(unexpectedError); + } + } + } } From cc03ca54fb52d558e903ef58fe667669dabd3b41 Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Fri, 19 Apr 2019 12:19:20 +1000 Subject: [PATCH 3/4] Tidy up code. --- .../BuildMessage/UntypedRequest.cs | 10 ---------- test/HTTPlease.TestHarness/Program.cs | 3 +++ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs b/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs index 84b1f23..734ea74 100644 --- a/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs +++ b/test/HTTPlease.Core.Tests/BuildMessage/UntypedRequest.cs @@ -22,16 +22,6 @@ public class UntypedRequest /// static readonly HttpRequest RelativeRequest = HttpRequest.Create("foo/bar"); - public UntypedRequest(ITestOutputHelper testOutput) - { - if (testOutput == null) - throw new ArgumentNullException(nameof(testOutput)); - - TestOutput = testOutput; - } - - ITestOutputHelper TestOutput { get; } - /// /// An throws . /// diff --git a/test/HTTPlease.TestHarness/Program.cs b/test/HTTPlease.TestHarness/Program.cs index ad7ec2b..516b257 100644 --- a/test/HTTPlease.TestHarness/Program.cs +++ b/test/HTTPlease.TestHarness/Program.cs @@ -9,6 +9,9 @@ namespace HTTPlease.TestHarness /// static class Program { + /// + /// The main program entry-point. + /// static void Main() { Trace.Listeners.Add( From d7b87504a66ffe98118de1b4a174138a5ec38796 Mon Sep 17 00:00:00 2001 From: Adam Friedman Date: Sat, 20 Apr 2019 09:29:20 +1000 Subject: [PATCH 4/4] Template segments render themselves. --- .../Core/Templates/LiteralQuerySegment.cs | 38 ++++++++++----- .../Core/Templates/LiteralUriSegment.cs | 27 +++++++---- .../Templates/ParameterizedQuerySegment.cs | 33 +++++++++---- .../Core/Templates/ParameterizedUriSegment.cs | 28 +++++++++-- .../Core/Templates/QuerySegment.cs | 15 +++++- .../Core/Templates/RootUriSegment.cs | 21 +++++---- .../Core/Templates/TemplateSegment.cs | 21 +++++---- .../Core/Templates/UriSegment.cs | 17 ++++++- src/HTTPlease.Core/UriTemplate.cs | 47 ++++++++----------- test/HTTPlease.Core.Tests/UriTemplateTests.cs | 6 +-- test/HTTPlease.TestHarness/Program.cs | 44 +++++++---------- 11 files changed, 186 insertions(+), 111 deletions(-) diff --git a/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs b/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs index 9505152..26214e4 100644 --- a/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs +++ b/src/HTTPlease.Core/Core/Templates/LiteralQuerySegment.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace HTTPlease.Core.Templates { @@ -11,7 +12,7 @@ sealed class LiteralQuerySegment /// /// The value for the query parameter that the segment represents. /// - readonly string _queryParameterValue; + readonly string _value; /// /// Create a new literal query segment. @@ -28,7 +29,7 @@ public LiteralQuerySegment(string queryParameterName, string queryParameterValue if (String.IsNullOrWhiteSpace(queryParameterValue)) throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'value'.", nameof(queryParameterValue)); - _queryParameterValue = queryParameterValue; + _value = queryParameterValue; } /// @@ -38,25 +39,38 @@ public string QueryParameterValue { get { - return _queryParameterValue; + return _value; } } /// - /// Get the value of the segment (if any). + /// Render the template segment as text. /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment has no value. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) + /// The to which the rendered text will be appended. + /// The template evaluation context. + /// true, if the segment produced any output; otherwise, false. + public override bool Render(StringBuilder output, ITemplateEvaluationContext evaluationContext) { + if (output == null) + throw new ArgumentNullException(nameof(output)); + if (evaluationContext == null) throw new ArgumentNullException(nameof(evaluationContext)); + + if (_value == null) + return false; + + output.Append(Name); + + if (_value != String.Empty) + { + output.Append('='); + output.Append( + Escape(_value) + ); + } - return _queryParameterValue; + return true; } } } diff --git a/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs b/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs index ed9216c..22a2569 100644 --- a/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/LiteralUriSegment.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace HTTPlease.Core.Templates { @@ -43,20 +44,28 @@ public string Value } /// - /// Get the value of the segment (if any). + /// Render the template segment as text. /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment is missing. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) + /// The to which the rendered text will be appended. + /// The template evaluation context. + /// true, if the segment produced any output; otherwise, false. + public override bool Render(StringBuilder output, ITemplateEvaluationContext evaluationContext) { + if (output == null) + throw new ArgumentNullException(nameof(output)); + if (evaluationContext == null) throw new ArgumentNullException(nameof(evaluationContext)); + + if (_value == null) + return false; + + output.Append(_value); + + if (IsDirectory) + output.Append('/'); - return _value; + return true; } } } diff --git a/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs b/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs index 96e96e5..935a339 100644 --- a/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs +++ b/src/HTTPlease.Core/Core/Templates/ParameterizedQuerySegment.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace HTTPlease.Core.Templates { @@ -74,20 +75,34 @@ public bool IsOptional public override bool IsParameterized => true; /// - /// Get the value of the segment (if any). + /// Render the template segment as text. /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment has no value. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) + /// The to which the rendered text will be appended. + /// The template evaluation context. + /// true, if the segment produced any output; otherwise, false. + public override bool Render(StringBuilder output, ITemplateEvaluationContext evaluationContext) { + if (output == null) + throw new ArgumentNullException(nameof(output)); + if (evaluationContext == null) throw new ArgumentNullException(nameof(evaluationContext)); + + string parameterValue = evaluationContext[_templateParameterName, _isOptional]; + if (parameterValue == null) + return false; + + output.Append(Name); + + if (parameterValue != String.Empty) + { + output.Append('='); + output.Append( + Escape(parameterValue) + ); + } - return evaluationContext[_templateParameterName, _isOptional]; + return true; } } } diff --git a/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs b/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs index 7b19ef7..609127f 100644 --- a/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/ParameterizedUriSegment.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace HTTPlease.Core.Templates { @@ -76,20 +77,37 @@ public bool IsOptional public override bool IsParameterized => true; /// - /// Get the value of the segment (if any). + /// Render the template segment as text. /// + /// + /// The to which the rendered text will be appended. + /// /// - /// The current template evaluation context. + /// The template evaluation context. /// /// - /// The segment value, or null if the segment is missing. + /// true, if the segment produced any output; otherwise, false. /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) + public override bool Render(StringBuilder output, ITemplateEvaluationContext evaluationContext) { + if (output == null) + throw new ArgumentNullException(nameof(output)); + if (evaluationContext == null) throw new ArgumentNullException(nameof(evaluationContext)); + + string parameterValue = evaluationContext[_templateParameterName, _isOptional]; + if (parameterValue == null) + return false; + + output.Append( + Escape(parameterValue) + ); + + if (IsDirectory) + output.Append('/'); - return evaluationContext[_templateParameterName, _isOptional]; + return true; } } } diff --git a/src/HTTPlease.Core/Core/Templates/QuerySegment.cs b/src/HTTPlease.Core/Core/Templates/QuerySegment.cs index 6b202ec..91dab1f 100644 --- a/src/HTTPlease.Core/Core/Templates/QuerySegment.cs +++ b/src/HTTPlease.Core/Core/Templates/QuerySegment.cs @@ -30,12 +30,25 @@ protected QuerySegment(string queryParameterName) /// /// The name of the query parameter that the segment represents. /// - public string QueryParameterName + public string Name { get { return _queryParameterName; } } + + /// + /// Escape the specified text according to the template segment type's escaping rules. + /// + /// The text to escape. + /// The escaped text. + protected override string Escape(string text) + { + if (text == null) + return text; + + return Uri.EscapeDataString(text); + } } } diff --git a/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs b/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs index c57ed3b..20c3d05 100644 --- a/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/RootUriSegment.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace HTTPlease.Core.Templates { @@ -22,20 +23,22 @@ sealed class RootUriSegment } /// - /// Get the value of the segment (if any). + /// Render the template segment as text. /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment is missing. - /// - public override string GetValue(ITemplateEvaluationContext evaluationContext) + /// The to which the rendered text will be appended. + /// The template evaluation context. + /// true, if the segment produced any output; otherwise, false. + public override bool Render(StringBuilder stringBuilder, ITemplateEvaluationContext evaluationContext) { + if (stringBuilder == null) + throw new ArgumentNullException(nameof(stringBuilder)); + if (evaluationContext == null) throw new ArgumentNullException(nameof(evaluationContext)); + + stringBuilder.Append('/'); - return String.Empty; + return false; } } } diff --git a/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs b/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs index d0a336a..ebe126f 100644 --- a/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/TemplateSegment.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.RegularExpressions; namespace HTTPlease.Core.Templates @@ -31,15 +32,19 @@ protected TemplateSegment() public virtual bool IsParameterized => true; /// - /// Get the value of the segment (if any). + /// Render the template segment as text. /// - /// - /// The current template evaluation context. - /// - /// - /// The segment value, or null if the segment has no value. - /// - public abstract string GetValue(ITemplateEvaluationContext evaluationContext); + /// The to which the rendered text will be appended. + /// The template evaluation context. + /// true, if the segment produced any output; otherwise, false. + public abstract bool Render(StringBuilder output, ITemplateEvaluationContext evaluationContext); + + /// + /// Escape the specified text according to the template segment type's escaping rules. + /// + /// The text to escape. + /// The escaped text. + protected virtual string Escape(string text) => text; /// /// Parse the specified URI into template segments. diff --git a/src/HTTPlease.Core/Core/Templates/UriSegment.cs b/src/HTTPlease.Core/Core/Templates/UriSegment.cs index 507ab8d..cf39443 100644 --- a/src/HTTPlease.Core/Core/Templates/UriSegment.cs +++ b/src/HTTPlease.Core/Core/Templates/UriSegment.cs @@ -1,4 +1,6 @@ -namespace HTTPlease.Core.Templates +using System; + +namespace HTTPlease.Core.Templates { /// /// The base class for URI template segments that represent segments of the URI. @@ -32,5 +34,18 @@ public bool IsDirectory return _isDirectory; } } + + /// + /// Escape the specified text according to the template segment type's escaping rules. + /// + /// The text to escape. + /// The escaped text. + protected override string Escape(string text) + { + if (text == null) + return text; + + return Uri.EscapeUriString(text); + } } } diff --git a/src/HTTPlease.Core/UriTemplate.cs b/src/HTTPlease.Core/UriTemplate.cs index a3611dd..9ada222 100644 --- a/src/HTTPlease.Core/UriTemplate.cs +++ b/src/HTTPlease.Core/UriTemplate.cs @@ -94,43 +94,36 @@ public Uri Populate(Uri baseUri, IDictionary templateParameters) if (_uriSegments.Count > 0) { foreach (UriSegment uriSegment in _uriSegments) - { - string segmentValue = uriSegment.GetValue(evaluationContext); - if (segmentValue == null) - continue; - - uriBuilder.Append( - Uri.EscapeUriString(segmentValue) - ); - if (uriSegment.IsDirectory) - uriBuilder.Append('/'); - } + uriSegment.Render(uriBuilder, evaluationContext); } else uriBuilder.Append('/'); - bool isFirstParameterWithValue = true; - foreach (QuerySegment segment in _querySegments) + if (_querySegments.Count > 0) { - string queryParameterValue = segment.GetValue(evaluationContext); - if (queryParameterValue == null) - continue; + int queryStartIndex = uriBuilder.Length; - // Different prefix for first parameter that has a value. - if (isFirstParameterWithValue) + bool renderedAny = false; + foreach (QuerySegment querySegment in _querySegments) { - uriBuilder.Append('?'); + int parameterStartIndex = uriBuilder.Length; + + bool rendered = querySegment.Render(uriBuilder, evaluationContext); + if (rendered && renderedAny) + { + // Insert the leading '&' for this query parameter. + uriBuilder.Insert(parameterStartIndex, '&'); + } + else + renderedAny |= rendered; - isFirstParameterWithValue = false; } - else - uriBuilder.Append('&'); - uriBuilder.AppendFormat( - "{0}={1}", - Uri.EscapeDataString(segment.QueryParameterName), - Uri.EscapeDataString(queryParameterValue) - ); + if (renderedAny) + { + // Insert the leading '?'. + uriBuilder.Insert(queryStartIndex, '?'); + } } return new Uri(uriBuilder.ToString(), UriKind.RelativeOrAbsolute); diff --git a/test/HTTPlease.Core.Tests/UriTemplateTests.cs b/test/HTTPlease.Core.Tests/UriTemplateTests.cs index a755870..8fe5e81 100644 --- a/test/HTTPlease.Core.Tests/UriTemplateTests.cs +++ b/test/HTTPlease.Core.Tests/UriTemplateTests.cs @@ -91,17 +91,17 @@ public void Can_Parse_TemplateSegments_From_Uri_WithQuery() Assert.Equal("properties", propertiesSegment.Value); ParameterizedQuerySegment propertyIdsSegment = Assert.IsAssignableFrom(segments[6]); - Assert.Equal("propertyIds", propertyIdsSegment.QueryParameterName); + Assert.Equal("propertyIds", propertyIdsSegment.Name); Assert.Equal("propertyGroupIds", propertyIdsSegment.TemplateParameterName); Assert.False(propertyIdsSegment.IsOptional); ParameterizedQuerySegment diddlySegment = Assert.IsAssignableFrom(segments[7]); - Assert.Equal("diddly", diddlySegment.QueryParameterName); + Assert.Equal("diddly", diddlySegment.Name); Assert.Equal("dee", diddlySegment.TemplateParameterName); Assert.True(diddlySegment.IsOptional); LiteralQuerySegment fooSegment = Assert.IsAssignableFrom(segments[8]); - Assert.Equal("foo", fooSegment.QueryParameterName); + Assert.Equal("foo", fooSegment.Name); Assert.Equal("bar", fooSegment.QueryParameterValue); } diff --git a/test/HTTPlease.TestHarness/Program.cs b/test/HTTPlease.TestHarness/Program.cs index 516b257..a4932ca 100644 --- a/test/HTTPlease.TestHarness/Program.cs +++ b/test/HTTPlease.TestHarness/Program.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Net.Http; namespace HTTPlease.TestHarness { @@ -20,32 +20,22 @@ static void Main() try { - string value1 = String.Empty; - string value2 = String.Empty; - - HttpRequest request = - HttpRequest.Factory.Create("http://localhost:1234/foo/bar") - .WithQueryParameter("flag", () => value1) - .WithQueryParameter("flag", () => value2); - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) - { - Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); - } - - value1 = null; - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) - { - Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); - } - - value2 = null; - - using (HttpRequestMessage requestMessage = request.BuildRequestMessage(HttpMethod.Get)) - { - Debug.WriteLine("URI = '{0}'", requestMessage.RequestUri); - } + UriTemplate template = new UriTemplate( + "api/{controller}/{action}/{id?}/properties?propertyIds={propertyGroupIds}&diddly={dee?}&foo=bar" + ); + + Uri generatedUri = template.Populate( + baseUri: new Uri("http://test-host/"), + templateParameters: new Dictionary + { + ["controller"] = "organizations", + ["action"] = "distinct", + ["dee"] = "hello", + ["propertyGroupIds"] = "System.OrganizationCommercial;EnterpriseMobility.OrganizationAirwatch" + } + ); + + Debug.WriteLine(generatedUri.AbsoluteUri); } catch (Exception unexpectedError) {