Implement IssuesClient and interface

This required updating our serialization strategy so we handle enums
better.
This commit is contained in:
Haacked
2013-10-22 10:29:26 -07:00
parent 1cc3da9ce3
commit ad210cecc7
35 changed files with 1278 additions and 55 deletions

View File

@@ -0,0 +1,74 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Octokit;
using Octokit.Tests.Integration;
using Xunit;
public class IssuesClientTests : IDisposable
{
readonly IGitHubClient _gitHubClient;
readonly Repository _repository;
public IssuesClientTests()
{
_gitHubClient = new GitHubClient("Test Runner User Agent")
{
Credentials = Helper.Credentials
};
var repoName = Helper.MakeNameWithTimestamp("public-repo");
_repository = _gitHubClient.Repository.Create(new NewRepository { Name = repoName }).Result;
}
[IntegrationTest]
public async Task CanCreateRetrieveAndDeleteIssue()
{
string owner = _repository.Owner.Login;
var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
var issue = await _gitHubClient.Issue.Create(owner, _repository.Name, newIssue);
try
{
Assert.NotNull(issue);
var retrieved = await _gitHubClient.Issue.Get(owner, _repository.Name, issue.Number);
var all = await _gitHubClient.Issue.GetForRepository(owner, _repository.Name);
Assert.NotNull(retrieved);
Assert.True(all.Any(i => i.Number == retrieved.Number));
}
finally
{
var closed = _gitHubClient.Issue.Update(owner, _repository.Name, issue.Number,
new IssueUpdate { State = ItemState.Closed })
.Result;
Assert.NotNull(closed);
}
}
[IntegrationTest]
public async Task CanRetrieveClosedIssues()
{
string owner = _repository.Owner.Login;
var newIssue = new NewIssue("A test issue") { Body = "A new unassigned issue" };
var issue1 = await _gitHubClient.Issue.Create(owner, _repository.Name, newIssue);
var issue2 = await _gitHubClient.Issue.Create(owner, _repository.Name, newIssue);
await _gitHubClient.Issue.Update(owner, _repository.Name, issue1.Number,
new IssueUpdate { State = ItemState.Closed });
await _gitHubClient.Issue.Update(owner, _repository.Name, issue2.Number,
new IssueUpdate { State = ItemState.Closed });
var retrieved = await _gitHubClient.Issue.GetForRepository(owner, _repository.Name,
new RepositoryIssueRequest { State = ItemState.Closed });
Assert.True(retrieved.Count >= 2);
Assert.True(retrieved.Any(i => i.Number == issue1.Number));
Assert.True(retrieved.Any(i => i.Number == issue2.Number));
}
public void Dispose()
{
Helper.DeleteRepo(_repository);
}
}

View File

@@ -56,6 +56,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="IntegrationTestAttribute.cs" /> <Compile Include="IntegrationTestAttribute.cs" />
<Compile Include="IssuesClientTests.cs" />
<Compile Include="MiscellaneousClientTests.cs" /> <Compile Include="MiscellaneousClientTests.cs" />
<Compile Include="Reactive\ObservableRepositoriesClientTests.cs" /> <Compile Include="Reactive\ObservableRepositoriesClientTests.cs" />
<Compile Include="ReleasesClientTests.cs" /> <Compile Include="ReleasesClientTests.cs" />

View File

@@ -0,0 +1,241 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NSubstitute;
using Octokit;
using Octokit.Internal;
using Octokit.Tests;
using Octokit.Tests.Helpers;
using Xunit;
public class IssuesClientTests
{
public class TheGetMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.Get("fake", "repo", 42);
connection.Received().Get<Issue>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/issues/42"),
null);
}
[Fact]
public async Task EnsuresNonNullArguments()
{
var client = new IssuesClient(Substitute.For<IApiConnection>());
await AssertEx.Throws<ArgumentNullException>(async () => await client.Get(null, "name", 1));
await AssertEx.Throws<ArgumentNullException>(async () => await client.Get("owner", null, 1));
}
}
public class TheGetAllForCurrentMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.GetAllForCurrent();
connection.Received().GetAll<Issue>(Arg.Is<Uri>(u => u.ToString() == "/issues"),
Args.EmptyDictionary);
}
[Fact]
public void SendsAppropriateParameters()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.GetAllForCurrent(new IssueRequest { SortDirection = SortDirection.Ascending });
connection.Received().GetAll<Issue>(Arg.Is<Uri>(u => u.ToString() == "/issues"),
Arg.Is<Dictionary<string, string>>(d => d["direction"] == "asc" && d.Count == 1));
}
}
public class TheGetAllForOwnedAndMemberRepositoriesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.GetAllForOwnedAndMemberRepositories();
connection.Received().GetAll<Issue>(Arg.Is<Uri>(u => u.ToString() == "/user/issues"),
Args.EmptyDictionary);
}
}
public class TheGetForRepositoryMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.GetForRepository("fake", "repo");
connection.Received().GetAll<Issue>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/issues"),
Args.EmptyDictionary);
}
[Fact]
public void SendsAppropriateParameters()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.GetForRepository("fake", "repo", new RepositoryIssueRequest
{
SortDirection = SortDirection.Ascending
});
connection.Received().GetAll<Issue>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/issues"),
Arg.Is<Dictionary<string, string>>(d => d["direction"] == "asc" && d.Count == 1));
}
[Fact]
public async Task EnsuresArgumentsNotNull()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
AssertEx.Throws<ArgumentNullException>(async () => await
client.GetForRepository(null, "name", new RepositoryIssueRequest()));
AssertEx.Throws<ArgumentException>(async () => await
client.GetForRepository("", "name", new RepositoryIssueRequest()));
AssertEx.Throws<ArgumentNullException>(async () => await
client.GetForRepository("owner", null, new RepositoryIssueRequest()));
AssertEx.Throws<ArgumentException>(async () => await
client.GetForRepository("owner", "", new RepositoryIssueRequest()));
AssertEx.Throws<ArgumentNullException>(async () => await
client.GetForRepository("owner", "name", null));
}
}
public class TheCreateMethod
{
[Fact]
public void PostsToCorrectUrl()
{
var newIssue = new NewIssue("some title");
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.Create("fake", "repo", newIssue);
connection.Received().Post<Issue>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/issues"),
newIssue);
}
[Fact]
public async Task EnsuresArgumentsNotNull()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create(null, "name", new NewIssue("title")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("", "name", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", null, new NewIssue("x")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("owner", "", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", "name", null));
}
}
public class TheUpdateMethod
{
[Fact]
public void PostsToCorrectUrl()
{
var issueUpdate = new IssueUpdate();
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
client.Update("fake", "repo", 42, issueUpdate);
connection.Received().Patch<Issue>(Arg.Is<Uri>(u => u.ToString() == "/repos/fake/repo/issues/42"),
issueUpdate);
}
[Fact]
public async Task EnsuresArgumentsNotNull()
{
var connection = Substitute.For<IApiConnection>();
var client = new IssuesClient(connection);
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create(null, "name", new NewIssue("title")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("", "name", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", null, new NewIssue("x")));
AssertEx.Throws<ArgumentException>(async () => await
client.Create("owner", "", new NewIssue("x")));
AssertEx.Throws<ArgumentNullException>(async () => await
client.Create("owner", "name", null));
}
}
public class TheCtor
{
[Fact]
public void EnsuresArgument()
{
Assert.Throws<ArgumentNullException>(() => new IssuesClient(null));
}
}
[Fact]
public void CanDeserializeIssue()
{
const string issueResponseJson = "{\"url\":\"https://api.github.com/repos/octokit-net-test/public-repo-" +
"20131022050247078/issues/1\",\"labels_url\":\"https://api.github.com/repos/octokit-net-test/publ" +
"ic-repo-20131022050247078/issues/1/labels{/name}\",\"comments_url\":\"https://api.github.com/rep" +
"os/octokit-net-test/public-repo-20131022050247078/issues/1/comments\",\"events_url\":\"https://a" +
"pi.github.com/repos/octokit-net-test/public-repo-20131022050247078/issues/1/events\",\"html_url" +
"\":\"https://github.com/octokit-net-test/public-repo-20131022050247078/issues/1\",\"id\":2139915" +
"4,\"number\":1,\"title\":\"A test issue\",\"user\":{\"login\":\"octokit-net-test\",\"id\":558045" +
"0,\"avatar_url\":\"https://2.gravatar.com/avatar/20724e5085dcbe92e660a61d282f665c?d=https%3A%2F%" +
"2Fidenticons.github.com%2Fb21d03168ecd65836d6407e4cdd61e0c.png\",\"gravatar_id\":\"20724e5085dcb" +
"e92e660a61d282f665c\",\"url\":\"https://api.github.com/users/octokit-net-test\",\"html_url\":\"h" +
"ttps://github.com/octokit-net-test\",\"followers_url\":\"https://api.github.com/users/octokit-ne" +
"t-test/followers\",\"following_url\":\"https://api.github.com/users/octokit-net-test/following{/" +
"other_user}\",\"gists_url\":\"https://api.github.com/users/octokit-net-test/gists{/gist_id}\",\"" +
"starred_url\":\"https://api.github.com/users/octokit-net-test/starred{/owner}{/repo}\",\"subscri" +
"ptions_url\":\"https://api.github.com/users/octokit-net-test/subscriptions\",\"organizations_url" +
"\":\"https://api.github.com/users/octokit-net-test/orgs\",\"repos_url\":\"https://api.github.com" +
"/users/octokit-net-test/repos\",\"events_url\":\"https://api.github.com/users/octokit-net-test/e" +
"vents{/privacy}\",\"received_events_url\":\"https://api.github.com/users/octokit-net-test/receiv" +
"ed_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"open\",\"assignee" +
"\":null,\"milestone\":null,\"comments\":0,\"created_at\":\"2013-10-22T17:02:48Z\",\"updated_at\"" +
":\"2013-10-22T17:02:48Z\",\"closed_at\":null,\"body\":\"A new unassigned issue\",\"closed_by\":null}";
var response = new ApiResponse<Issue>
{
Body = issueResponseJson,
ContentType = "application/json"
};
var jsonPipeline = new JsonHttpPipeline();
jsonPipeline.DeserializeResponse(response);
Assert.NotNull(response.BodyAsObject);
Assert.Equal(issueResponseJson, response.Body);
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using NSubstitute; using NSubstitute;
using Octokit.Internal; using Octokit.Internal;
@@ -45,5 +46,10 @@ namespace Octokit.Tests
{ {
get { return Arg.Any<NewRepository>(); } get { return Arg.Any<NewRepository>(); }
} }
public static Dictionary<string, string> EmptyDictionary
{
get { return Arg.Is<Dictionary<string, string>>(d => d.Count == 0); }
}
} }
} }

View File

@@ -22,6 +22,20 @@ namespace Octokit.Tests.Helpers
Assert.Equal(new Uri("https://example.com?foo=fooval&bar=barval"), uriWithParameters); Assert.Equal(new Uri("https://example.com?foo=fooval&bar=barval"), uriWithParameters);
} }
[Fact]
public void AppendsParametersAsQueryStringToRelativeUri()
{
var uri = new Uri("/issues", UriKind.Relative);
var uriWithParameters = uri.ApplyParameters(new Dictionary<string, string>
{
{"foo", "fooval"},
{"bar", "barval"}
});
Assert.Equal(new Uri("/issues?foo=fooval&bar=barval", UriKind.Relative), uriWithParameters);
}
[Fact] [Fact]
public void OverwritesExistingParameters() public void OverwritesExistingParameters()
{ {
@@ -37,19 +51,19 @@ namespace Octokit.Tests.Helpers
} }
[Fact] [Fact]
public void DoesNotChangeUrlWhenParametersIsNullOrEmpty() public void DoesNotChangeUrlWhenParametersEmpty()
{ {
var uri = new Uri("https://example.com"); var uri = new Uri("https://example.com");
var uriWithNullParameters = uri.ApplyParameters(null);
var uriWithEmptyParameters = uri.ApplyParameters(new Dictionary<string, string>()); var uriWithEmptyParameters = uri.ApplyParameters(new Dictionary<string, string>());
var uriWithNullParameters = uri.ApplyParameters(null);
Assert.Same(uri, uriWithNullParameters);
Assert.Equal(uri, uriWithEmptyParameters); Assert.Equal(uri, uriWithEmptyParameters);
Assert.Equal(uri, uriWithNullParameters);
} }
[Fact] [Fact]
public void EnsuresUriNotNull() public void EnsuresArgumentNotNull()
{ {
Uri uri = null; Uri uri = null;
Assert.Throws<ArgumentNullException>(() => uri.ApplyParameters(new Dictionary<string, string>())); Assert.Throws<ArgumentNullException>(() => uri.ApplyParameters(new Dictionary<string, string>()));

View File

@@ -0,0 +1,56 @@
using System;
using System.Globalization;
using Octokit;
using Xunit;
public class IssueRequestTests
{
public class TheToParametersDictionaryMethod
{
[Fact]
public void OnlyContainsChangedValues()
{
var request = new IssueRequest { SortDirection = SortDirection.Ascending };
var parameters = request.ToParametersDictionary();
Assert.Equal(1, parameters.Count);
Assert.Equal("asc", parameters["direction"]);
}
[Fact]
public void ContainsSetValues()
{
var request = new IssueRequest
{
Filter = IssueFilter.All,
State = ItemState.Closed,
SortProperty = IssueSort.Comments,
SortDirection = SortDirection.Ascending,
Since = DateTimeOffset.ParseExact("Wed 23 Jan 2013 8:30 AM -08:00",
"ddd dd MMM yyyy h:mm tt zzz", CultureInfo.InvariantCulture)
};
request.Labels.Add("bug");
request.Labels.Add("feature");
var parameters = request.ToParametersDictionary();
Assert.Equal("all", parameters["filter"]);
Assert.Equal("closed", parameters["state"]);
Assert.Equal("comments", parameters["sort"]);
Assert.Equal("asc", parameters["direction"]);
Assert.Equal("bug,feature", parameters["labels"]);
Assert.Equal("2013-01-23T16:30:00Z", parameters["since"]);
}
[Fact]
public void ReturnsEmptyDictionaryForDefaultRequest()
{
var request = new IssueRequest();
var parameters = request.ToParametersDictionary();
Assert.Empty(parameters);
}
}
}

View File

@@ -60,6 +60,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Authentication\CredentialsTests.cs" /> <Compile Include="Authentication\CredentialsTests.cs" />
<Compile Include="Clients\IssuesClientTests.cs" />
<Compile Include="Clients\NotificationsClientTests.cs" /> <Compile Include="Clients\NotificationsClientTests.cs" />
<Compile Include="Clients\ReleasesClientTests.cs" /> <Compile Include="Clients\ReleasesClientTests.cs" />
<Compile Include="Clients\SshKeysClientTests.cs" /> <Compile Include="Clients\SshKeysClientTests.cs" />
@@ -91,6 +92,7 @@
<Compile Include="Http\RateLimitTests.cs" /> <Compile Include="Http\RateLimitTests.cs" />
<Compile Include="Http\ResponseTests.cs" /> <Compile Include="Http\ResponseTests.cs" />
<Compile Include="Http\RequestTests.cs" /> <Compile Include="Http\RequestTests.cs" />
<Compile Include="Models\IssueRequestTests.cs" />
<Compile Include="Models\ModelExtensionsTests.cs" /> <Compile Include="Models\ModelExtensionsTests.cs" />
<Compile Include="Models\ReadOnlyPagedCollectionTests.cs" /> <Compile Include="Models\ReadOnlyPagedCollectionTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View File

@@ -51,6 +51,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Authentication\CredentialsTests.cs" /> <Compile Include="Authentication\CredentialsTests.cs" />
<Compile Include="Clients\IssuesClientTests.cs" />
<Compile Include="Clients\MiscellaneousClientTests.cs" /> <Compile Include="Clients\MiscellaneousClientTests.cs" />
<Compile Include="Clients\NotificationsClientTests.cs" /> <Compile Include="Clients\NotificationsClientTests.cs" />
<Compile Include="Clients\ReleasesClientTests.cs" /> <Compile Include="Clients\ReleasesClientTests.cs" />
@@ -82,7 +83,9 @@
<Compile Include="Http\RateLimitTests.cs" /> <Compile Include="Http\RateLimitTests.cs" />
<Compile Include="Http\ResponseTests.cs" /> <Compile Include="Http\ResponseTests.cs" />
<Compile Include="Http\RequestTests.cs" /> <Compile Include="Http\RequestTests.cs" />
<Compile Include="Models\IssueRequestTests.cs" />
<Compile Include="Models\ModelExtensionsTests.cs" /> <Compile Include="Models\ModelExtensionsTests.cs" />
<Compile Include="Models\ReadOnlyPagedCollectionTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\StringExtensionsTests.cs" /> <Compile Include="Helpers\StringExtensionsTests.cs" />
<Compile Include="Clients\RepositoriesClientTests.cs" /> <Compile Include="Clients\RepositoriesClientTests.cs" />

View File

@@ -8,12 +8,12 @@
/// <summary> /// <summary>
/// Initializes a new API client. /// Initializes a new API client.
/// </summary> /// </summary>
/// <param name="client">The client's connection.</param> /// <param name="apiConnection">The client's connection.</param>
protected ApiClient(IApiConnection client) protected ApiClient(IApiConnection apiConnection)
{ {
Ensure.ArgumentNotNull(client, "client"); Ensure.ArgumentNotNull(apiConnection, "apiConnection");
Client = client; ApiConnection = apiConnection;
} }
/// <summary> /// <summary>
@@ -22,6 +22,6 @@
/// <value> /// <value>
/// The API client's connection /// The API client's connection
/// </value> /// </value>
protected IApiConnection Client {get; private set;} protected IApiConnection ApiConnection {get; private set;}
} }
} }

View File

@@ -14,7 +14,7 @@ namespace Octokit
/// </remarks> /// </remarks>
public class AuthorizationsClient : ApiClient, IAuthorizationsClient public class AuthorizationsClient : ApiClient, IAuthorizationsClient
{ {
public AuthorizationsClient(IApiConnection client) : base(client) public AuthorizationsClient(IApiConnection apiConnection) : base(apiConnection)
{ {
} }
@@ -28,7 +28,7 @@ namespace Octokit
/// <returns>An <see cref="Authorization"/></returns> /// <returns>An <see cref="Authorization"/></returns>
public async Task<IReadOnlyList<Authorization>> GetAll() public async Task<IReadOnlyList<Authorization>> GetAll()
{ {
return await Client.GetAll<Authorization>(ApiUrls.Authorizations()); return await ApiConnection.GetAll<Authorization>(ApiUrls.Authorizations());
} }
/// <summary> /// <summary>
@@ -43,7 +43,7 @@ namespace Octokit
public async Task<Authorization> Get(int id) public async Task<Authorization> Get(int id)
{ {
var endpoint = "/authorizations/{0}".FormatUri(id); var endpoint = "/authorizations/{0}".FormatUri(id);
return await Client.Get<Authorization>(endpoint); return await ApiConnection.Get<Authorization>(endpoint);
} }
/// <summary> /// <summary>
@@ -81,7 +81,7 @@ namespace Octokit
note_url = newAuthorization.NoteUrl note_url = newAuthorization.NoteUrl
}; };
return await Client.Put<Authorization>(endpoint, requestData); return await ApiConnection.Put<Authorization>(endpoint, requestData);
} }
/// <summary> /// <summary>
@@ -124,7 +124,7 @@ namespace Octokit
try try
{ {
return await Client.Put<Authorization>( return await ApiConnection.Put<Authorization>(
endpoint, endpoint,
requestData, requestData,
twoFactorAuthenticationCode); twoFactorAuthenticationCode);
@@ -146,7 +146,7 @@ namespace Octokit
Ensure.ArgumentNotNull(authorizationUpdate, "authorizationUpdate"); Ensure.ArgumentNotNull(authorizationUpdate, "authorizationUpdate");
var endpoint = "/authorizations/{0}".FormatUri(id); var endpoint = "/authorizations/{0}".FormatUri(id);
return await Client.Patch<Authorization>(endpoint, authorizationUpdate); return await ApiConnection.Patch<Authorization>(endpoint, authorizationUpdate);
} }
/// <summary> /// <summary>
@@ -158,7 +158,7 @@ namespace Octokit
{ {
Ensure.ArgumentNotNull(newAuthorization, "newAuthorization"); Ensure.ArgumentNotNull(newAuthorization, "newAuthorization");
return await Client.Post<Authorization>(ApiUrls.Authorizations(), newAuthorization); return await ApiConnection.Post<Authorization>(ApiUrls.Authorizations(), newAuthorization);
} }
/// <summary> /// <summary>
@@ -169,7 +169,7 @@ namespace Octokit
public async Task Delete(int id) public async Task Delete(int id)
{ {
var endpoint = "/authorizations/{0}".FormatUri(id); var endpoint = "/authorizations/{0}".FormatUri(id);
await Client.Delete(endpoint); await ApiConnection.Delete(endpoint);
} }
} }
} }

View File

@@ -0,0 +1,188 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Octokit
{
public class IssuesClient : ApiClient, IIssuesClient
{
public IssuesClient(IApiConnection apiConnection) : base(apiConnection)
{
}
/// <summary>
/// Gets a single Issue by number./// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#get-a-single-issue
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public async Task<Issue> Get(string owner, string name, int number)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
return await ApiConnection.Get<Issue>(ApiUrls.Issue(owner, name, number));
}
/// <summary>
/// Gets all open issues assigned to the authenticated user across all the authenticated users visible
/// repositories including owned repositories, member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetAllForCurrent()
{
return await GetAllForCurrent(new IssueRequest());
}
/// <summary>
/// Gets all issues across all the authenticated users visible repositories including owned repositories,
/// member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetAllForCurrent(IssueRequest request)
{
Ensure.ArgumentNotNull(request, "request");
return await ApiConnection.GetAll<Issue>(ApiUrls.Issues(), request.ToParametersDictionary());
}
/// <summary>
/// Gets all open issues assigned to the authenticated user across owned and member repositories for the
/// authenticated user.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetAllForOwnedAndMemberRepositories()
{
return await GetAllForOwnedAndMemberRepositories(new IssueRequest());
}
/// <summary>
/// Gets all issues across owned and member repositories for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetAllForOwnedAndMemberRepositories(IssueRequest request)
{
Ensure.ArgumentNotNull(request, "request");
return await ApiConnection.GetAll<Issue>(ApiUrls.IssuesForOwnedAndMember(),
request.ToParametersDictionary());
}
/// <summary>
/// Gets all open issues assigned to the authenticated user for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetAllForOrganization(string organization)
{
return await GetAllForOrganization(organization, new IssueRequest());
}
/// <summary>
/// Gets all issues for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetAllForOrganization(string organization, IssueRequest request)
{
return await ApiConnection.GetAll<Issue>(ApiUrls.Issues(organization), request.ToParametersDictionary());
}
/// <summary>
/// Gets all open issues assigned to the authenticated user for the repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetForRepository(string owner, string name)
{
return await GetForRepository(owner, name, new RepositoryIssueRequest());
}
/// <summary>
/// Gets issues for a repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
public async Task<IReadOnlyList<Issue>> GetForRepository(string owner, string name,
RepositoryIssueRequest request)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(request, "request");
return await ApiConnection.GetAll<Issue>(ApiUrls.Issues(owner, name), request.ToParametersDictionary());
}
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="newIssue">A <see cref="NewIssue"/> instance describing the new issue to create</param>
/// <returns></returns>
public async Task<Issue> Create(string owner, string name, NewIssue newIssue)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(newIssue, "newIssue");
return await ApiConnection.Post<Issue>(ApiUrls.Issues(owner, name), newIssue);
}
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <param name="issueUpdate">An <see cref="IssueUpdate"/> instance describing the changes to make to the issue
/// </param>
/// <returns></returns>
public async Task<Issue> Update(string owner, string name, int number, IssueUpdate issueUpdate)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNull(issueUpdate, "issueUpdate");
return await ApiConnection.Patch<Issue>(ApiUrls.Issue(owner, name, number), issueUpdate);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Octokit
/// <returns>A <see cref="IReadOnlyPagedCollection{Notification}"/> of <see cref="Notification"/>.</returns> /// <returns>A <see cref="IReadOnlyPagedCollection{Notification}"/> of <see cref="Notification"/>.</returns>
public async Task<IReadOnlyCollection<Notification>> GetAllForCurrent() public async Task<IReadOnlyCollection<Notification>> GetAllForCurrent()
{ {
return await Client.GetAll<Notification>(ApiUrls.Notifications()); return await ApiConnection.GetAll<Notification>(ApiUrls.Notifications());
} }
/// <summary> /// <summary>
@@ -27,7 +27,7 @@ namespace Octokit
/// <returns>A <see cref="IReadOnlyPagedCollection{Notification}"/> of <see cref="Notification"/>.</returns> /// <returns>A <see cref="IReadOnlyPagedCollection{Notification}"/> of <see cref="Notification"/>.</returns>
public async Task<IReadOnlyCollection<Notification>> GetAllForRepository(string owner, string name) public async Task<IReadOnlyCollection<Notification>> GetAllForRepository(string owner, string name)
{ {
return await Client.GetAll<Notification>(ApiUrls.Notifications(owner, name)); return await ApiConnection.GetAll<Notification>(ApiUrls.Notifications(owner, name));
} }
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Octokit
{ {
public class OrganizationsClient : ApiClient, IOrganizationsClient public class OrganizationsClient : ApiClient, IOrganizationsClient
{ {
public OrganizationsClient(IApiConnection client) : base(client) public OrganizationsClient(IApiConnection apiConnection) : base(apiConnection)
{ {
} }
@@ -17,19 +17,19 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(org, "org"); Ensure.ArgumentNotNullOrEmptyString(org, "org");
var endpoint = "/orgs/{0}".FormatUri(org); var endpoint = "/orgs/{0}".FormatUri(org);
return await Client.Get<Organization>(endpoint); return await ApiConnection.Get<Organization>(endpoint);
} }
public async Task<IReadOnlyList<Organization>> GetAllForCurrent() public async Task<IReadOnlyList<Organization>> GetAllForCurrent()
{ {
return await Client.GetAll<Organization>(ApiUrls.Organizations()); return await ApiConnection.GetAll<Organization>(ApiUrls.Organizations());
} }
public async Task<IReadOnlyList<Organization>> GetAll(string user) public async Task<IReadOnlyList<Organization>> GetAll(string user)
{ {
Ensure.ArgumentNotNullOrEmptyString(user, "user"); Ensure.ArgumentNotNullOrEmptyString(user, "user");
return await Client.GetAll<Organization>(ApiUrls.Organizations(user)); return await ApiConnection.GetAll<Organization>(ApiUrls.Organizations(user));
} }
} }
} }

View File

@@ -7,7 +7,7 @@ namespace Octokit
{ {
public class ReleasesClient : ApiClient, IReleasesClient public class ReleasesClient : ApiClient, IReleasesClient
{ {
public ReleasesClient(IApiConnection client) : base(client) public ReleasesClient(IApiConnection apiConnection) : base(apiConnection)
{ {
} }
@@ -17,7 +17,7 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(name, "repository"); Ensure.ArgumentNotNullOrEmptyString(name, "repository");
var endpoint = ApiUrls.Releases(owner, name); var endpoint = ApiUrls.Releases(owner, name);
return await Client.GetAll<Release>(endpoint, null, "application/vnd.github.manifold-preview"); return await ApiConnection.GetAll<Release>(endpoint, null, "application/vnd.github.manifold-preview");
} }
public async Task<Release> CreateRelease(string owner, string name, ReleaseUpdate data) public async Task<Release> CreateRelease(string owner, string name, ReleaseUpdate data)
@@ -27,7 +27,7 @@ namespace Octokit
Ensure.ArgumentNotNull(data, "data"); Ensure.ArgumentNotNull(data, "data");
var endpoint = ApiUrls.Releases(owner, name); var endpoint = ApiUrls.Releases(owner, name);
return await Client.Post<Release>(endpoint, data, "application/vnd.github.manifold-preview"); return await ApiConnection.Post<Release>(endpoint, data, "application/vnd.github.manifold-preview");
} }
public async Task<ReleaseAsset> UploadAsset(Release release, ReleaseAssetUpload data) public async Task<ReleaseAsset> UploadAsset(Release release, ReleaseAssetUpload data)
@@ -36,7 +36,7 @@ namespace Octokit
Ensure.ArgumentNotNull(data, "data"); Ensure.ArgumentNotNull(data, "data");
var endpoint = release.UploadUrl.ExpandUriTemplate(new {name = data.FileName}); var endpoint = release.UploadUrl.ExpandUriTemplate(new {name = data.FileName});
return await Client.Post<ReleaseAsset>( return await ApiConnection.Post<ReleaseAsset>(
endpoint, endpoint,
data.RawData, data.RawData,
"application/vnd.github.manifold-preview", "application/vnd.github.manifold-preview",

View File

@@ -8,7 +8,7 @@ namespace Octokit
{ {
public class RepositoriesClient : ApiClient, IRepositoriesClient public class RepositoriesClient : ApiClient, IRepositoriesClient
{ {
public RepositoriesClient(IApiConnection client) : base(client) public RepositoriesClient(IApiConnection apiConnection) : base(apiConnection)
{ {
} }
@@ -23,7 +23,7 @@ namespace Octokit
if (string.IsNullOrEmpty(newRepository.Name)) if (string.IsNullOrEmpty(newRepository.Name))
throw new ArgumentException("The new repository's name must not be null."); throw new ArgumentException("The new repository's name must not be null.");
return await Client.Post<Repository>(ApiUrls.Repositories(), newRepository); return await ApiConnection.Post<Repository>(ApiUrls.Repositories(), newRepository);
} }
/// <summary> /// <summary>
@@ -39,7 +39,7 @@ namespace Octokit
if (string.IsNullOrEmpty(newRepository.Name)) if (string.IsNullOrEmpty(newRepository.Name))
throw new ArgumentException("The new repository's name must not be null."); throw new ArgumentException("The new repository's name must not be null.");
return await Client.Post<Repository>(ApiUrls.OrganizationRepositories(organizationLogin), newRepository); return await ApiConnection.Post<Repository>(ApiUrls.OrganizationRepositories(organizationLogin), newRepository);
} }
/// <summary> /// <summary>
@@ -54,7 +54,7 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(name, "name");
var endpoint = "/repos/{0}/{1}".FormatUri(owner, name); var endpoint = "/repos/{0}/{1}".FormatUri(owner, name);
await Client.Delete(endpoint); await ApiConnection.Delete(endpoint);
} }
public async Task<Repository> Get(string owner, string name) public async Task<Repository> Get(string owner, string name)
@@ -63,26 +63,26 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(name, "name");
var endpoint = "/repos/{0}/{1}".FormatUri(owner, name); var endpoint = "/repos/{0}/{1}".FormatUri(owner, name);
return await Client.Get<Repository>(endpoint); return await ApiConnection.Get<Repository>(endpoint);
} }
public async Task<IReadOnlyList<Repository>> GetAllForCurrent() public async Task<IReadOnlyList<Repository>> GetAllForCurrent()
{ {
return await Client.GetAll<Repository>(ApiUrls.Repositories()); return await ApiConnection.GetAll<Repository>(ApiUrls.Repositories());
} }
public async Task<IReadOnlyList<Repository>> GetAllForUser(string login) public async Task<IReadOnlyList<Repository>> GetAllForUser(string login)
{ {
Ensure.ArgumentNotNullOrEmptyString(login, "login"); Ensure.ArgumentNotNullOrEmptyString(login, "login");
return await Client.GetAll<Repository>(ApiUrls.Repositories(login)); return await ApiConnection.GetAll<Repository>(ApiUrls.Repositories(login));
} }
public async Task<IReadOnlyList<Repository>> GetAllForOrg(string organization) public async Task<IReadOnlyList<Repository>> GetAllForOrg(string organization)
{ {
Ensure.ArgumentNotNullOrEmptyString(organization, "organization"); Ensure.ArgumentNotNullOrEmptyString(organization, "organization");
return await Client.GetAll<Repository>(ApiUrls.OrganizationRepositories(organization)); return await ApiConnection.GetAll<Repository>(ApiUrls.OrganizationRepositories(organization));
} }
public async Task<Readme> GetReadme(string owner, string name) public async Task<Readme> GetReadme(string owner, string name)
@@ -91,8 +91,8 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(name, "name");
var endpoint = "/repos/{0}/{1}/readme".FormatUri(owner, name); var endpoint = "/repos/{0}/{1}/readme".FormatUri(owner, name);
var readmeInfo = await Client.Get<ReadmeResponse>(endpoint, null); var readmeInfo = await ApiConnection.Get<ReadmeResponse>(endpoint, null);
return new Readme(readmeInfo, Client); return new Readme(readmeInfo, ApiConnection);
} }
public async Task<string> GetReadmeHtml(string owner, string name) public async Task<string> GetReadmeHtml(string owner, string name)
@@ -101,7 +101,7 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(name, "name");
var endpoint = "/repos/{0}/{1}/readme".FormatUri(owner, name); var endpoint = "/repos/{0}/{1}/readme".FormatUri(owner, name);
return await Client.GetHtml(endpoint, null); return await ApiConnection.GetHtml(endpoint, null);
} }
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Octokit
{ {
public class SshKeysClient : ApiClient, ISshKeysClient public class SshKeysClient : ApiClient, ISshKeysClient
{ {
public SshKeysClient(IApiConnection client) : base(client) public SshKeysClient(IApiConnection apiConnection) : base(apiConnection)
{ {
} }
@@ -16,26 +16,26 @@ namespace Octokit
{ {
var endpoint = "/user/keys/{0}".FormatUri(id); var endpoint = "/user/keys/{0}".FormatUri(id);
return await Client.Get<SshKey>(endpoint); return await ApiConnection.Get<SshKey>(endpoint);
} }
public async Task<IReadOnlyList<SshKey>> GetAll(string user) public async Task<IReadOnlyList<SshKey>> GetAll(string user)
{ {
Ensure.ArgumentNotNullOrEmptyString(user, "user"); Ensure.ArgumentNotNullOrEmptyString(user, "user");
return await Client.GetAll<SshKey>(ApiUrls.SshKeys(user)); return await ApiConnection.GetAll<SshKey>(ApiUrls.SshKeys(user));
} }
public async Task<IReadOnlyList<SshKey>> GetAllForCurrent() public async Task<IReadOnlyList<SshKey>> GetAllForCurrent()
{ {
return await Client.GetAll<SshKey>(ApiUrls.SshKeys()); return await ApiConnection.GetAll<SshKey>(ApiUrls.SshKeys());
} }
public async Task<SshKey> Create(SshKeyUpdate key) public async Task<SshKey> Create(SshKeyUpdate key)
{ {
Ensure.ArgumentNotNull(key, "key"); Ensure.ArgumentNotNull(key, "key");
return await Client.Post<SshKey>(ApiUrls.SshKeys(), key); return await ApiConnection.Post<SshKey>(ApiUrls.SshKeys(), key);
} }
public async Task<SshKey> Update(int id, SshKeyUpdate key) public async Task<SshKey> Update(int id, SshKeyUpdate key)
@@ -43,14 +43,14 @@ namespace Octokit
Ensure.ArgumentNotNull(key, "key"); Ensure.ArgumentNotNull(key, "key");
var endpoint = "/user/keys/{0}".FormatUri(id); var endpoint = "/user/keys/{0}".FormatUri(id);
return await Client.Patch<SshKey>(endpoint, key); return await ApiConnection.Patch<SshKey>(endpoint, key);
} }
public async Task Delete(int id) public async Task Delete(int id)
{ {
var endpoint = "/user/keys/{0}".FormatUri(id); var endpoint = "/user/keys/{0}".FormatUri(id);
await Client.Delete(endpoint); await ApiConnection.Delete(endpoint);
} }
} }
} }

View File

@@ -15,7 +15,7 @@ namespace Octokit
{ {
static readonly Uri _userEndpoint = new Uri("/user", UriKind.Relative); static readonly Uri _userEndpoint = new Uri("/user", UriKind.Relative);
public UsersClient(IApiConnection client) : base(client) public UsersClient(IApiConnection apiConnection) : base(apiConnection)
{ {
} }
@@ -30,7 +30,7 @@ namespace Octokit
Ensure.ArgumentNotNullOrEmptyString(login, "login"); Ensure.ArgumentNotNullOrEmptyString(login, "login");
var endpoint = "/users/{0}".FormatUri(login); var endpoint = "/users/{0}".FormatUri(login);
return await Client.Get<User>(endpoint); return await ApiConnection.Get<User>(endpoint);
} }
/// <summary> /// <summary>
@@ -40,7 +40,7 @@ namespace Octokit
/// <returns>A <see cref="User"/></returns> /// <returns>A <see cref="User"/></returns>
public async Task<User> Current() public async Task<User> Current()
{ {
return await Client.Get<User>(_userEndpoint); return await ApiConnection.Get<User>(_userEndpoint);
} }
/// <summary> /// <summary>
@@ -53,7 +53,7 @@ namespace Octokit
{ {
Ensure.ArgumentNotNull(user, "user"); Ensure.ArgumentNotNull(user, "user");
return await Client.Patch<User>(_userEndpoint, user); return await ApiConnection.Patch<User>(_userEndpoint, user);
} }
/// <summary> /// <summary>
@@ -62,7 +62,7 @@ namespace Octokit
/// <returns></returns> /// <returns></returns>
public async Task<IReadOnlyList<EmailAddress>> GetEmails() public async Task<IReadOnlyList<EmailAddress>> GetEmails()
{ {
return await Client.Get<ReadOnlyCollection<EmailAddress>>(ApiUrls.Emails(), null); return await ApiConnection.Get<ReadOnlyCollection<EmailAddress>>(ApiUrls.Emails(), null);
} }
} }
} }

View File

@@ -35,6 +35,7 @@ namespace Octokit
Connection = connection; Connection = connection;
Authorization = new AuthorizationsClient(new ApiConnection(connection)); Authorization = new AuthorizationsClient(new ApiConnection(connection));
Issue = new IssuesClient(new ApiConnection(connection));
Miscellaneous = new MiscellaneousClient(connection); Miscellaneous = new MiscellaneousClient(connection);
Notification = new NotificationsClient(new ApiConnection(connection)); Notification = new NotificationsClient(new ApiConnection(connection));
Organization = new OrganizationsClient(new ApiConnection(connection)); Organization = new OrganizationsClient(new ApiConnection(connection));
@@ -79,6 +80,7 @@ namespace Octokit
public IConnection Connection { get; private set; } public IConnection Connection { get; private set; }
public IAuthorizationsClient Authorization { get; private set; } public IAuthorizationsClient Authorization { get; private set; }
public IIssuesClient Issue { get; private set; }
public IMiscellaneousClient Miscellaneous { get; private set; } public IMiscellaneousClient Miscellaneous { get; private set; }
public IOrganizationsClient Organization { get; private set; } public IOrganizationsClient Organization { get; private set; }
public IRepositoriesClient Repository { get; private set; } public IRepositoriesClient Repository { get; private set; }

View File

@@ -13,6 +13,8 @@ namespace Octokit
static readonly Uri _currentUserEmailsEndpoint = new Uri("/user/emails", UriKind.Relative); static readonly Uri _currentUserEmailsEndpoint = new Uri("/user/emails", UriKind.Relative);
static readonly Uri _currentUserAuthorizationsEndpoint = new Uri("/authorizations", UriKind.Relative); static readonly Uri _currentUserAuthorizationsEndpoint = new Uri("/authorizations", UriKind.Relative);
static readonly Uri _currentUserNotificationsEndpoint = new Uri("/notifications", UriKind.Relative); static readonly Uri _currentUserNotificationsEndpoint = new Uri("/notifications", UriKind.Relative);
static readonly Uri _currentUserAllIssues = new Uri("/issues", UriKind.Relative);
static readonly Uri _currentUserOwnedAndMemberIssues = new Uri("/user/issues", UriKind.Relative);
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> that returns all of the repositories for the currently logged in user in /// Returns the <see cref="Uri"/> that returns all of the repositories for the currently logged in user in
@@ -125,10 +127,67 @@ namespace Octokit
/// Returns the <see cref="Uri"/> that returns all of the notifications for the currently logged in user /// Returns the <see cref="Uri"/> that returns all of the notifications for the currently logged in user
/// specific to the repository. /// specific to the repository.
/// </summary> /// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns> /// <returns></returns>
public static Uri Notifications(string owner, string name) public static Uri Notifications(string owner, string name)
{ {
return "/repos/{0}/{1}/notifications".FormatUri(owner, name); return "/repos/{0}/{1}/notifications".FormatUri(owner, name);
} }
/// <summary>
/// Returns the <see cref="Uri"/> that returns all of the issues across all the authenticated users visible
/// repositories including owned repositories, member repositories, and organization repositories:
/// </summary>
public static Uri Issues()
{
return _currentUserAllIssues;
}
/// <summary>
/// Returns the <see cref="Uri"/> that returns all of the issues across owned and member repositories for the
/// authenticated user:
/// </summary>
public static Uri IssuesForOwnedAndMember()
{
return _currentUserOwnedAndMemberIssues;
}
/// <summary>
/// Returns the <see cref="Uri"/> that returns all of the issues for the currently logged in user
/// specific to the repository.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
public static Uri Issues(string owner, string name)
{
return "/repos/{0}/{1}/issues".FormatUri(owner, name);
}
/// <summary>
/// Returns the <see cref="Uri"/> that returns all of the issues for the specified organization for the
/// currently logged in user.
/// </summary>
/// <param name="organization">The name of the organization</param>
/// <returns></returns>
public static Uri Issues(string organization)
{
return "/orgs/{0}/issues".FormatUri(organization);
}
/// <summary>
/// Returns the <see cref="Uri"/> for the specified issue.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
public static Uri Issue(string owner, string name, int number)
{
return "/repos/{0}/{1}/issues/{2}".FormatUri(owner, name, number);
}
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Octokit namespace Octokit
{ {

View File

@@ -10,13 +10,19 @@ namespace Octokit
{ {
Ensure.ArgumentNotNull(uri, "uri"); Ensure.ArgumentNotNull(uri, "uri");
if (parameters == null) return uri; if (parameters == null || !parameters.Any()) return uri;
string query = String.Join("&", parameters.Select(kvp => kvp.Key + "=" + kvp.Value));
if (uri.IsAbsoluteUri)
{
var uriBuilder = new UriBuilder(uri) var uriBuilder = new UriBuilder(uri)
{ {
Query = String.Join("&", parameters.Select(kvp => kvp.Key + "=" + kvp.Value)) Query = query
}; };
return uriBuilder.Uri; return uriBuilder.Uri;
} }
return new Uri(uri + "?" + query, UriKind.Relative);
}
} }
} }

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;

View File

@@ -50,6 +50,37 @@ namespace Octokit.Internal
output = obj; output = obj;
return true; return true;
} }
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase",
Justification = "The API expects lowercase values")]
protected override object SerializeEnum(Enum p)
{
return p.ToString().ToLowerInvariant();
}
// Overridden to handle enums.
public override object DeserializeObject(object value, Type type)
{
var stringValue = value as string;
if (stringValue != null)
{
if (ReflectionUtils.GetTypeInfo(type).IsEnum)
{
return Enum.Parse(type, stringValue, ignoreCase: true);
}
if (ReflectionUtils.IsNullableType(type))
{
var underlyingType = Nullable.GetUnderlyingType(type);
if (ReflectionUtils.GetTypeInfo(underlyingType).IsEnum)
{
return Enum.Parse(underlyingType, stringValue, ignoreCase: true);
}
}
}
return base.DeserializeObject(value, type);
}
} }
} }
} }

View File

@@ -7,6 +7,7 @@ namespace Octokit
IConnection Connection { get; } IConnection Connection { get; }
IAuthorizationsClient Authorization { get; } IAuthorizationsClient Authorization { get; }
IIssuesClient Issue { get; }
IMiscellaneousClient Miscellaneous { get; } IMiscellaneousClient Miscellaneous { get; }
IOrganizationsClient Organization { get; } IOrganizationsClient Organization { get; }
IRepositoriesClient Repository { get; } IRepositoriesClient Repository { get; }

134
Octokit/IIssuesClient.cs Normal file
View File

@@ -0,0 +1,134 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Octokit
{
public interface IIssuesClient
{
/// <summary>
/// Gets a single Issue by number.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#get-a-single-issue
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <returns></returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get",
Justification = "Method makes a network request")]
Task<Issue> Get(string owner, string name, int number);
/// <summary>
/// Gets all open issues assigned to the authenticated user across all the authenticated users visible
/// repositories including owned repositories, member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetAllForCurrent();
/// <summary>
/// Gets all issues across all the authenticated users visible repositories including owned repositories,
/// member repositories, and organization repositories.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetAllForCurrent(IssueRequest request);
/// <summary>
/// Gets all open issues assigned to the authenticated user across owned and member repositories for the
/// authenticated user.
/// </summary>
/// <remarks>
/// Issues are sorted by the create date descending.
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetAllForOwnedAndMemberRepositories();
/// <summary>
/// Gets all issues across owned and member repositories for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetAllForOwnedAndMemberRepositories(IssueRequest request);
/// <summary>
/// Gets all open issues assigned to the authenticated user for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetAllForOrganization(string organization);
/// <summary>
/// Gets all issues for a given organization for the authenticated user.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues
/// </remarks>
/// <param name="organization">The name of the organization</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetAllForOrganization(string organization, IssueRequest request);
/// <summary>
/// Gets all open issues assigned to the authenticated user for the repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetForRepository(string owner, string name);
/// <summary>
/// Gets issues for a repository.
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/issues/#list-issues-for-a-repository
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="request">Used to filter and sort the list of issues returned</param>
/// <returns></returns>
Task<IReadOnlyList<Issue>> GetForRepository(string owner, string name, RepositoryIssueRequest request);
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="newIssue">A <see cref="NewIssue"/> instance describing the new issue to create</param>
/// <returns></returns>
Task<Issue> Create(string owner, string name, NewIssue newIssue);
/// <summary>
/// Creates an issue for the specified repository. Any user with pull access to a repository can create an
/// issue.
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#create-an-issue</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="number">The issue number</param>
/// <param name="issueUpdate">An <see cref="IssueUpdate"/> instance describing the changes to make to the issue
/// </param>
/// <returns></returns>
Task<Issue> Update(string owner, string name, int number, IssueUpdate issueUpdate);
}
}

78
Octokit/Models/Issue.cs Normal file
View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Octokit
{
public class Issue
{
/// <summary>
/// The URL for this milestone.
/// </summary>
public Uri Url { get; set; }
public Uri HtmlUrl { get; set; }
/// <summary>
/// The issue number.
/// </summary>
public int Number { get; set; }
/// <summary>
/// Whether the issue is open or closed.
/// </summary>
public ItemState State { get; set; }
/// <summary>
/// Title of the issue
/// </summary>
public string Title { get; set; }
/// <summary>
/// Details about the issue.
/// </summary>
public string Body { get; set; }
/// <summary>
/// The user that created the issue.
/// </summary>
public User User { get; set; }
/// <summary>
/// The set of labels applied to the issue
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public ICollection<Label> Labels { get; set; }
/// <summary>
/// The user this issue is assigned to.
/// </summary>
public User Assignee { get; set; }
/// <summary>
/// The milestone, if any, that this issue is assigned to.
/// </summary>
public Milestone Milestone { get; set; }
/// <summary>
/// The number of comments on the issue.
/// </summary>
public int Comments { get; set; }
public PullRequest PullRequest { get; set; }
/// <summary>
/// The date the issue was closed if closed.
/// </summary>
public DateTimeOffset? ClosedAt { get; set; }
/// <summary>
/// The date the issue was created.
/// </summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// The date the issue was last updated.
/// </summary>
public DateTimeOffset? UpdatedAt { get; set; }
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Octokit
{
public class IssueRequest
{
static readonly IssueRequest _defaultParameterValues = new IssueRequest();
public IssueRequest()
{
Filter = IssueFilter.Assigned;
State = ItemState.Open;
Labels = new Collection<string>();
SortProperty = IssueSort.Created;
SortDirection = SortDirection.Descending;
}
public IssueFilter Filter { get; set; }
public ItemState State { get; set; }
public Collection<string> Labels { get; private set; }
public IssueSort SortProperty { get; set; }
public SortDirection SortDirection { get; set; }
public DateTimeOffset? Since { get; set; }
/// <summary>
/// Returns a dictionary of query string parameters that represent this request. Only values that
/// do not have default values are in the dictionary. If everything is default, this returns an
/// empty dictionary.
/// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase",
Justification = "The API expects lowercase")]
public virtual IDictionary<string, string> ToParametersDictionary()
{
var parameters = new Dictionary<string, string>();
if (Filter != _defaultParameterValues.Filter)
{
parameters.Add("filter", Enum.GetName(typeof(IssueFilter), Filter).ToLowerInvariant());
}
if (State != _defaultParameterValues.State)
{
parameters.Add("state", Enum.GetName(typeof(ItemState), State).ToLowerInvariant());
}
if (SortProperty != _defaultParameterValues.SortProperty)
{
parameters.Add("sort", Enum.GetName(typeof(IssueSort), SortProperty).ToLowerInvariant());
}
if (SortDirection != _defaultParameterValues.SortDirection)
{
parameters.Add("direction", "asc");
}
if (Since != null)
{
parameters.Add("since", Since.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture));
}
if (Labels.Count > 0)
{
parameters.Add("labels", String.Join(",", Labels));
}
return parameters;
}
}
/// <summary>
///
/// </summary>
/// <remarks>http://developer.github.com/v3/issues/#list-issues</remarks>
public enum IssueFilter
{
/// <summary>
/// Issues assigned to the authenticated user.
/// </summary>
Assigned,
/// <summary>
/// Issues created by the authenticated user.
/// </summary>
Created,
/// <summary>
/// Issues mentioning the authenticated user.
/// </summary>
Mentioned,
/// <summary>
/// Issues the authenticated user is subscribed to for updates.
/// </summary>
Subscribed,
/// <summary>
/// All issues the authenticated user can see, regardless of participation or creation.
/// </summary>
All
}
public enum ItemState
{
/// <summary>
/// Isuses that are open (default).
/// </summary>
Open,
/// <summary>
/// Isuses that are closed.
/// </summary>
Closed
}
public enum IssueSort
{
Created,
Updated,
Comments
}
public enum SortDirection
{
Ascending,
Descending
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.ObjectModel;
namespace Octokit
{
public class IssueUpdate
{
public IssueUpdate()
{
Labels = new Collection<string>();
}
/// <summary>
/// Title of the milestone (required)
/// </summary>
public string Title { get; set; }
/// <summary>
/// Details about the issue.
/// </summary>
public string Body { get; set; }
/// <summary>
/// Login for the user that this issue should be assigned to.
/// </summary>
/// <remarks>
/// Only users with push access can set the assignee for new issues. The assignee is silently dropped otherwise.
/// </remarks>
public string Assignee { get; set; }
/// <summary>
/// Milestone to associate this issue with.
/// </summary>
/// <remarks>
/// Only users with push access can set the milestone for new issues. The milestone is silently dropped
/// otherwise
/// </remarks>
public int? Milestone { get; set; }
/// <summary>
/// Labels to associate with this issue.
/// </summary>
/// <remarks>
/// Only users with push access can set labels for new issues. Labels are silently dropped otherwise.
/// </remarks>
public Collection<string> Labels { get; private set; }
/// <summary>
/// Whether the issue is open or closed.
/// </summary>
public ItemState State { get; set; }
}
}

11
Octokit/Models/Label.cs Normal file
View File

@@ -0,0 +1,11 @@
using System;
namespace Octokit
{
public class Label
{
public Uri Url { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Octokit
{
public class Milestone
{
public Uri Url { get; set; }
public int Number { get; set; }
public ItemState State { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public User Creator { get; set; }
public int OpenIssues { get; set; }
public int ClosedIssues { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset? DueOn { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Octokit
{
/// <summary>
/// Describes a new issue to create via the <see cref="IIssuesClient.Create(NewIssue)"/> method.
/// </summary>
public class NewIssue
{
public NewIssue(string title)
{
Title = title;
Labels = new Collection<string>();
}
/// <summary>
/// Title of the milestone (required)
/// </summary>
public string Title { get; private set; }
/// <summary>
/// Details about the issue.
/// </summary>
public string Body { get; set; }
/// <summary>
/// Login for the user that this issue should be assigned to.
/// </summary>
/// <remarks>
/// Only users with push access can set the assignee for new issues. The assignee is silently dropped otherwise.
/// </remarks>
public string Assignee { get; set; }
/// <summary>
/// Milestone to associate this issue with.
/// </summary>
/// <remarks>
/// Only users with push access can set the milestone for new issues. The milestone is silently dropped
/// otherwise
/// </remarks>
public int? Milestone { get; set; }
/// <summary>
/// Labels to associate with this issue.
/// </summary>
/// <remarks>
/// Only users with push access can set labels for new issues. Labels are silently dropped otherwise.
/// </remarks>
public Collection<string> Labels { get; private set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Octokit
{
public class PullRequest
{
public Uri HtmlUrl { get; set; }
public Uri DiffUrl { get; set; }
public Uri PatchUrl { get; set; }
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace Octokit
{
public class RepositoryIssueRequest : IssueRequest
{
/// <summary>
/// Identifies a filter for the milestone. Use "*" for issues with any milestone.
/// Use the milestone number for a specific milestone. Use the value "none" for issues with any milestones.
/// </summary>
public string Milestone { get; set; }
/// <summary>
/// Returns a dictionary of query string parameters that represent this request. Only values that
/// do not have default values are in the dictionary. If everything is default, this returns an
/// empty dictionary.
/// </summary>
/// <returns></returns>
public override IDictionary<string, string> ToParametersDictionary()
{
var dictionary = base.ToParametersDictionary();
Debug.Assert(dictionary != null, "Base implementation is wrong. Dictionary should never be null");
if (!Milestone.IsBlank())
{
dictionary.Add("milestone", Milestone);
}
return dictionary;
}
}
}

View File

@@ -81,8 +81,18 @@
<Compile Include="..\SolutionInfo.cs"> <Compile Include="..\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link> <Link>Properties\SolutionInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Clients\IssuesClient.cs" />
<Compile Include="IIssuesClient.cs" />
<Compile Include="Models\Issue.cs" />
<Compile Include="Models\IssueRequest.cs" />
<Compile Include="Models\IssueUpdate.cs" />
<Compile Include="Models\Label.cs" />
<Compile Include="Models\Milestone.cs" />
<Compile Include="Models\NewIssue.cs" />
<Compile Include="Models\Notification.cs" /> <Compile Include="Models\Notification.cs" />
<Compile Include="Models\NotificationInfo.cs" /> <Compile Include="Models\NotificationInfo.cs" />
<Compile Include="Models\PullRequest.cs" />
<Compile Include="Models\RepositoryIssueRequest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Exceptions\TwoFactorChallengeFailedException.cs" /> <Compile Include="Exceptions\TwoFactorChallengeFailedException.cs" />
<Compile Include="Exceptions\TwoFactorRequiredException.cs" /> <Compile Include="Exceptions\TwoFactorRequiredException.cs" />

View File

@@ -111,6 +111,7 @@
<Compile Include="Clients\ApiClient.cs" /> <Compile Include="Clients\ApiClient.cs" />
<Compile Include="Clients\ApiPagination.cs" /> <Compile Include="Clients\ApiPagination.cs" />
<Compile Include="Clients\AuthorizationsClient.cs" /> <Compile Include="Clients\AuthorizationsClient.cs" />
<Compile Include="Clients\IssuesClient.cs" />
<Compile Include="Clients\MiscellaneousClient.cs" /> <Compile Include="Clients\MiscellaneousClient.cs" />
<Compile Include="Clients\NotificationsClient.cs" /> <Compile Include="Clients\NotificationsClient.cs" />
<Compile Include="Clients\OrganizationsClient.cs" /> <Compile Include="Clients\OrganizationsClient.cs" />
@@ -146,6 +147,7 @@
<Compile Include="IApiPagination.cs" /> <Compile Include="IApiPagination.cs" />
<Compile Include="IAuthorizationsClient.cs" /> <Compile Include="IAuthorizationsClient.cs" />
<Compile Include="IGitHubClient.cs" /> <Compile Include="IGitHubClient.cs" />
<Compile Include="IIssuesClient.cs" />
<Compile Include="IMiscellaneousClient.cs" /> <Compile Include="IMiscellaneousClient.cs" />
<Compile Include="INotificationsClient.cs" /> <Compile Include="INotificationsClient.cs" />
<Compile Include="IOrganizationsClient.cs" /> <Compile Include="IOrganizationsClient.cs" />
@@ -180,12 +182,19 @@
<Compile Include="Models\Authorization.cs" /> <Compile Include="Models\Authorization.cs" />
<Compile Include="Models\AuthorizationUpdate.cs" /> <Compile Include="Models\AuthorizationUpdate.cs" />
<Compile Include="Models\EmailAddress.cs" /> <Compile Include="Models\EmailAddress.cs" />
<Compile Include="Models\Issue.cs" />
<Compile Include="Models\IssueRequest.cs" />
<Compile Include="Models\IssueUpdate.cs" />
<Compile Include="Models\Label.cs" />
<Compile Include="Models\Milestone.cs" />
<Compile Include="Models\NewAuthorization.cs" /> <Compile Include="Models\NewAuthorization.cs" />
<Compile Include="Models\NewIssue.cs" />
<Compile Include="Models\NewRepository.cs" /> <Compile Include="Models\NewRepository.cs" />
<Compile Include="Models\Notification.cs" /> <Compile Include="Models\Notification.cs" />
<Compile Include="Models\NotificationInfo.cs" /> <Compile Include="Models\NotificationInfo.cs" />
<Compile Include="Models\Organization.cs" /> <Compile Include="Models\Organization.cs" />
<Compile Include="Models\Plan.cs" /> <Compile Include="Models\Plan.cs" />
<Compile Include="Models\PullRequest.cs" />
<Compile Include="Models\Readme.cs" /> <Compile Include="Models\Readme.cs" />
<Compile Include="Models\ReadmeResponse.cs" /> <Compile Include="Models\ReadmeResponse.cs" />
<Compile Include="Models\Release.cs" /> <Compile Include="Models\Release.cs" />
@@ -193,6 +202,7 @@
<Compile Include="Models\ReleaseAssetUpload.cs" /> <Compile Include="Models\ReleaseAssetUpload.cs" />
<Compile Include="Models\ReleaseUpdate.cs" /> <Compile Include="Models\ReleaseUpdate.cs" />
<Compile Include="Models\Repository.cs" /> <Compile Include="Models\Repository.cs" />
<Compile Include="Models\RepositoryIssueRequest.cs" />
<Compile Include="Models\SshKey.cs" /> <Compile Include="Models\SshKey.cs" />
<Compile Include="Models\SshKeyInfo.cs" /> <Compile Include="Models\SshKeyInfo.cs" />
<Compile Include="Models\SshKeyUpdate.cs" /> <Compile Include="Models\SshKeyUpdate.cs" />