Skip to content

Commit

Permalink
add metadata to each client action (#2124)
Browse files Browse the repository at this point in the history
  • Loading branch information
shiftkey authored Mar 5, 2020
1 parent a2b0d87 commit f968856
Show file tree
Hide file tree
Showing 79 changed files with 1,178 additions and 78 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"**/obj": true,
"**/packaging": true,
"**/coverage-results": true
}
},
"files.trimTrailingWhitespace": true,
"files.trimFinalNewlines": true,
"files.insertFinalNewline": true
}
89 changes: 89 additions & 0 deletions Octokit.Tests.Conventions/ClientRouteTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;

namespace Octokit.Tests.Conventions
{
public class ClientRouteTests
{
[Theory]
[MemberData(nameof(GetClientClasses))]
public void ClassMethodsAreMarkedAsGeneratedManualOrDotNetSpecific(Type clientClass)
{
var methodFailures = new List<MethodInfo>();

foreach (var method in clientClass.GetMethodsOrdered().Where(IsNotBoilerplateMethod))
{
var success = MethodIsMarkedGeneratedManualOrDotNetSpecific(method);
if (!success)
{
methodFailures.Add(method);
}
}

if (methodFailures.Count > 0)
{
var methodNames = string.Join(", ", methodFailures.Select(m => m.Name));
throw new Xunit.Sdk.XunitException($"These methods on {clientClass.Name} are not marked with one of ManualRouteAttribute, GeneratedRouteAttribute or DotNetSpecificRouteAttribute: '{methodNames}'");
}
}

static bool MethodIsMarkedGeneratedManualOrDotNetSpecific(MethodInfo method)
{
var manualRoute = method.GetCustomAttribute<ManualRouteAttribute>();
if (manualRoute != null)
{
return true;
}

var generatedRoute = method.GetCustomAttribute<GeneratedRouteAttribute>();
if (generatedRoute != null)
{
return true;
}

var dotnetSpecificRoute = method.GetCustomAttribute<DotNetSpecificRouteAttribute>();
if (dotnetSpecificRoute != null)
{
return true;
}

var obsolete = method.GetCustomAttribute<ObsoleteAttribute>();
if (obsolete != null)
{
return true;
}

return false;
}

static bool IsNotBoilerplateMethod(MethodInfo method)
{
if (method.IsSpecialName)
{
return false;
}

if (method.Name == "GetType" || method.Name == "ToString" || method.Name == "GetHashCode" || method.Name == "Equals")
{
return false;
}

return true;
}

public static IEnumerable<object[]> GetClientClasses()
{
return typeof(IGitHubClient)
.GetTypeInfo()
.Assembly
.ExportedTypes
.Where(TypeExtensions.IsClientClass)
.Where(t => t != typeof(StatisticsClient)) // This convention doesn't apply to this one type.
.Where(t => t != typeof(GitHubClient))
.Select(type => new[] { type });
}
}
}
7 changes: 6 additions & 1 deletion Octokit.Tests.Conventions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public static bool IsClientInterface(this Type type)
return type.GetTypeInfo().IsInterface && type.Name.EndsWith(ClientSuffix) && type.Namespace == typeof(IGitHubClient).Namespace;
}

public static bool IsClientClass(this Type type)
{
return type.GetTypeInfo().IsClass && type.Name.EndsWith(ClientSuffix) && type.Namespace == typeof(IGitHubClient).Namespace;
}

public static Type GetObservableClientInterface(this Type type)
{
var observableClient = typeof(IObservableEventsClient);
Expand Down Expand Up @@ -124,4 +129,4 @@ public struct CustomTypeInfo
public Type Type { get; set; }
public TypeCategory TypeCategory { get; set; }
}
}
}
8 changes: 8 additions & 0 deletions Octokit/Clients/AssigneesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public AssigneesClient(IApiConnection apiConnection) : base(apiConnection)
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
[ManualRoute("GET", "/repos/{owner}/{name}/assignees")]
public Task<IReadOnlyList<User>> GetAllForRepository(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Expand All @@ -36,6 +37,7 @@ public Task<IReadOnlyList<User>> GetAllForRepository(string owner, string name)
/// Gets all the available assignees (owner + collaborators) to which issues may be assigned.
/// </summary>
/// <param name="repositoryId">The Id of the repository</param>
[ManualRoute("GET", "/repositories/{id}/assignees")]
public Task<IReadOnlyList<User>> GetAllForRepository(long repositoryId)
{
return GetAllForRepository(repositoryId, ApiOptions.None);
Expand All @@ -47,6 +49,7 @@ public Task<IReadOnlyList<User>> GetAllForRepository(long repositoryId)
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="options">The options to change API's response.</param>
[ManualRoute("GET", "/repos/{owner}/{name}/assignees")]
public Task<IReadOnlyList<User>> GetAllForRepository(string owner, string name, ApiOptions options)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Expand All @@ -63,6 +66,7 @@ public Task<IReadOnlyList<User>> GetAllForRepository(string owner, string name,
/// </summary>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="options">The options to change API's response.</param>
[ManualRoute("GET", "/repositories/{id}/assignees")]
public Task<IReadOnlyList<User>> GetAllForRepository(long repositoryId, ApiOptions options)
{
Ensure.ArgumentNotNull(options, nameof(options));
Expand All @@ -78,6 +82,7 @@ public Task<IReadOnlyList<User>> GetAllForRepository(long repositoryId, ApiOptio
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="assignee">Username of the prospective assignee</param>
[ManualRoute("GET", "/repos/{owner}/{name}/assignees/{username}")]
public async Task<bool> CheckAssignee(string owner, string name, string assignee)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Expand All @@ -103,6 +108,7 @@ public async Task<bool> CheckAssignee(string owner, string name, string assignee
/// <param name="number">The issue number</param>
/// <param name="assignees">List of names of assignees to add</param>
/// <returns></returns>
[ManualRoute("POST", "/repos/{owner}/{name}/issues/{number}/assignees")]
public Task<Issue> AddAssignees(string owner, string name, int number, AssigneesUpdate assignees)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Expand All @@ -120,6 +126,7 @@ public Task<Issue> AddAssignees(string owner, string name, int number, Assignees
/// <param name="number">The issue number</param>
/// <param name="assignees">List of assignees to remove</param>
/// <returns></returns>
[ManualRoute("DELETE", "/repos/{owner}/{name}/issues/{number}/assignees")]
public Task<Issue> RemoveAssignees(string owner, string name, int number, AssigneesUpdate assignees)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Expand All @@ -134,6 +141,7 @@ public Task<Issue> RemoveAssignees(string owner, string name, int number, Assign
/// </summary>
/// <param name="repositoryId">The Id of the repository</param>
/// <param name="assignee">Username of the prospective assignee</param>
[ManualRoute("GET", "/repositories/{id}/assignees/{username}")]
public async Task<bool> CheckAssignee(long repositoryId, string assignee)
{
Ensure.ArgumentNotNullOrEmptyString(assignee, nameof(assignee));
Expand Down
25 changes: 20 additions & 5 deletions Octokit/Clients/AuthorizationsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public AuthorizationsClient(IApiConnection apiConnection) : base(apiConnection)
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A list of <see cref="Authorization"/>s for the authenticated user.</returns>
[ManualRoute("GET", "/authorizations")]
public Task<IReadOnlyList<Authorization>> GetAll()
{
return GetAll(ApiOptions.None);
Expand All @@ -50,6 +51,7 @@ public Task<IReadOnlyList<Authorization>> GetAll()
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A list of <see cref="Authorization"/>s for the authenticated user.</returns>
[ManualRoute("GET", "/authorizations")]
public Task<IReadOnlyList<Authorization>> GetAll(ApiOptions options)
{
Ensure.ArgumentNotNull(options, nameof(options));
Expand All @@ -70,6 +72,7 @@ public Task<IReadOnlyList<Authorization>> GetAll(ApiOptions options)
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The specified <see cref="Authorization"/>.</returns>
[ManualRoute("GET", "/authorizations/{id}")]
public Task<Authorization> Get(int id)
{
return ApiConnection.Get<Authorization>(ApiUrls.Authorizations(id), null);
Expand All @@ -92,6 +95,7 @@ public Task<Authorization> Get(int id)
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
[ManualRoute("POST", "/authorizations")]
public Task<ApplicationAuthorization> Create(NewAuthorization newAuthorization)
{
Ensure.ArgumentNotNull(newAuthorization, nameof(newAuthorization));
Expand Down Expand Up @@ -126,6 +130,7 @@ public Task<ApplicationAuthorization> Create(NewAuthorization newAuthorization)
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
[ManualRoute("POST", "/authorizations")]
public Task<ApplicationAuthorization> Create(
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode)
Expand Down Expand Up @@ -164,6 +169,7 @@ public Task<ApplicationAuthorization> Create(
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
[ManualRoute("POST", "/authorizations")]
public Task<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
Expand Down Expand Up @@ -208,6 +214,7 @@ public Task<ApplicationAuthorization> Create(
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
[ManualRoute("POST", "/authorizations")]
public Task<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
Expand All @@ -234,7 +241,7 @@ public Task<ApplicationAuthorization> Create(
}

/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application doesn’t already
/// Creates a new authorization for the specified OAuth application if an authorization for that application doesn’t already
/// exist for the user; otherwise, returns the user’s existing authorization for that application.
/// </summary>
/// <remarks>
Expand All @@ -252,6 +259,7 @@ public Task<ApplicationAuthorization> Create(
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
[ManualRoute("PUT", "/authorizations/clients/{id}")]
public Task<ApplicationAuthorization> GetOrCreateApplicationAuthentication(
string clientId,
string clientSecret,
Expand All @@ -275,7 +283,7 @@ public Task<ApplicationAuthorization> GetOrCreateApplicationAuthentication(
}

/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application doesn’t already
/// Creates a new authorization for the specified OAuth application if an authorization for that application doesn’t already
/// exist for the user; otherwise, returns the user’s existing authorization for that application.
/// </summary>
/// <remarks>
Expand All @@ -294,6 +302,7 @@ public Task<ApplicationAuthorization> GetOrCreateApplicationAuthentication(
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
[ManualRoute("PUT", "/authorizations/clients/{id}")]
public async Task<ApplicationAuthorization> GetOrCreateApplicationAuthentication(
string clientId,
string clientSecret,
Expand Down Expand Up @@ -336,6 +345,7 @@ public async Task<ApplicationAuthorization> GetOrCreateApplicationAuthentication
/// <param name="clientId">Client Id of the OAuth application for the token</param>
/// <param name="accessToken">The OAuth token to check</param>
/// <returns>The valid <see cref="ApplicationAuthorization"/>.</returns>
[ManualRoute("POST", "/applications/{id}/token")]
public Task<ApplicationAuthorization> CheckApplicationAuthentication(string clientId, string accessToken)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
Expand All @@ -360,6 +370,7 @@ public Task<ApplicationAuthorization> CheckApplicationAuthentication(string clie
/// <param name="clientId">ClientID of the OAuth application for the token</param>
/// <param name="accessToken">The OAuth token to reset</param>
/// <returns>The valid <see cref="ApplicationAuthorization"/> with a new OAuth token</returns>
[ManualRoute("PATCH", "/applications/{id}/token")]
public Task<ApplicationAuthorization> ResetApplicationAuthentication(string clientId, string accessToken)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
Expand All @@ -384,6 +395,7 @@ public Task<ApplicationAuthorization> ResetApplicationAuthentication(string clie
/// <param name="clientId">ClientID of the OAuth application for the token</param>
/// <param name="accessToken">The OAuth token to revoke</param>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
[ManualRoute("DELETE", "/applications/{id}/token")]
public Task RevokeApplicationAuthentication(string clientId, string accessToken)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
Expand All @@ -403,7 +415,7 @@ public Task RevokeApplicationAuthentication(string clientId, string accessToken)
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#update-an-existing-authorization">API
/// See the <a href="http://developer.github.com/v3/oauth/#update-an-existing-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">Id of the <see cref="Authorization"/> to update</param>
Expand All @@ -413,6 +425,7 @@ public Task RevokeApplicationAuthentication(string clientId, string accessToken)
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The updated <see cref="Authorization"/>.</returns>
[ManualRoute("PATCH", "/authorizations/{id}")]
public Task<Authorization> Update(int id, AuthorizationUpdate authorizationUpdate)
{
Ensure.ArgumentNotNull(authorizationUpdate, nameof(authorizationUpdate));
Expand All @@ -427,7 +440,7 @@ public Task<Authorization> Update(int id, AuthorizationUpdate authorizationUpdat
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide Id of the authorization to delete</param>
Expand All @@ -436,6 +449,7 @@ public Task<Authorization> Update(int id, AuthorizationUpdate authorizationUpdat
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
[ManualRoute("DELETE", "/authorizations/{id}")]
public Task Delete(int id)
{
return ApiConnection.Delete(ApiUrls.Authorizations(id));
Expand All @@ -446,7 +460,7 @@ public Task Delete(int id)
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide Id of the authorization to delete</param>
Expand All @@ -456,6 +470,7 @@ public Task Delete(int id)
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
[ManualRoute("DELETE", "/authorizations/{id}")]
public Task Delete(int id, string twoFactorAuthenticationCode)
{
return ApiConnection.Delete(ApiUrls.Authorizations(id), twoFactorAuthenticationCode);
Expand Down
Loading

0 comments on commit f968856

Please sign in to comment.