Skip to content

Commit 359668b

Browse files
committed
Adds Utils.AppendSegmentToUrl for IRepositoryClient.GetArchive overload
1 parent faaebbf commit 359668b

File tree

5 files changed

+177
-48
lines changed

5 files changed

+177
-48
lines changed

NGitLab.Tests/Impl/UtilsTests.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,118 @@ public void AddParameter_ConsidersEnumMemberAttribute(EventAction value, string
1515

1616
Assert.That(url, Is.EqualTo($"{basePath}?event_action={expectedQueryParamValue}"));
1717
}
18+
19+
[TestCase]
20+
public void AppendSegmentToUrl_ValueIsNullIncludeSegmentSeparatorFalse_ReturnsUrlWithoutAnyChange()
21+
{
22+
// Arrange
23+
const string basePath = "https://gitlab.org/api/v4/stuff";
24+
var url = basePath;
25+
var expected = basePath;
26+
27+
// Act
28+
var actual = NGitLab.Impl.Utils.AppendSegmentToUrl<string>(url, value: null, includeSegmentSeparator: false);
29+
30+
// Assert
31+
Assert.That(expected, Is.EqualTo(actual));
32+
}
33+
34+
[TestCase]
35+
public void AppendSegmentToUrl_ValueIsNullIncludeSegmentSeparatorTrue_ReturnsUrlWithoutAnyChange()
36+
{
37+
// Arrange
38+
const string basePath = "https://gitlab.org/api/v4/stuff";
39+
var url = basePath;
40+
var expected = basePath;
41+
42+
// Act
43+
var actual = NGitLab.Impl.Utils.AppendSegmentToUrl<string>(url, value: null, includeSegmentSeparator: true);
44+
45+
// Assert
46+
Assert.That(expected, Is.EqualTo(actual));
47+
}
48+
49+
[TestCase]
50+
public void AppendSegmentToUrl_UrlAlreadyContainsQueryString_ThrowsInvalidOperationException()
51+
{
52+
// Arrange
53+
const string basePath = "https://gitlab.org/api/v4/stuff";
54+
var url = basePath;
55+
56+
url = NGitLab.Impl.Utils.AddParameter(url, "param1", "one");
57+
58+
// Act and Assert
59+
Assert.That(() => NGitLab.Impl.Utils.AppendSegmentToUrl(url, "segment"), Throws.InvalidOperationException);
60+
}
61+
62+
[TestCase("https://gitlab.org/api/v4/stuff/", "/segment")]
63+
[TestCase("https://gitlab.org/api/v4/stuff/", "segment")]
64+
[TestCase("https://gitlab.org/api/v4/stuff", "/segment")]
65+
[TestCase("https://gitlab.org/api/v4/stuff", "segment")]
66+
public void AppendSegmentToUrl_IncludeSegmentSeparatorIsTrue_SegmentAppendedWithSeparator(string basePath, string segment)
67+
{
68+
// Arrange
69+
var url = basePath;
70+
var expected = "https://gitlab.org/api/v4/stuff/segment";
71+
72+
// Act
73+
var actual = NGitLab.Impl.Utils.AppendSegmentToUrl(url, value: segment, includeSegmentSeparator: true);
74+
75+
// Assert
76+
Assert.That(expected, Is.EqualTo(actual));
77+
}
78+
79+
[TestCase("https://gitlab.org/api/v4/stuff/", "/segment")]
80+
[TestCase("https://gitlab.org/api/v4/stuff/", "segment")]
81+
[TestCase("https://gitlab.org/api/v4/stuff", "/segment")]
82+
[TestCase("https://gitlab.org/api/v4/stuff", "segment")]
83+
public void AppendSegmentToUrl_IncludeSegmentSeparatorIsFalse_SegmentAppendedWithoutSeparator(string basePath, string segment)
84+
{
85+
// Arrange
86+
var url = basePath;
87+
var expected = "https://gitlab.org/api/v4/stuffsegment";
88+
89+
// Act
90+
var actual = NGitLab.Impl.Utils.AppendSegmentToUrl(url, value: segment, includeSegmentSeparator: false);
91+
92+
// Assert
93+
Assert.That(expected, Is.EqualTo(actual));
94+
}
95+
96+
[TestCase("https://gitlab.org/api/v4/stuff.bz2", FileArchiveFormat.Bz2)]
97+
[TestCase("https://gitlab.org/api/v4/stuff.gz", FileArchiveFormat.Gz)]
98+
[TestCase("https://gitlab.org/api/v4/stuff.tar", FileArchiveFormat.Tar)]
99+
[TestCase("https://gitlab.org/api/v4/stuff.tar.bz2", FileArchiveFormat.TarBz2)]
100+
[TestCase("https://gitlab.org/api/v4/stuff.tar.gz", FileArchiveFormat.TarGz)]
101+
[TestCase("https://gitlab.org/api/v4/stuff.tb2", FileArchiveFormat.Tb2)]
102+
[TestCase("https://gitlab.org/api/v4/stuff.tbz", FileArchiveFormat.Tbz)]
103+
[TestCase("https://gitlab.org/api/v4/stuff.tbz2", FileArchiveFormat.Tbz2)]
104+
[TestCase("https://gitlab.org/api/v4/stuff.zip", FileArchiveFormat.Zip)]
105+
public void AppendSegmentToUrl_ValueIsEnumWithEnumMemberAttribute_EnumMemberValueAppended(string expected, FileArchiveFormat fileArchiveFormat)
106+
{
107+
// Arrange
108+
const string basePath = "https://gitlab.org/api/v4/stuff";
109+
var url = basePath;
110+
111+
// Act
112+
var actual = NGitLab.Impl.Utils.AppendSegmentToUrl(url, value: fileArchiveFormat, includeSegmentSeparator: false);
113+
114+
// Assert
115+
Assert.That(expected, Is.EqualTo(actual));
116+
}
117+
118+
[TestCase("https://gitlab.org/api/v4/stuff/Group", BadgeKind.Group)]
119+
[TestCase("https://gitlab.org/api/v4/stuff/Project", BadgeKind.Project)]
120+
public void AppendSegmentToUrl_ValueIsEnumWithoutEnumMemberAttribute_EnumToStringValueAppended(string expected, BadgeKind badgeKind)
121+
{
122+
// Arrange
123+
const string basePath = "https://gitlab.org/api/v4/stuff";
124+
var url = basePath;
125+
126+
// Act
127+
var actual = NGitLab.Impl.Utils.AppendSegmentToUrl(url, value: badgeKind, includeSegmentSeparator: true);
128+
129+
// Assert
130+
Assert.That(expected, Is.EqualTo(actual));
131+
}
18132
}

NGitLab.Tests/RepositoryClient/RepositoryClientTests.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ public async Task GetCommitRefs(CommitRefType type)
356356

357357
[Test]
358358
[NGitLabRetry]
359-
public async Task GetArchive()
359+
public async Task GetArchive_NoQuerySpecified_PathConstructedWithNoParameters()
360360
{
361361
// Arrange
362362
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2).ConfigureAwait(false);
@@ -376,7 +376,7 @@ public async Task GetArchive()
376376

377377
[Test]
378378
[NGitLabRetry]
379-
public async Task GetArchiveWithNullQueryPassesNoParameters()
379+
public async Task GetArchive_QueryInstanceIsNull_PathConstructedWithNoParameters()
380380
{
381381
// Arrange
382382
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2).ConfigureAwait(false);
@@ -405,7 +405,8 @@ public async Task GetArchiveWithNullQueryPassesNoParameters()
405405
[TestCase(FileArchiveFormat.Tbz2, ".tbz2")]
406406
[TestCase(FileArchiveFormat.Zip, ".zip")]
407407
[NGitLabRetry]
408-
public async Task GetArchiveFormatValuePassedCorrectly(FileArchiveFormat? archiveFormat, string expectedExtension)
408+
public async Task GetArchive_QuerySpecifiesFormatValue_ArchiveExtensionPassedCorrectly(
409+
FileArchiveFormat? archiveFormat, string expectedExtension)
409410
{
410411
// Arrange
411412
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
@@ -423,13 +424,14 @@ public async Task GetArchiveFormatValuePassedCorrectly(FileArchiveFormat? archiv
423424
Assert.Multiple(() =>
424425
{
425426
Assert.That(requestPathAndQuery, Is.Not.Null);
426-
Assert.That(requestPathAndQuery.EndsWith($"/archive{expectedExtension}", StringComparison.OrdinalIgnoreCase), Is.True);
427+
Assert.That(requestPathAndQuery.EndsWith($"/archive{expectedExtension}",
428+
StringComparison.OrdinalIgnoreCase), Is.True);
427429
});
428430
}
429431

430432
[Test]
431433
[NGitLabRetry]
432-
public async Task GetArchiveShaValuePassedCorrectly()
434+
public async Task GetArchive_QuerySpecifiesRevision_ShaValuePassedCorrectly()
433435
{
434436
// Arrange
435437
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
@@ -448,13 +450,14 @@ public async Task GetArchiveShaValuePassedCorrectly()
448450
Assert.Multiple(() =>
449451
{
450452
Assert.That(requestPathAndQuery, Is.Not.Null);
451-
Assert.That(requestPathAndQuery.Contains($"sha={firstCommitId}", StringComparison.OrdinalIgnoreCase), Is.True);
453+
Assert.That(requestPathAndQuery.Contains($"sha={firstCommitId}",
454+
StringComparison.OrdinalIgnoreCase), Is.True);
452455
});
453456
}
454457

455458
[Test]
456459
[NGitLabRetry]
457-
public async Task GetArchivePathValuePassedCorrectly()
460+
public async Task GetArchive_QuerySpecifiesPath_PathValuePassedCorrectly()
458461
{
459462
// Arrange
460463
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
@@ -479,7 +482,7 @@ public async Task GetArchivePathValuePassedCorrectly()
479482

480483
[Test]
481484
[NGitLabRetry]
482-
public async Task GetArchiveCombinationOfValuesPassedCorrectly()
485+
public async Task GetArchive_QuerySpecifiesAllParameters_AllParametersPassedCorrectly()
483486
{
484487
// Arrange
485488
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);

NGitLab/Extensions/TypeExtensions.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.

NGitLab/Impl/RepositoryClient.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,14 @@ public void GetRawBlob(string sha, Action<Stream> parser)
5959

6060
public void GetArchive(Action<Stream> parser, FileArchiveQuery fileArchiveQuery)
6161
{
62-
var url = $"{_repoPath}/archive";
62+
var url = Utils.AppendSegmentToUrl(_repoPath, "/archive");
6363

6464
if (fileArchiveQuery != null)
6565
{
6666
// If a particular archive file format is requested, it is appended to the path directly as follows:
6767
// /project/123/repository/archive.zip
6868
// /project/123/repository/archive.tar
69-
if (fileArchiveQuery.Format.HasValue)
70-
{
71-
url += fileArchiveQuery.Format.Value.GetEnumMemberAttributeValue();
72-
}
73-
69+
url = Utils.AppendSegmentToUrl(url, fileArchiveQuery.Format, includeSegmentSeparator: false);
7470
url = Utils.AddParameter(url, "path", fileArchiveQuery.Path);
7571
url = Utils.AddParameter(url, "sha", fileArchiveQuery.Ref);
7672
}

NGitLab/Impl/Utils.cs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,18 @@ namespace NGitLab.Impl;
88

99
internal static class Utils
1010
{
11+
private const char UrlSegmentSeparatorChar = '/';
12+
1113
public static string AddParameter<T>(string url, string parameterName, T value)
1214
{
1315
if (value is null)
1416
return url;
1517

1618
var valueString = value.ToString();
17-
var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
18-
if (type.IsEnum)
19-
{
20-
var enumField = type.GetFields().FirstOrDefault(f => string.Equals(f.Name, valueString, StringComparison.Ordinal));
21-
if (enumField is not null)
22-
{
23-
var enumMemberValue = enumField.GetCustomAttributes(typeof(EnumMemberAttribute), inherit: true)
24-
.Cast<EnumMemberAttribute>()
25-
.FirstOrDefault()?
26-
.Value;
27-
if (enumMemberValue is not null)
28-
return AddParameterInternal(url, parameterName, enumMemberValue);
29-
}
30-
}
19+
var enumMemberValue = GetEnumMemberValue<T>(valueString);
20+
21+
if (enumMemberValue is not null)
22+
return AddParameterInternal(url, parameterName, enumMemberValue);
3123

3224
return AddParameterInternal(url, parameterName, valueString);
3325
}
@@ -96,11 +88,55 @@ public static string AddPageParams(string url, int? page, int? perPage)
9688
return url;
9789
}
9890

91+
public static string AppendSegmentToUrl<T>(string url, T value, bool includeSegmentSeparator = true)
92+
{
93+
if (value is null)
94+
return url;
95+
96+
// Don't allow segments to a url which already has parameters present
97+
if (url.Contains('?'))
98+
throw new InvalidOperationException("Cannot append segment to url which already has parameters present");
99+
100+
var valueString = value.ToString();
101+
var enumMemberValue = GetEnumMemberValue<T>(valueString);
102+
103+
if (enumMemberValue is not null)
104+
valueString = enumMemberValue;
105+
106+
url = url.TrimEnd(UrlSegmentSeparatorChar);
107+
valueString = valueString.TrimStart(UrlSegmentSeparatorChar);
108+
valueString = WebUtility.UrlEncode(valueString);
109+
110+
if (includeSegmentSeparator)
111+
return $"{url}{UrlSegmentSeparatorChar}{valueString}";
112+
113+
return $"{url}{valueString}";
114+
}
115+
99116
private static string AddParameterInternal(string url, string parameterName, string stringValue)
100117
{
101118
var @operator = !url.Contains("?") ? "?" : "&";
102119
var formattedValue = WebUtility.UrlEncode(stringValue);
103120
var parameter = $"{@operator}{parameterName}={formattedValue}";
104121
return url + parameter;
105122
}
123+
124+
private static string GetEnumMemberValue<T>(string valueString)
125+
{
126+
string enumMemberValue = null;
127+
var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
128+
if (type.IsEnum)
129+
{
130+
var enumField = type.GetFields().FirstOrDefault(f => string.Equals(f.Name, valueString, StringComparison.Ordinal));
131+
if (enumField is not null)
132+
{
133+
enumMemberValue = enumField.GetCustomAttributes(typeof(EnumMemberAttribute), inherit: true)
134+
.Cast<EnumMemberAttribute>()
135+
.FirstOrDefault()?
136+
.Value;
137+
}
138+
}
139+
140+
return enumMemberValue;
141+
}
106142
}

0 commit comments

Comments
 (0)