Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Access to the last ApiInfo object #855

Merged
merged 11 commits into from
Aug 28, 2015
2 changes: 1 addition & 1 deletion Octokit.Reactive/IObservableGitHubClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Octokit.Reactive
{
public interface IObservableGitHubClient
public interface IObservableGitHubClient : IApiInfo
{
IConnection Connection { get; }

Expand Down
6 changes: 6 additions & 0 deletions Octokit.Reactive/ObservableGitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,11 @@ public IConnection Connection
public IObservableNotificationsClient Notification { get; private set; }
public IObservableGitDatabaseClient GitDatabase { get; private set; }
public IObservableSearchClient Search { get; private set; }

/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>
/// <returns><seealso cref="ApiInfo"/> representing the information returned as part of an Api call</returns>
public ApiInfo LastApiInfo { get { return _gitHubClient.Connection.LastApiInfo; } }
}
}
32 changes: 32 additions & 0 deletions Octokit.Tests.Integration/Clients/GitHubClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Octokit.Tests.Integration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

public class GitHubClientTests
{
public class TheLastApiInfoProperty
{
[IntegrationTest]
public async Task CanRetrieveLastApiInfo()
{
var github = Helper.GetAuthenticatedClient();

// Doesn't matter which API gets called
await github.Miscellaneous.GetRateLimits();

var result = github.LastApiInfo;

//Assert.True(result.Links.Count > 0);
//Assert.True(result.AcceptedOauthScopes.Count > 0);
//Assert.True(result.OauthScopes.Count > 0);
//Assert.False(String.IsNullOrEmpty(result.Etag));
Assert.True(result.RateLimit.Limit > 0);
Assert.True(result.RateLimit.Remaining > -1);
Assert.NotNull(result.RateLimit.Reset);
}
}
}
1 change: 1 addition & 0 deletions Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
<Compile Include="Clients\AuthorizationClientTests.cs" />
<Compile Include="Clients\BlobClientTests.cs" />
<Compile Include="Clients\BranchesClientTests.cs" />
<Compile Include="Clients\GitHubClientTests.cs" />
<Compile Include="Clients\MergingClientTests.cs" />
<Compile Include="Clients\CommitsClientTests.cs" />
<Compile Include="Clients\CommitStatusClientTests.cs" />
Expand Down
67 changes: 67 additions & 0 deletions Octokit.Tests/GitHubClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Octokit.Internal;
using Xunit;
using Xunit.Extensions;
using System.Collections.Generic;

namespace Octokit.Tests
{
Expand Down Expand Up @@ -104,5 +105,71 @@ public void IsRetrievedFromCredentialStore()
Assert.Equal("bar", client.Credentials.Password);
}
}

public class TheLastApiInfoProperty
{
[Fact]
public async Task ReturnsNullIfNew()
{
var connection = Substitute.For<IConnection>();
connection.LastApiInfo.Returns((ApiInfo)null);
var client = new GitHubClient(connection);

var result = client.LastApiInfo;

Assert.Null(result);

var temp = connection.Received(1).LastApiInfo;
}

[Fact]
public async Task ReturnsObjectIfNotNew()
{
var apiInfo = new ApiInfo(
new Dictionary<string, Uri>
{
{
"next",
new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5")
},
{
"last",
new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5")
},
{
"first",
new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5")
},
{
"prev",
new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5")
}
},
new List<string>
{
"user",
},
new List<string>
{
"user",
"public_repo",
"repo",
"gist"
},
"5634b0b187fd2e91e3126a75006cc4fa",
new RateLimit(100, 75, 1372700873)
);
var connection = Substitute.For<IConnection>();
connection.LastApiInfo.Returns(apiInfo);
var client = new GitHubClient(connection);

var result = client.LastApiInfo;

Assert.NotNull(result);

var temp = connection.Received(1).LastApiInfo;
}
}

}
}
80 changes: 80 additions & 0 deletions Octokit.Tests/Http/ConnectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -564,5 +564,85 @@ public void CreatesConnectionWithBaseAddress()
Assert.True(connection.UserAgent.StartsWith("OctokitTests ("));
}
}

public class TheLastAPiInfoProperty
{
[Fact]
public async Task ReturnsNullIfNew()
{
var httpClient = Substitute.For<IHttpClient>();
httpClient.LastApiInfo.Returns((ApiInfo)null);
var connection = new Connection(new ProductHeaderValue("OctokitTests"),
_exampleUri,
Substitute.For<ICredentialStore>(),
httpClient,
Substitute.For<IJsonSerializer>());

var result = connection.LastApiInfo;

Assert.Null(result);

var temp = httpClient.Received(1).LastApiInfo;
}

[Fact]
public async Task ReturnsObjectIfNotNew()
{
var apiInfo = new ApiInfo(
new Dictionary<string, Uri>
{
{
"next",
new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5")
},
{
"last",
new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5")
},
{
"first",
new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5")
},
{
"prev",
new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5")
}
},
new List<string>
{
"user",
},
new List<string>
{
"user",
"public_repo",
"repo",
"gist"
},
"5634b0b187fd2e91e3126a75006cc4fa",
new RateLimit(100, 75, 1372700873)
);

var httpClient = Substitute.For<IHttpClient>();
httpClient.LastApiInfo.Returns(apiInfo);
var connection = new Connection(new ProductHeaderValue("OctokitTests"),
_exampleUri,
Substitute.For<ICredentialStore>(),
httpClient,
Substitute.For<IJsonSerializer>());

var result = connection.LastApiInfo;

// No point checking all of the values as they are tested elsewhere
// Just provde that the ApiInfo is populated
Assert.Equal(4, result.Links.Count);
Assert.Equal(1, result.OauthScopes.Count);
Assert.Equal(4, result.AcceptedOauthScopes.Count);
Assert.Equal("5634b0b187fd2e91e3126a75006cc4fa", result.Etag);
Assert.Equal(100, result.RateLimit.Limit);

var temp = httpClient.Received(1).LastApiInfo;
}
}
}
}
6 changes: 6 additions & 0 deletions Octokit/GitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ public GitHubClient(IConnection connection)
Deployment = new DeploymentsClient(apiConnection);
}

/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>
/// <returns><seealso cref="ApiInfo"/> representing the information returned as part of an Api call</returns>
public ApiInfo LastApiInfo { get { return Connection.LastApiInfo; } }

/// <summary>
/// Convenience property for getting and setting credentials.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Octokit/Http/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ public Connection(
_jsonPipeline = new JsonHttpPipeline();
}

/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>
/// <returns><seealso cref="ApiInfo"/> representing the information returned as part of an Api call</returns>
public ApiInfo LastApiInfo { get { return _httpClient.LastApiInfo; } }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khellang I've added this for the thread safety. Thoughts welcome

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do we actually need locks here? If someone is reading the value just as another thread is writing the value, it sort of doesn't matter who wins. After all, the read value will only be slightly outdated.

In other words, I don't see any problems with race conditions in this case as this isn't data that's timing sensitive. I don't think torn reads are a problem because these are 32 bit references.

This value is going to be set on every request, I'd rather avoid a lock here unless it's absolutely needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@khellang @haacked I'm easy either way - with locking/ without

What's the preferred method of resolving coding decisions? A well argued debate between respectful & collaborate peers? Rocks/ Paper/ Scissors? A fight to the death?

(is it wrong to hope for the last one)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been taking the Benevolent Dictator role. 😛

I'd like to hear from @khellang if there are specific concerns with removing the locks. If not, let's remove them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Off topic)

Please tell me someone out there is photoshoping the below - replacing with @haacked and the Octocat (not saying which way round)

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to remove the locks, it's probably a bit too much. I just brought it up because there's now some shared, mutable state going on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Let's remove the locks, but make sure we comment it and explain why so if someone else comes along, they understand why there's shared mutable state.

Also, one idea we should do is to set LastApiInfo to a copy of the ApiInfo to reduce the sharing of mutable state. In fact, I think LastApiInfo should be a method GetLastApiInfo and it should always return a copy of the last ApiInfo

That way, Octokit is in control of the lifetime of that object and a consumer can't inadvertently keep the object around longer than expected. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM 👍

public Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts)
{
Ensure.ArgumentNotNull(uri, "uri");
Expand Down
12 changes: 11 additions & 1 deletion Octokit/Http/HttpClientAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public HttpClientAdapter(Func<HttpMessageHandler> getHandler)
_http = new HttpClient(new RedirectHandler { InnerHandler = getHandler() });
}

/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>
/// <returns><seealso cref="ApiInfo"/> representing the information returned as part of an Api call</returns>
public ApiInfo LastApiInfo { get; private set; }

/// <summary>
/// Sends the specified request and returns a response.
/// </summary>
Expand All @@ -45,7 +51,11 @@ public async Task<IResponse> Send(IRequest request, CancellationToken cancellati
// Make the request
var responseMessage = await _http.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationTokenForRequest)
.ConfigureAwait(false);
return await BuildResponse(responseMessage).ConfigureAwait(false);
var response = await BuildResponse(responseMessage).ConfigureAwait(false);

LastApiInfo = response.ApiInfo;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not have to be set here and IMO it's not a HTTP concern at all. All requests go trough Connection.RunRequest so I think it would be better to move this up a level.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what about threading? Should there be a lock introduced here?


return response;
}
}

Expand Down
20 changes: 20 additions & 0 deletions Octokit/Http/IApiInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Octokit
{
/// <summary>
/// Provides a property for the Last recorded API infomation
/// </summary>
public interface IApiInfo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like an interface for the ApiInfo itself. What about IApiInfoProvider, it seems to be more in line with the comments as well?

{
/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>
/// <returns><seealso cref="ApiInfo"/> representing the information returned as part of an Api call</returns>
ApiInfo LastApiInfo { get; }
}
}
2 changes: 1 addition & 1 deletion Octokit/Http/IConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Octokit
/// <summary>
/// A connection for making HTTP requests against URI endpoints.
/// </summary>
public interface IConnection
public interface IConnection : IApiInfo
{
/// <summary>
/// Performs an asynchronous HTTP GET request that expects a <seealso cref="IResponse"/> containing HTML.
Expand Down
2 changes: 1 addition & 1 deletion Octokit/Http/IHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Octokit.Internal
/// <remarks>
/// Most folks won't ever need to swap this out. But if you're trying to run this on Windows Phone, you might.
/// </remarks>
public interface IHttpClient : IDisposable
public interface IHttpClient : IDisposable, IApiInfo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not feel right. API info is not a HTTP concern (see previous comment for a potention solution).

{
/// <summary>
/// Sends the specified request and returns a response.
Expand Down
2 changes: 1 addition & 1 deletion Octokit/IGitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Octokit
/// <summary>
/// A Client for the GitHub API v3. You can read more about the api here: http://developer.github.com.
/// </summary>
public interface IGitHubClient
public interface IGitHubClient : IApiInfo
{
/// <summary>
/// Provides a client connection to make rest requests to HTTP endpoints.
Expand Down
1 change: 1 addition & 0 deletions Octokit/Octokit-Mono.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@
<Compile Include="Models\Response\ResourceRateLimit.cs" />
<Compile Include="Models\Response\MiscellaneousRateLimit.cs" />
<Compile Include="Exceptions\RepositoryFormatException.cs" />
<Compile Include="Http\IApiInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
3 changes: 2 additions & 1 deletion Octokit/Octokit-MonoAndroid.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Expand Down Expand Up @@ -415,6 +415,7 @@
<Compile Include="Models\Response\ResourceRateLimit.cs" />
<Compile Include="Models\Response\MiscellaneousRateLimit.cs" />
<Compile Include="Exceptions\RepositoryFormatException.cs" />
<Compile Include="Http\IApiInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
</Project>
3 changes: 2 additions & 1 deletion Octokit/Octokit-Monotouch.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Expand Down Expand Up @@ -408,6 +408,7 @@
<Compile Include="Models\Response\ResourceRateLimit.cs" />
<Compile Include="Models\Response\MiscellaneousRateLimit.cs" />
<Compile Include="Exceptions\RepositoryFormatException.cs" />
<Compile Include="Http\IApiInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.MonoTouch.CSharp.targets" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
Expand Down
1 change: 1 addition & 0 deletions Octokit/Octokit-Portable.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@
<Compile Include="Models\Response\ResourceRateLimit.cs" />
<Compile Include="Models\Response\MiscellaneousRateLimit.cs" />
<Compile Include="Exceptions\RepositoryFormatException.cs" />
<Compile Include="Http\IApiInfo.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml">
Expand Down
1 change: 1 addition & 0 deletions Octokit/Octokit-netcore45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@
<Compile Include="Models\Response\ResourceRateLimit.cs" />
<Compile Include="Models\Response\MiscellaneousRateLimit.cs" />
<Compile Include="Exceptions\RepositoryFormatException.cs" />
<Compile Include="Http\IApiInfo.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml">
Expand Down
1 change: 1 addition & 0 deletions Octokit/Octokit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<Compile Include="Helpers\PropertyOrField.cs" />
<Compile Include="Helpers\SerializeNullAttribute.cs" />
<Compile Include="Http\HttpMessageHandlerFactory.cs" />
<Compile Include="Http\IApiInfo.cs" />
<Compile Include="Http\ProductHeaderValue.cs" />
<Compile Include="Models\Request\GistFileUpdate.cs" />
<Compile Include="Models\Request\NewMerge.cs" />
Expand Down