Skip to content

Commit 765b8c2

Browse files
Merge pull request #475 from martincostello/more-callback-overloads
Support dynamic HTTP headers
2 parents fca25e8 + 9c44ab0 commit 765b8c2

File tree

6 files changed

+312
-35
lines changed

6 files changed

+312
-35
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
3939
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
4040
<AssemblyVersion>3.0.0.0</AssemblyVersion>
41-
<VersionPrefix>3.1.3</VersionPrefix>
42-
<VersionSuffix Condition=" '$(VersionSuffix)' == '' AND '$(GITHUB_ACTIONS)' != '' ">beta$([System.Convert]::ToInt32(`$(GITHUB_RUN_NUMBER)`).ToString(`0000`))</VersionSuffix>
41+
<VersionPrefix>3.2.0</VersionPrefix>
42+
<VersionSuffix Condition=" '$(VersionSuffix)' == '' AND '$(GITHUB_ACTIONS)' != '' AND '$(GITHUB_HEAD_REF)' != '' ">beta.$(GITHUB_RUN_NUMBER)</VersionSuffix>
4343
<VersionPrefix Condition=" $(GITHUB_REF.StartsWith(`refs/tags/v`)) ">$(GITHUB_REF.Replace('refs/tags/v', ''))</VersionPrefix>
4444
<VersionSuffix Condition=" $(GITHUB_REF.StartsWith(`refs/tags/v`)) "></VersionSuffix>
4545
<FileVersion Condition=" '$(GITHUB_RUN_NUMBER)' != '' ">$(VersionPrefix).$(GITHUB_RUN_NUMBER)</FileVersion>

Directory.Build.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<CommitBranch Condition=" '$(CommitBranch)' == '' ">$(BUILD_SOURCEBRANCHNAME)</CommitBranch>
4-
<CommitBranch Condition=" '$(CommitBranch)' == '' and '$(GITHUB_REF)' != '' ">$(GITHUB_REF.Substring(11))</CommitBranch>
4+
<CommitBranch Condition=" '$(CommitBranch)' == '' and '$(GITHUB_REF_NAME)' != '' ">$(GITHUB_REF_NAME)</CommitBranch>
55
<CommitHash Condition=" '$(CommitHash)' == '' ">$(GITHUB_SHA)</CommitHash>
66
</PropertyGroup>
77
<Target Name="AddGitMetadaAssemblyAttributes"

src/HttpClientInterception/HttpRequestInterceptionBuilder.cs

Lines changed: 225 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Just Eat, 2017. All rights reserved.
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

4+
using System.Collections;
5+
using System.Diagnostics.CodeAnalysis;
46
using System.Net;
57

68
namespace JustEat.HttpClientInterception;
@@ -426,10 +428,7 @@ public HttpRequestInterceptionBuilder WithContentHeader(string name, IEnumerable
426428
throw new ArgumentNullException(nameof(values));
427429
}
428430

429-
if (_contentHeaders is null)
430-
{
431-
_contentHeaders = new Dictionary<string, ICollection<string>>(StringComparer.OrdinalIgnoreCase);
432-
}
431+
_contentHeaders ??= new Dictionary<string, ICollection<string>>(StringComparer.OrdinalIgnoreCase);
433432

434433
if (!_contentHeaders.TryGetValue(name, out var current))
435434
{
@@ -500,6 +499,29 @@ public HttpRequestInterceptionBuilder WithContentHeaders(IDictionary<string, str
500499
return this;
501500
}
502501

502+
/// <summary>
503+
/// Sets a delegate to a method that generates any custom HTTP content headers to use.
504+
/// </summary>
505+
/// <param name="headerFactory">Any delegate that creates any custom HTTP content headers to use.</param>
506+
/// <returns>
507+
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
508+
/// </returns>
509+
/// <exception cref="ArgumentNullException">
510+
/// <paramref name="headerFactory"/> is <see langword="null"/>.
511+
/// </exception>
512+
public HttpRequestInterceptionBuilder WithContentHeaders(
513+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory)
514+
{
515+
if (headerFactory is null)
516+
{
517+
throw new ArgumentNullException(nameof(headerFactory));
518+
}
519+
520+
_contentHeaders = new DynamicDictionary(headerFactory);
521+
IncrementRevision();
522+
return this;
523+
}
524+
503525
/// <summary>
504526
/// Sets a custom HTTP response header to use with a single value.
505527
/// </summary>
@@ -551,10 +573,7 @@ public HttpRequestInterceptionBuilder WithResponseHeader(string name, IEnumerabl
551573
throw new ArgumentNullException(nameof(values));
552574
}
553575

554-
if (_responseHeaders is null)
555-
{
556-
_responseHeaders = new Dictionary<string, ICollection<string>>(StringComparer.OrdinalIgnoreCase);
557-
}
576+
_responseHeaders ??= new Dictionary<string, ICollection<string>>(StringComparer.OrdinalIgnoreCase);
558577

559578
if (!_responseHeaders.TryGetValue(name, out ICollection<string>? current))
560579
{
@@ -617,6 +636,29 @@ public HttpRequestInterceptionBuilder WithResponseHeaders(IDictionary<string, IC
617636
return this;
618637
}
619638

639+
/// <summary>
640+
/// Sets a delegate to a method that generates any custom HTTP response headers to use.
641+
/// </summary>
642+
/// <param name="headerFactory">Any delegate that creates any custom HTTP response headers to use.</param>
643+
/// <returns>
644+
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
645+
/// </returns>
646+
/// <exception cref="ArgumentNullException">
647+
/// <paramref name="headerFactory"/> is <see langword="null"/>.
648+
/// </exception>
649+
public HttpRequestInterceptionBuilder WithResponseHeaders(
650+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory)
651+
{
652+
if (headerFactory is null)
653+
{
654+
throw new ArgumentNullException(nameof(headerFactory));
655+
}
656+
657+
_responseHeaders = new DynamicDictionary(headerFactory);
658+
IncrementRevision();
659+
return this;
660+
}
661+
620662
/// <summary>
621663
/// Sets media type for the response body content.
622664
/// </summary>
@@ -869,10 +911,7 @@ public HttpRequestInterceptionBuilder ForRequestHeader(string name, IEnumerable<
869911
throw new ArgumentNullException(nameof(values));
870912
}
871913

872-
if (_requestHeaders is null)
873-
{
874-
_requestHeaders = new Dictionary<string, ICollection<string>>(StringComparer.OrdinalIgnoreCase);
875-
}
914+
_requestHeaders ??= new Dictionary<string, ICollection<string>>(StringComparer.OrdinalIgnoreCase);
876915

877916
if (!_requestHeaders.TryGetValue(name, out ICollection<string>? current))
878917
{
@@ -931,16 +970,50 @@ public HttpRequestInterceptionBuilder ForRequestHeaders(IDictionary<string, stri
931970
/// <returns>
932971
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
933972
/// </returns>
973+
/// <exception cref="ArgumentNullException">
974+
/// <paramref name="headers"/> is <see langword="null"/>.
975+
/// </exception>
934976
/// <remarks>
935977
/// HTTP request headers are only tested for interception if the URI requested was registered for interception.
936978
/// </remarks>
937979
public HttpRequestInterceptionBuilder ForRequestHeaders(IDictionary<string, ICollection<string>> headers)
938980
{
981+
if (headers is null)
982+
{
983+
throw new ArgumentNullException(nameof(headers));
984+
}
985+
939986
_requestHeaders = new Dictionary<string, ICollection<string>>(headers, StringComparer.OrdinalIgnoreCase);
940987
IncrementRevision();
941988
return this;
942989
}
943990

991+
/// <summary>
992+
/// Sets a delegate to a method that generates the HTTP request headers to intercept.
993+
/// </summary>
994+
/// <param name="headerFactory">Any delegate that returns the HTTP request headers to intercept..</param>
995+
/// <returns>
996+
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
997+
/// </returns>
998+
/// <exception cref="ArgumentNullException">
999+
/// <paramref name="headerFactory"/> is <see langword="null"/>.
1000+
/// </exception>
1001+
/// <remarks>
1002+
/// HTTP request headers are only tested for interception if the URI requested was registered for interception.
1003+
/// </remarks>
1004+
public HttpRequestInterceptionBuilder ForRequestHeaders(
1005+
Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> headerFactory)
1006+
{
1007+
if (headerFactory is null)
1008+
{
1009+
throw new ArgumentNullException(nameof(headerFactory));
1010+
}
1011+
1012+
_requestHeaders = new DynamicDictionary(headerFactory);
1013+
IncrementRevision();
1014+
return this;
1015+
}
1016+
9441017
/// <summary>
9451018
/// Configures the builder to match any request whose HTTP content meets the criteria defined by the specified predicate.
9461019
/// </summary>
@@ -983,40 +1056,61 @@ internal HttpInterceptionResponse Build()
9831056
Version = _version,
9841057
};
9851058

986-
if (_requestHeaders?.Count > 0)
1059+
if (_requestHeaders is not null)
9871060
{
988-
var headers = new Dictionary<string, IEnumerable<string>>(_requestHeaders.Count);
989-
990-
foreach (var pair in _requestHeaders)
1061+
if (_requestHeaders is DynamicDictionary factory)
9911062
{
992-
headers[pair.Key] = pair.Value;
1063+
response.RequestHeaders = factory;
9931064
}
1065+
else if (_requestHeaders.Count > 0)
1066+
{
1067+
var headers = new Dictionary<string, IEnumerable<string>>(_requestHeaders.Count);
9941068

995-
response.RequestHeaders = headers;
1069+
foreach (var pair in _requestHeaders)
1070+
{
1071+
headers[pair.Key] = pair.Value;
1072+
}
1073+
1074+
response.RequestHeaders = headers;
1075+
}
9961076
}
9971077

998-
if (_responseHeaders?.Count > 0)
1078+
if (_responseHeaders is not null)
9991079
{
1000-
var headers = new Dictionary<string, IEnumerable<string>>(_responseHeaders.Count);
1001-
1002-
foreach (var pair in _responseHeaders)
1080+
if (_responseHeaders is DynamicDictionary factory)
10031081
{
1004-
headers[pair.Key] = pair.Value;
1082+
response.ResponseHeaders = factory;
10051083
}
1084+
else if (_responseHeaders.Count > 0)
1085+
{
1086+
var headers = new Dictionary<string, IEnumerable<string>>(_responseHeaders.Count);
1087+
1088+
foreach (var pair in _responseHeaders)
1089+
{
1090+
headers[pair.Key] = pair.Value;
1091+
}
10061092

1007-
response.ResponseHeaders = headers;
1093+
response.ResponseHeaders = headers;
1094+
}
10081095
}
10091096

1010-
if (_contentHeaders?.Count > 0)
1097+
if (_contentHeaders is not null)
10111098
{
1012-
var headers = new Dictionary<string, IEnumerable<string>>(_contentHeaders.Count);
1013-
1014-
foreach (var pair in _contentHeaders)
1099+
if (_contentHeaders is DynamicDictionary factory)
10151100
{
1016-
headers[pair.Key] = pair.Value;
1101+
response.ContentHeaders = factory;
10171102
}
1103+
else if (_contentHeaders.Count > 0)
1104+
{
1105+
var headers = new Dictionary<string, IEnumerable<string>>(_contentHeaders.Count);
1106+
1107+
foreach (var pair in _contentHeaders)
1108+
{
1109+
headers[pair.Key] = pair.Value;
1110+
}
10181111

1019-
response.ContentHeaders = headers;
1112+
response.ContentHeaders = headers;
1113+
}
10201114
}
10211115

10221116
return response;
@@ -1036,4 +1130,105 @@ private void IncrementRevision()
10361130
_revision++;
10371131
}
10381132
}
1133+
1134+
private sealed class DynamicDictionary :
1135+
IDictionary<string, ICollection<string>>,
1136+
IEnumerable<KeyValuePair<string, IEnumerable<string>>>
1137+
{
1138+
private readonly Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> _generator;
1139+
1140+
internal DynamicDictionary(Func<IEnumerable<KeyValuePair<string, ICollection<string>>>> generator)
1141+
{
1142+
_generator = generator;
1143+
}
1144+
1145+
[ExcludeFromCodeCoverage]
1146+
public ICollection<string> Keys => throw new NotSupportedException();
1147+
1148+
[ExcludeFromCodeCoverage]
1149+
public ICollection<ICollection<string>> Values => throw new NotSupportedException();
1150+
1151+
[ExcludeFromCodeCoverage]
1152+
public int Count => throw new NotSupportedException();
1153+
1154+
[ExcludeFromCodeCoverage]
1155+
public bool IsReadOnly => true;
1156+
1157+
[ExcludeFromCodeCoverage]
1158+
public ICollection<string> this[string key]
1159+
{
1160+
get => throw new NotSupportedException();
1161+
set => throw new NotSupportedException();
1162+
}
1163+
1164+
public IEnumerator<KeyValuePair<string, ICollection<string>>> GetEnumerator()
1165+
=> _generator().GetEnumerator();
1166+
1167+
IEnumerator IEnumerable.GetEnumerator()
1168+
=> GetEnumerator();
1169+
1170+
IEnumerator<KeyValuePair<string, IEnumerable<string>>> IEnumerable<KeyValuePair<string, IEnumerable<string>>>.GetEnumerator()
1171+
=> new EnumeratorAdapter(GetEnumerator());
1172+
1173+
[ExcludeFromCodeCoverage]
1174+
public void Add(string key, ICollection<string> value)
1175+
=> throw new NotSupportedException();
1176+
1177+
[ExcludeFromCodeCoverage]
1178+
public void Add(KeyValuePair<string, ICollection<string>> item)
1179+
=> throw new NotSupportedException();
1180+
1181+
[ExcludeFromCodeCoverage]
1182+
public void Clear()
1183+
=> throw new NotSupportedException();
1184+
1185+
[ExcludeFromCodeCoverage]
1186+
public bool Contains(KeyValuePair<string, ICollection<string>> item)
1187+
=> throw new NotSupportedException();
1188+
1189+
[ExcludeFromCodeCoverage]
1190+
public bool ContainsKey(string key)
1191+
=> throw new NotSupportedException();
1192+
1193+
[ExcludeFromCodeCoverage]
1194+
public void CopyTo(KeyValuePair<string, ICollection<string>>[] array, int arrayIndex)
1195+
=> throw new NotSupportedException();
1196+
1197+
[ExcludeFromCodeCoverage]
1198+
public bool Remove(string key)
1199+
=> throw new NotSupportedException();
1200+
1201+
[ExcludeFromCodeCoverage]
1202+
public bool Remove(KeyValuePair<string, ICollection<string>> item)
1203+
=> throw new NotSupportedException();
1204+
1205+
[ExcludeFromCodeCoverage]
1206+
public bool TryGetValue(string key, out ICollection<string> value)
1207+
=> throw new NotSupportedException();
1208+
1209+
private sealed class EnumeratorAdapter : IEnumerator<KeyValuePair<string, IEnumerable<string>>>
1210+
{
1211+
private readonly IEnumerator<KeyValuePair<string, ICollection<string>>> _enumerator;
1212+
1213+
internal EnumeratorAdapter(IEnumerator<KeyValuePair<string, ICollection<string>>> enumerator)
1214+
{
1215+
_enumerator = enumerator;
1216+
}
1217+
1218+
public KeyValuePair<string, IEnumerable<string>> Current
1219+
=> new(_enumerator.Current.Key, _enumerator.Current.Value);
1220+
1221+
object IEnumerator.Current
1222+
=> _enumerator.Current;
1223+
1224+
public void Dispose()
1225+
=> _enumerator.Dispose();
1226+
1227+
public bool MoveNext()
1228+
=> _enumerator.MoveNext();
1229+
1230+
public void Reset()
1231+
=> _enumerator.Reset();
1232+
}
1233+
}
10391234
}

src/HttpClientInterception/PublicAPI.Shipped.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForRequestHeader(s
3333
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForRequestHeader(string! name, string! value) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
3434
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForRequestHeaders(System.Collections.Generic.IDictionary<string!, System.Collections.Generic.ICollection<string!>!>! headers) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
3535
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForRequestHeaders(System.Collections.Generic.IDictionary<string!, string!>! headers) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
36+
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForRequestHeaders(System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, System.Collections.Generic.ICollection<string!>!>>!>! headerFactory) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
3637
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForScheme(string! scheme) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
3738
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForUri(System.Uri! uri) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
3839
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.ForUri(System.UriBuilder! uriBuilder) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
@@ -47,6 +48,7 @@ JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentHeader(
4748
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentHeader(string! name, string! value) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
4849
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentHeaders(System.Collections.Generic.IDictionary<string!, System.Collections.Generic.ICollection<string!>!>! headers) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
4950
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentHeaders(System.Collections.Generic.IDictionary<string!, string!>! headers) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
51+
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentHeaders(System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, System.Collections.Generic.ICollection<string!>!>>!>! headerFactory) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
5052
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentStream(System.Func<System.IO.Stream!>? contentStream) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
5153
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithContentStream(System.Func<System.Threading.Tasks.Task<System.IO.Stream!>!>! contentStream) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
5254
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithInterceptionCallback(System.Action<System.Net.Http.HttpRequestMessage!>! onIntercepted) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
@@ -62,6 +64,7 @@ JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithResponseHeader
6264
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithResponseHeader(string! name, string! value) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
6365
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithResponseHeaders(System.Collections.Generic.IDictionary<string!, System.Collections.Generic.ICollection<string!>!>! headers) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
6466
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithResponseHeaders(System.Collections.Generic.IDictionary<string!, string!>! headers) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
67+
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithResponseHeaders(System.Func<System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, System.Collections.Generic.ICollection<string!>!>>!>! headerFactory) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
6568
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithStatus(System.Net.HttpStatusCode statusCode) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
6669
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithStatus(int statusCode) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
6770
JustEat.HttpClientInterception.HttpRequestInterceptionBuilder.WithVersion(System.Version! version) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!

0 commit comments

Comments
 (0)