Skip to content

Commit

Permalink
New updates to generated code
Browse files Browse the repository at this point in the history
  • Loading branch information
octokitbot committed Aug 16, 2024
1 parent d28f526 commit 3da12dc
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 142 deletions.
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,53 @@ To install the package, you can use either of the following options:
- Type `Install-Package GitHub.Octokit.SDK` into the Package Manager Console, or
- Type `dotnet add ./path/to/myproject.csproj package GitHub.Octokit.SDK` in a terminal (replace `./path/to/myproject.csproj` by the path to the _*.csproj_ file you want to add the dependency)


### Make your first request

```csharp
using GitHub;
using GitHub.Octokit.Client;
using GitHub.Octokit.Client.Authentication;

var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "";
var request = RequestAdapter.Create(new TokenAuthProvider(new TokenProvider(token)));
var gitHubClient = new GitHubClient(request);
var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");
var adapter = RequestAdapter.Create(new TokenAuthProvider(tokenProvider));
await MakeRequest(new GitHubClient(adapter));

try
{
var response = await gitHubClient.Repositories.GetAsync();
response?.ForEach(repo => Console.WriteLine(repo.FullName));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
```

#### Custom configuration for requests

```csharp
using GitHub;
using GitHub.Octokit.Client;
using GitHub.Octokit.Client.Authentication;

var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");

var pullRequests = await gitHubClient.Repos["octokit"]["octokit.net"].Pulls.GetAsync();
var adapter = new ClientFactory()
.WithAuthenticationProvider(new TokenAuthProvider(tokenProvider))
.WithUserAgent("my-app", "1.0.0")
.WithRequestTimeout(TimeSpan.FromSeconds(100))
.WithBaseUrl("https://api.github.com")
.Build();

foreach (var pullRequest in pullRequests)
try
{
var response = await gitHubClient.Repositories.GetAsync();
response?.ForEach(repo => Console.WriteLine(repo.FullName));
}
catch (Exception e)
{
Console.WriteLine($"#{pullRequest.Number} {pullRequest.Title}");
Console.WriteLine(e.Message);
}
```

Expand Down
55 changes: 49 additions & 6 deletions cli/Authentication/AppInstallationToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,67 @@ public class AppInstallationToken
static readonly string CLIENT_ID = Environment.GetEnvironmentVariable("GITHUB_APP_CLIENT_ID") ?? "";
static readonly string PRIVATE_KEY_PATH = File.ReadAllText(Environment.GetEnvironmentVariable("GITHUB_APP_PRIVATE_KEY_PATH") ?? "");

public static async Task Run()

public static async Task Run(string approach)
{
switch (approach)
{
case "builder":
await RunWithBuilder();
break;
case "default":
await RunWithDefault();
break;
default:
Console.WriteLine("Invalid approach. Please provide 'builder' or 'default'");
break;
}
}

private static async Task RunWithBuilder()
{
var githubAppTokenProvider = new GitHubAppTokenProvider();
var rsa = RSA.Create();
rsa.ImportFromPem(PRIVATE_KEY_PATH);
var rsa = BuildRSAKey();

var aiAccessTokenProvider = new AppInstallationTokenProvider(CLIENT_ID, rsa, INSTALLATION_ID, githubAppTokenProvider);

var adapter = new ClientFactory()
.WithAuthenticationProvider(new AppInstallationAuthProvider(aiAccessTokenProvider))
.WithUserAgent("my-app", "1.0.0")
.WithRequestTimeout(TimeSpan.FromSeconds(100))
.WithBaseUrl("https://api.github.com")
.Build();

await MakeRequest(new GitHubClient(adapter));
}

private static async Task RunWithDefault()
{
var githubAppTokenProvider = new GitHubAppTokenProvider();
var rsa = BuildRSAKey();

var aiAccessTokenProvider = new AppInstallationTokenProvider(CLIENT_ID, rsa, INSTALLATION_ID, githubAppTokenProvider);
var aiAdapter = RequestAdapter.Create(new AppInstallationAuthProvider(aiAccessTokenProvider));
var aiGitHubClient = new GitHubClient(aiAdapter);
var adapter = RequestAdapter.Create(new AppInstallationAuthProvider(aiAccessTokenProvider));
await MakeRequest(new GitHubClient(adapter));
}

private static async Task MakeRequest(GitHubClient gitHubClient)
{
try
{
var response = await aiGitHubClient.Installation.Repositories.GetAsync();
var response = await gitHubClient.Installation.Repositories.GetAsync();
response?.Repositories?.ForEach(repo => Console.WriteLine(repo.FullName));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

private static RSA BuildRSAKey()
{
var rsa = RSA.Create();
rsa.ImportFromPem(PRIVATE_KEY_PATH);
return rsa;
}
}
38 changes: 35 additions & 3 deletions cli/Authentication/PersonalAccessToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,45 @@ namespace cli.Authentication;

public class PersonalAccessToken
{
public static async Task Run()
public static async Task Run(string approach)
{
switch (approach)
{
case "builder":
await RunWithBuilder();
break;
case "default":
await RunWithDefault();
break;
default:
Console.WriteLine("Invalid approach. Please provide 'builder' or 'default'");
break;
}
}

private static async Task RunWithBuilder()
{
var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");

var adapter = new ClientFactory()
.WithAuthenticationProvider(new TokenAuthProvider(tokenProvider))
.WithUserAgent("my-app", "1.0.0")
.WithRequestTimeout(TimeSpan.FromSeconds(100))
.WithBaseUrl("https://api.github.com")
.Build();

await MakeRequest(new GitHubClient(adapter));
}

private static async Task RunWithDefault()
{
// Personal Access Token authentication
var tokenProvider = new TokenProvider(Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? "");
var adapter = RequestAdapter.Create(new TokenAuthProvider(tokenProvider));
var gitHubClient = new GitHubClient(adapter);
await MakeRequest(new GitHubClient(adapter));
}

private static async Task MakeRequest(GitHubClient gitHubClient)
{
try
{
var response = await gitHubClient.Repositories.GetAsync();
Expand Down
21 changes: 18 additions & 3 deletions cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,34 @@ class Program
{
static async Task Main(string[] args)
{
if (args != null && args.Length == 0)
if (args == null || args.Length == 0)
{
Console.WriteLine("Please provide an argument: 'AppInstallationToken' or 'PersonalAccessToken'");
return;
}

var approach = "default";

if (args.Length > 1)
{
if (args[1] == "builder" || args[1] == "default")
{
approach = args[1];
}
else
{
Console.WriteLine("Invalid argument. Please provide 'builder' or 'default'");
return;
}
}

switch (args[0])
{
case "AppInstallationToken":
await AppInstallationToken.Run();
await AppInstallationToken.Run(approach);
break;
case "PersonalAccessToken":
await PersonalAccessToken.Run();
await PersonalAccessToken.Run(approach);
break;
default:
Console.WriteLine("Invalid argument. Please provide 'AppInstallationToken' or 'PersonalAccessToken'");
Expand Down
2 changes: 1 addition & 1 deletion src/Client/Authentication/GitHubAppTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task<string> GetGitHubAccessTokenAsync(string baseUrl, string jwt,
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));

var userAgentOptions = new UserAgentOptions();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(userAgentOptions.ProductName, userAgentOptions.ProductVersion));
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(userAgentOptions.ProductName ?? string.Empty, userAgentOptions.ProductVersion));

var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Expand Down
81 changes: 80 additions & 1 deletion src/Client/ClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

using System.Net;
using GitHub.Octokit.Client.Middleware;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;

namespace GitHub.Octokit.Client;

/// <summary>
/// Represents a client factory for creating <see cref="HttpClient"/>.
/// </summary>
public static class ClientFactory
public class ClientFactory
{
private TimeSpan? _requestTimeout;
private string? _baseUrl;

private IAuthenticationProvider? _authenticationProvider;
private readonly HttpMessageHandler? _finalHandler;
private static readonly Lazy<List<DelegatingHandler>> s_handlers =
new(() =>
[
Expand All @@ -19,6 +25,10 @@ public static class ClientFactory
new RateLimitHandler(),
]);

public ClientFactory(HttpMessageHandler? finalHandler = null) {
_finalHandler = finalHandler;
}

/// <summary>
/// Creates an <see cref="HttpClient"/> instance with the specified <see cref="HttpMessageHandler"/>.
/// If no <see cref="HttpMessageHandler"/> is provided, a default one will be used.
Expand All @@ -36,6 +46,52 @@ public static HttpClient Create(HttpMessageHandler? finalHandler = null)
return handler is not null ? new HttpClient(handler) : new HttpClient();
}

public ClientFactory WithUserAgent(string productName, string productVersion) {
AddOrCreateHandler(new UserAgentHandler(new Middleware.Options.UserAgentOptions{ProductName = productName, ProductVersion = productVersion}));
return this;
}

public ClientFactory WithRequestTimeout(TimeSpan timeSpan) {
_requestTimeout = timeSpan;
return this;
}

public ClientFactory WithBaseUrl(string baseUrl) {
_baseUrl = baseUrl;
return this;
}

public ClientFactory WithAuthenticationProvider(IAuthenticationProvider authenticationProvider) {
_authenticationProvider = authenticationProvider;
return this;
}

public HttpClientRequestAdapter Build()
{

if (_authenticationProvider == null) throw new ArgumentNullException("authenticationProvider");

var httpClient = new HttpClient();
var defaultHandlers = CreateDefaultHandlers();
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler: _finalHandler ?? GetDefaultHttpMessageHandler(), handlers: [.. defaultHandlers]);

if (handler != null)
{
httpClient = new HttpClient(handler);
}

if (_requestTimeout.HasValue)
{
httpClient.Timeout = _requestTimeout.Value;
}

if (!string.IsNullOrEmpty(_baseUrl))
{
httpClient.BaseAddress = new Uri(_baseUrl);
}

return RequestAdapter.Create(_authenticationProvider, httpClient);;
}
/// <summary>
/// Creates a list of default delegating handlers for the Octokit client.
/// </summary>
Expand Down Expand Up @@ -97,4 +153,27 @@ public static IList<DelegatingHandler> CreateDefaultHandlers()
/// <returns>The default HTTP message handler.</returns>
public static HttpMessageHandler GetDefaultHttpMessageHandler(IWebProxy? proxy = null) =>
new HttpClientHandler { Proxy = proxy, AllowAutoRedirect = false };

/// <summary>
/// In support of the constructor approach to building a client factory, this method allows for adding or updating
/// a handler in the list of handlers.
/// The final result of the list of handlers will be processed in the Build() method.
/// </summary>
/// <typeparam name="THandler"></typeparam>
/// <param name="handler"></param>
private void AddOrCreateHandler<THandler>(THandler handler) where THandler : DelegatingHandler
{
// Find the index of the handler that matches the specified type
int index = s_handlers.Value.FindIndex(h => h is THandler);

// If the handler is found, replace it with the new handler otehrwise add the new handler to the list
if (index >= 0)
{
s_handlers.Value[index] = handler;
}
else
{
s_handlers.Value.Add(handler);
}
}
}
15 changes: 10 additions & 5 deletions src/Client/RequestAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
// Copyright (c) GitHub 2023-2024 - Licensed as MIT.

using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;

namespace GitHub.Octokit.Client;

public static class RequestAdapter
/// <summary>
/// Represents an adapter for making HTTP requests using HttpClient with additional configuration options.
/// </summary>
public class RequestAdapter
{
/// <summary>
/// Represents an adapter for making HTTP requests using HttpClient.
/// Initializes and returns a new instance of the <see cref="HttpClientRequestAdapter"/> class.
/// </summary>
/// TODO: Implement the missing props and methods
/// <param name="authenticationProvider">The authentication provider to use for making requests.</param>
/// <param name="clientFactory">Optional: A custom HttpClient factory. If not provided, a default client will be created.</param>
/// <returns>A new instance of the <see cref="HttpClientRequestAdapter"/> class.</returns>

public static HttpClientRequestAdapter Create(IAuthenticationProvider authenticationProvider, HttpClient? clientFactory = null)
{
clientFactory ??= ClientFactory.Create();
Expand All @@ -25,4 +30,4 @@ public static HttpClientRequestAdapter Create(IAuthenticationProvider authentica

return gitHubRequestAdapter;
}
}
}
Loading

0 comments on commit 3da12dc

Please sign in to comment.