Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add BasicAuth #14

Merged
merged 10 commits into from
Feb 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/DefaultFluentHttp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private static HttpRequestMessage CreateRequest(in HttpMethod method, in Uri uri
var req = new HttpRequestMessage(method, uri);

foreach (var (key, value) in options.Headers)
req.Headers.Add(key, value);
req.Headers.TryAddWithoutValidation(key, value);

return req;
}
Expand Down
1 change: 1 addition & 0 deletions src/Exceptions/HttpCallException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace MyNihongo.FluentHttp;
public sealed class HttpCallException : Exception
{
public HttpCallException(HttpStatusCode statusCode, string content)
: base($"Response code: {statusCode}")
{
StatusCode = statusCode;
Content = content;
Expand Down
28 changes: 28 additions & 0 deletions src/Fluent/Extensions/FluentHttpEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

public static class FluentHttpEx
{
/// <summary>
/// Appends <see cref="pathSegment"/> to the base URI of the HTTP call
/// </summary>
/// <param name="this">Instance of the HTTP client provider</param>
/// <param name="pathSegment">Path segment added to the base URI</param>
public static IFluentHttpWithOptions AppendPathSegment(this IFluentHttp @this, string pathSegment) =>
new FluentHttpWithOptions(@this)
{
Expand All @@ -11,6 +16,11 @@ public static class FluentHttpEx
}
};

/// <summary>
/// Appends <see cref="pathSegments"/> to the base URI of the HTTP call
/// </summary>
/// <param name="this">Instance of the HTTP client provider</param>
/// <param name="pathSegments">Path segments added to the base URI</param>
public static IFluentHttpWithOptions AppendPathSegments(this IFluentHttp @this, params string[] pathSegments)
{
var result = new FluentHttpWithOptions(@this);
Expand All @@ -19,6 +29,12 @@ public static IFluentHttpWithOptions AppendPathSegments(this IFluentHttp @this,
return result;
}

/// <summary>
/// Adds the <see cref="header"/> with the <see cref="value"/> to the HTTP call
/// </summary>
/// <param name="this">Instance of the HTTP client provider</param>
/// <param name="header">Key of the header</param>
/// <param name="value">Value of the header</param>
public static IFluentHttpWithOptions WithHeader(this IFluentHttp @this, string header, string value) =>
new FluentHttpWithOptions(@this)
{
Expand All @@ -30,4 +46,16 @@ public static IFluentHttpWithOptions AppendPathSegments(this IFluentHttp @this,
}
}
};

/// <summary>
/// Adds the Basic Authentication header with the <see cref="username"/> and the <see cref="password"/> to the HTTP call
/// </summary>
/// <param name="this">Instance of the HTTP client provider</param>
/// <param name="username">Username of the authenticating user</param>
/// <param name="password">Password of the authenticating user</param>
public static IFluentHttpWithOptions WithBasicAuth(this IFluentHttp @this, string username, string password)
{
var (header, value) = AuthUtils.BasicAuth(username, password);
return @this.WithHeader(header, value);
}
}
7 changes: 7 additions & 0 deletions src/Fluent/Extensions/FluentHttpWithOptionsEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,11 @@ public static IFluentHttpWithOptions WithHeader(this IFluentHttpWithOptions @thi
@this.Options.Headers.Add(header, value);
return @this;
}

/// <inheritdoc cref="FluentHttpEx.WithBasicAuth"/>
public static IFluentHttpWithOptions WithBasicAuth(this IFluentHttpWithOptions @this, string username, string password)
{
var (header, value) = AuthUtils.BasicAuth(username, password);
return @this.WithHeader(header, value);
}
}
20 changes: 20 additions & 0 deletions src/Models/HeaderModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace MyNihongo.FluentHttp;

internal readonly ref struct HeaderModel
{
public HeaderModel(string key, string value)
{
Key = key;
Value = value;
}

public string Key { get; }

public string Value { get; }

public void Deconstruct(out string key, out string value)
{
key = Key;
value = Value;
}
}
2 changes: 1 addition & 1 deletion src/MyNihongo.FluentHttp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<Authors>MyNihongo</Authors>
<Description>Fluent wrapper around IHttpClientFactory</Description>
<Copyright>Copyright © 2022 MyNihongo</Copyright>
Expand Down
14 changes: 14 additions & 0 deletions src/Utils/AuthUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text;

namespace MyNihongo.FluentHttp;

internal static class AuthUtils
{
private const string AuthHeaderKey = "Authorization";

public static HeaderModel BasicAuth(string username, string password)
{
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
return new HeaderModel(AuthHeaderKey, $"Basic {value}");
}
}
67 changes: 60 additions & 7 deletions tests/MyNihongo.FluentHttp.Tests.Integration/ApprovalTests.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using ApprovalTests;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace MyNihongo.FluentHttp.Tests.Integration;

internal static class ApprovalTests
{
public static void VerifyJson(object obj)
private static readonly JsonSerializer JsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings
{
var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions
Converters = new JsonConverter[]
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
new StringEnumConverter()
}
});

static ApprovalTests()
{
VerifierSettings.DerivePathInfo((_, projectDirectory, type, method) =>
{
var basePath = GetExecutionPath(projectDirectory, type);
basePath = Path.Combine(basePath, "Approvals");

return new PathInfo(basePath, type.Name, method.Name);
});

Approvals.VerifyJson(json);
VerifierSettings.UseStrictJson();
}

public static async Task<string> ToJsonStringAsync<T>(this Task<T> @this)
{
var obj = await @this.ConfigureAwait(false);
return GetJsonString(obj);
}

private static string GetJsonString(object? obj)
{
if (obj == null)
return string.Empty;

using var stringWriter = new StringWriter();
using var jsonWriter = new JsonTextWriter(stringWriter)
{
Formatting = Formatting.Indented,
IndentChar = '\t',
Indentation = 1
};

JsonSerializer.Serialize(jsonWriter, obj);
return stringWriter.ToString();
}

private static string GetExecutionPath(string projectDirectory, Type classType)
{
if (string.IsNullOrEmpty(classType.Namespace))
return projectDirectory;

var @namespace = classType.Namespace;
if (projectDirectory.EndsWith(Path.DirectorySeparatorChar))
projectDirectory = Path.GetDirectoryName(projectDirectory)!;

var rootNamespace = Path.GetFileName(projectDirectory);
if (@namespace.StartsWith(rootNamespace))
@namespace = @namespace[(rootNamespace.Length + 1)..]; // +1 for the trailing `.`

var dirs = @namespace.Split('.')
.Prepend(projectDirectory)
.ToArray();

return Path.Combine(dirs);
}
}