From 958bc5f1f87cd97cd8520ac74f958c2df12903b3 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:16:41 +0100 Subject: [PATCH] [feat]: SDKs for ActionsArtifacts APIs --- .../IObservableActionsArtifactsClient.cs | 44 ++++++++- .../ObservableActionsArtifactsClient.cs | 37 ++++++- Octokit.Tests/Http/HttpClientAdapterTests.cs | 8 +- Octokit.Tests/Models/ArtifactsTests.cs | 98 +++++++++++++++++++ Octokit/Caching/CachedResponse.cs | 4 +- Octokit/Clients/ActionsArtifactsClient.cs | 60 +++++++++++- Octokit/Clients/IActionsArtifactsClient.cs | 46 ++++++++- Octokit/Helpers/ApiUrls.cs | 48 +++++++++ Octokit/Helpers/Ensure.cs | 12 +++ Octokit/Http/ApiConnection.cs | 10 ++ Octokit/Http/Connection.cs | 39 +++++++- Octokit/Http/HttpClientAdapter.cs | 29 +++--- Octokit/Http/IApiConnection.cs | 10 ++ Octokit/Http/IConnection.cs | 10 ++ Octokit/Http/IResponse.cs | 2 +- Octokit/Http/Response.cs | 4 +- .../Models/Request/ListArtifactsRequest.cs | 32 ++++++ Octokit/Models/Response/Artifact.cs | 83 ++++++++++++++++ .../Models/Response/ArtifactWorkflowRun.cs | 46 +++++++++ .../Models/Response/ListArtifactsResponse.cs | 29 ++++++ 20 files changed, 621 insertions(+), 30 deletions(-) create mode 100644 Octokit.Tests/Models/ArtifactsTests.cs create mode 100644 Octokit/Models/Request/ListArtifactsRequest.cs create mode 100644 Octokit/Models/Response/Artifact.cs create mode 100644 Octokit/Models/Response/ArtifactWorkflowRun.cs create mode 100644 Octokit/Models/Response/ListArtifactsResponse.cs diff --git a/Octokit.Reactive/Clients/IObservableActionsArtifactsClient.cs b/Octokit.Reactive/Clients/IObservableActionsArtifactsClient.cs index 38e6a34a9c..7962035272 100644 --- a/Octokit.Reactive/Clients/IObservableActionsArtifactsClient.cs +++ b/Octokit.Reactive/Clients/IObservableActionsArtifactsClient.cs @@ -1,4 +1,8 @@ -namespace Octokit.Reactive +using System; +using System.IO; +using System.Reactive; + +namespace Octokit.Reactive { /// /// A client for GitHub's Actions Artifacts API. @@ -8,5 +12,43 @@ /// public interface IObservableActionsArtifactsClient { + IObservable ListArtifacts(string owner, string repository, ListArtifactsRequest listArtifactsRequest = null); + + /// + /// Gets the specified artifact + /// + /// + /// + /// + /// + IObservable GetArtifact(string owner, string repository, long artifactId); + + /// + /// Deletes the specified artifact + /// + /// + /// + /// + /// + IObservable DeleteArtifact(string owner, string repository, long artifactId); + + /// + /// Downloads the specified artifact's contents + /// + /// + /// + /// + /// + /// + IObservable DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat); + + /// + /// Lists the artifacts for a specific workflow run + /// + /// + /// + /// + /// + IObservable ListWorkflowArtifacts(string owner, string repository, long runId, ListArtifactsRequest listArtifactsRequest = null); } } diff --git a/Octokit.Reactive/Clients/ObservableActionsArtifactsClient.cs b/Octokit.Reactive/Clients/ObservableActionsArtifactsClient.cs index 9f152cbd11..5358992e32 100644 --- a/Octokit.Reactive/Clients/ObservableActionsArtifactsClient.cs +++ b/Octokit.Reactive/Clients/ObservableActionsArtifactsClient.cs @@ -1,4 +1,9 @@ -namespace Octokit.Reactive +using System; +using System.IO; +using System.Reactive; +using System.Reactive.Threading.Tasks; + +namespace Octokit.Reactive { public class ObservableActionsArtifactsClient : IObservableActionsArtifactsClient { @@ -14,5 +19,35 @@ public ObservableActionsArtifactsClient(IGitHubClient client) _client = client.Actions.Artifacts; } + + /// + public IObservable ListArtifacts(string owner, string repository, ListArtifactsRequest listArtifactsRequest = null) + { + return _client.ListArtifacts(owner, repository, listArtifactsRequest).ToObservable(); + } + + /// + public IObservable GetArtifact(string owner, string repository, long artifactId) + { + return _client.GetArtifact(owner, repository, artifactId).ToObservable(); + } + + /// + public IObservable DeleteArtifact(string owner, string repository, long artifactId) + { + return _client.DeleteArtifact(owner, repository, artifactId).ToObservable(); + } + + /// + public IObservable DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat) + { + return _client.DownloadArtifact(owner, repository, artifactId, archiveFormat).ToObservable(); + } + + /// + public IObservable ListWorkflowArtifacts(string owner, string repository, long runId, ListArtifactsRequest listArtifactsRequest = null) + { + return _client.ListWorkflowArtifacts(owner, repository, runId, listArtifactsRequest).ToObservable(); + } } } diff --git a/Octokit.Tests/Http/HttpClientAdapterTests.cs b/Octokit.Tests/Http/HttpClientAdapterTests.cs index 6a41c30f68..8d1c570b19 100644 --- a/Octokit.Tests/Http/HttpClientAdapterTests.cs +++ b/Octokit.Tests/Http/HttpClientAdapterTests.cs @@ -143,9 +143,9 @@ public async Task BuildsResponseFromResponseMessage(HttpStatusCode httpStatusCod Assert.Equal(httpStatusCode, response.StatusCode); Assert.Null(response.ContentType); } - + [Fact] - public async Task BuildsByteArrayResponseFromResponseMessage() + public async Task BuildsStreamResponseFromResponseMessage() { var responseMessage = new HttpResponseMessage { @@ -157,7 +157,9 @@ public async Task BuildsByteArrayResponseFromResponseMessage() var response = await tester.BuildResponseTester(responseMessage); - Assert.Equal(new byte[] { 0, 1, 1, 0, 1 }, response.Body); + var memoryStream = Assert.IsType(response.Body); + + Assert.Equal(new byte[] { 0, 1, 1, 0, 1 }, memoryStream.ToArray()); Assert.Equal("image/png", response.ContentType); } diff --git a/Octokit.Tests/Models/ArtifactsTests.cs b/Octokit.Tests/Models/ArtifactsTests.cs new file mode 100644 index 0000000000..3cb1cff7a1 --- /dev/null +++ b/Octokit.Tests/Models/ArtifactsTests.cs @@ -0,0 +1,98 @@ +using System; +using Octokit.Internal; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class ArtifactsTests + { + [Fact] + public void CanBeDeserialized() + { + var json = @"{ + ""total_count"": 2, + ""artifacts"": [ + { + ""id"": 11, + ""node_id"": ""MDg6QXJ0aWZhY3QxMQ=="", + ""name"": ""Rails"", + ""size_in_bytes"": 556, + ""url"": ""https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/11"", + ""archive_download_url"": ""https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/11/zip"", + ""expired"": false, + ""created_at"": ""2020-01-10T14:59:22Z"", + ""expires_at"": ""2020-03-21T14:59:22Z"", + ""updated_at"": ""2020-02-21T14:59:22Z"", + ""workflow_run"": { + ""id"": 2332938, + ""repository_id"": 1296269, + ""head_repository_id"": 1296269, + ""head_branch"": ""main"", + ""head_sha"": ""328faa0536e6fef19753d9d91dc96a9931694ce3"" + } + }, + { + ""id"": 13, + ""node_id"": ""MDg6QXJ0aWZhY3QxMw=="", + ""name"": ""Test output"", + ""size_in_bytes"": 453, + ""url"": ""https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/13"", + ""archive_download_url"": ""https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/13/zip"", + ""expired"": false, + ""created_at"": ""2020-01-10T14:59:22Z"", + ""expires_at"": ""2020-03-21T14:59:22Z"", + ""updated_at"": ""2020-02-21T14:59:22Z"", + ""workflow_run"": { + ""id"": 2332942, + ""repository_id"": 1296269, + ""head_repository_id"": 1296269, + ""head_branch"": ""main"", + ""head_sha"": ""178f4f6090b3fccad4a65b3e83d076a622d59652"" + } + } + ] +}"; + + var serializer = new SimpleJsonSerializer(); + + var payload = serializer.Deserialize(json); + + Assert.NotNull(payload); + + Assert.Equal(2, payload.TotalCount); + Assert.Equal(2, payload.Artifacts.Count); + + Assert.Equal(11, payload.Artifacts[0].Id); + Assert.Equal("MDg6QXJ0aWZhY3QxMQ==", payload.Artifacts[0].NodeId); + Assert.Equal("Rails", payload.Artifacts[0].Name); + Assert.Equal(556, payload.Artifacts[0].SizeInBytes); + Assert.Equal("https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/11", payload.Artifacts[0].Url); + Assert.Equal("https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/11/zip", payload.Artifacts[0].ArchiveDownloadUrl); + Assert.False(payload.Artifacts[0].Expired); + Assert.Equal(new DateTime(2020, 1, 10, 14, 59, 22, DateTimeKind.Utc), payload.Artifacts[0].CreatedAt); + Assert.Equal(new DateTime(2020, 3, 21, 14, 59, 22, DateTimeKind.Utc), payload.Artifacts[0].ExpiresAt); + Assert.Equal(new DateTime(2020, 2, 21, 14, 59, 22, DateTimeKind.Utc), payload.Artifacts[0].UpdatedAt); + Assert.Equal(2332938, payload.Artifacts[0].WorkflowRun.Id); + Assert.Equal(1296269, payload.Artifacts[0].WorkflowRun.RepositoryId); + Assert.Equal(1296269, payload.Artifacts[0].WorkflowRun.HeadRepositoryId); + Assert.Equal("main", payload.Artifacts[0].WorkflowRun.HeadBranch); + Assert.Equal("328faa0536e6fef19753d9d91dc96a9931694ce3", payload.Artifacts[0].WorkflowRun.HeadSha); + + Assert.Equal(13, payload.Artifacts[1].Id); + Assert.Equal("MDg6QXJ0aWZhY3QxMw==", payload.Artifacts[1].NodeId); + Assert.Equal("Test output", payload.Artifacts[1].Name); + Assert.Equal(453, payload.Artifacts[1].SizeInBytes); + Assert.Equal("https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/13", payload.Artifacts[1].Url); + Assert.Equal("https://api.github.com/repos/octo-org/octo-docs/actions/artifacts/13/zip", payload.Artifacts[1].ArchiveDownloadUrl); + Assert.False(payload.Artifacts[1].Expired); + Assert.Equal(new DateTime(2020, 1, 10, 14, 59, 22, DateTimeKind.Utc), payload.Artifacts[1].CreatedAt); + Assert.Equal(new DateTime(2020, 3, 21, 14, 59, 22, DateTimeKind.Utc), payload.Artifacts[1].ExpiresAt); + Assert.Equal(new DateTime(2020, 2, 21, 14, 59, 22, DateTimeKind.Utc), payload.Artifacts[1].UpdatedAt); + Assert.Equal(2332942, payload.Artifacts[1].WorkflowRun.Id); + Assert.Equal(1296269, payload.Artifacts[1].WorkflowRun.RepositoryId); + Assert.Equal(1296269, payload.Artifacts[1].WorkflowRun.HeadRepositoryId); + Assert.Equal("main", payload.Artifacts[1].WorkflowRun.HeadBranch); + Assert.Equal("178f4f6090b3fccad4a65b3e83d076a622d59652", payload.Artifacts[1].WorkflowRun.HeadSha); + } + } +} diff --git a/Octokit/Caching/CachedResponse.cs b/Octokit/Caching/CachedResponse.cs index 4d63371aff..0b2328de96 100644 --- a/Octokit/Caching/CachedResponse.cs +++ b/Octokit/Caching/CachedResponse.cs @@ -28,9 +28,7 @@ public V1(object body, IReadOnlyDictionary headers, ApiInfo apiI ContentType = contentType; } - /// - /// Raw response body. Typically a string, but when requesting images, it will be a byte array. - /// + /// public object Body { get; private set; } /// /// Information about the API. diff --git a/Octokit/Clients/ActionsArtifactsClient.cs b/Octokit/Clients/ActionsArtifactsClient.cs index cc57d0c433..96f8967d42 100644 --- a/Octokit/Clients/ActionsArtifactsClient.cs +++ b/Octokit/Clients/ActionsArtifactsClient.cs @@ -1,4 +1,7 @@ -namespace Octokit +using System.IO; +using System.Threading.Tasks; + +namespace Octokit { /// /// A client for GitHub's Actions Artifacts API. @@ -15,5 +18,60 @@ public class ActionsArtifactsClient : ApiClient, IActionsArtifactsClient public ActionsArtifactsClient(IApiConnection apiConnection) : base(apiConnection) { } + + /// + [ManualRoute("GET", "/repos/{owner}/{repository}/actions/artifacts")] + public Task ListArtifacts(string owner, string repository, ListArtifactsRequest listArtifactsRequest = null) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository)); + + return ApiConnection.Get(ApiUrls.ListArtifacts(owner, repository), listArtifactsRequest?.ToParametersDictionary()); + } + + /// + [ManualRoute("GET", "/repos/{owner}/{repository}/actions/artifacts/{artifact_id}")] + public Task GetArtifact(string owner, string repository, long artifactId) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository)); + Ensure.ArgumentNotNullOrDefault(artifactId, nameof(artifactId)); + + return ApiConnection.Get(ApiUrls.Artifact(owner, repository, artifactId), null); + } + + /// + [ManualRoute("DELETE", "/repos/{owner}/{repository}/actions/artifacts/{artifact_id}")] + public Task DeleteArtifact(string owner, string repository, long artifactId) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository)); + Ensure.ArgumentNotNullOrDefault(artifactId, nameof(artifactId)); + + return ApiConnection.Delete(ApiUrls.Artifact(owner, repository, artifactId), null); + } + + /// + [ManualRoute("GET", "/repos/{owner}/{repository}/actions/artifacts/{artifact_id}/{archive_format}")] + public Task DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository)); + Ensure.ArgumentNotNullOrDefault(artifactId, nameof(artifactId)); + Ensure.ArgumentNotNullOrEmptyString(repository, nameof(archiveFormat)); + + return ApiConnection.GetRawStream(ApiUrls.DownloadArtifact(owner, repository, artifactId, archiveFormat), null); + } + + /// + [ManualRoute("GET", "/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts")] + public Task ListWorkflowArtifacts(string owner, string repository, long runId, ListArtifactsRequest listArtifactsRequest = null) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repository, nameof(repository)); + Ensure.ArgumentNotNullOrDefault(runId, nameof(runId)); + + return ApiConnection.Get(ApiUrls.ListWorkflowArtifacts(owner, repository, runId), listArtifactsRequest?.ToParametersDictionary()); + } } } diff --git a/Octokit/Clients/IActionsArtifactsClient.cs b/Octokit/Clients/IActionsArtifactsClient.cs index 9905bad9cb..1ce86f2c94 100644 --- a/Octokit/Clients/IActionsArtifactsClient.cs +++ b/Octokit/Clients/IActionsArtifactsClient.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; namespace Octokit @@ -11,5 +11,49 @@ namespace Octokit /// public interface IActionsArtifactsClient { + /// + /// Lists artifacts for a repository + /// + /// + /// + /// + Task ListArtifacts(string owner, string repository, ListArtifactsRequest listArtifactsRequest = null); + + /// + /// Gets the specified artifact + /// + /// + /// + /// + /// + Task GetArtifact(string owner, string repository, long artifactId); + + /// + /// Deletes the specified artifact + /// + /// + /// + /// + /// + Task DeleteArtifact(string owner, string repository, long artifactId); + + /// + /// Downloads the specified artifact's contents + /// + /// + /// + /// + /// + /// + Task DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat); + + /// + /// Lists the artifacts for a specific workflow run + /// + /// + /// + /// + /// + Task ListWorkflowArtifacts(string owner, string repository, long runId, ListArtifactsRequest listArtifactsRequest = null); } } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 20765f96cc..62ea8140a0 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -5472,5 +5472,53 @@ public static Uri CodespaceStop(string codespaceName) { return "user/codespaces/{0}/stop".FormatUri(codespaceName); } + + /// + /// Returns the that lists the artifacts for a repository. + /// + /// The owner of the repository + /// The name of the repository + /// + public static Uri ListArtifacts(string owner, string repository) + { + return "repos/{0}/{1}/actions/artifacts".FormatUri(owner, repository); + } + + /// + /// Returns the for the specified artifact. + /// + /// The owner of the repository + /// The name of the repository + /// The id of the artifact + /// + public static Uri Artifact(string owner, string repository, long artifactId) + { + return "repos/{0}/{1}/actions/artifacts/{2}".FormatUri(owner, repository, artifactId); + } + + /// + /// Returns the to download the specified artifact. + /// + /// The owner of the repository + /// The name of the repository + /// The id of the artifact + /// The archive format e.g. zip + /// + public static Uri DownloadArtifact(string owner, string repository, long artifactId, string archiveFormat) + { + return "repos/{0}/{1}/actions/artifacts/{2}/{3}".FormatUri(owner, repository, artifactId, archiveFormat); + } + + /// + /// Returns the to list the artifacts for a workflow. + /// + /// The owner of the repository + /// The name of the repository + /// The id of the workflow run + /// + public static Uri ListWorkflowArtifacts(string owner, string repository, long runId) + { + return "repos/{0}/{1}/actions/runs/{2}/artifacts".FormatUri(owner, repository, runId); + } } } diff --git a/Octokit/Helpers/Ensure.cs b/Octokit/Helpers/Ensure.cs index 77204d9531..b028156ceb 100644 --- a/Octokit/Helpers/Ensure.cs +++ b/Octokit/Helpers/Ensure.cs @@ -21,6 +21,18 @@ public static void ArgumentNotNull([ValidatedNotNull]object value, string name) throw new ArgumentNullException(name); } + + /// + /// Checks an argument to ensure it isn't null or the default value. + /// + /// The argument value to check + /// The name of the argument + public static void ArgumentNotNullOrDefault([ValidatedNotNull]T value, string name) + { + if (value != null && !EqualityComparer.Default.Equals(value, default)) return; + + throw new ArgumentNullException(name); + } /// /// Checks a string argument to ensure it isn't null or empty. diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index e590a37ac5..9d729947e9 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -138,6 +139,15 @@ public async Task GetRaw(Uri uri, IDictionary parameters var response = await Connection.GetRaw(uri, parameters).ConfigureAwait(false); return response.Body; } + + /// + public async Task GetRawStream(Uri uri, IDictionary parameters) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + + var response = await Connection.GetRawStream(uri, parameters).ConfigureAwait(false); + return response.Body; + } /// /// Gets all API resources in the list at the specified URI. diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 46eeadcc28..c0fc81f4ee 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -241,6 +242,19 @@ public Task> GetRaw(Uri uri, IDictionary pa Endpoint = uri.ApplyParameters(parameters) }); } + + /// + public Task> GetRawStream(Uri uri, IDictionary parameters) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + + return GetRawStream(new Request + { + Method = HttpMethod.Get, + BaseAddress = BaseAddress, + Endpoint = uri.ApplyParameters(parameters) + }); + } public Task> Patch(Uri uri, object body) { @@ -686,7 +700,30 @@ async Task> GetRaw(IRequest request) { request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType); var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false); - return new ApiResponse(response, response.Body as byte[]); + + return new ApiResponse(response, await StreamToByteArray(response.Body as Stream)); + } + + async Task> GetRawStream(IRequest request) + { + request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType); + var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false); + + return new ApiResponse(response, response.Body as Stream); + } + + async Task StreamToByteArray(Stream stream) + { + if (stream is MemoryStream memoryStream) + { + return memoryStream.ToArray(); + } + + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms); + return ms.ToArray(); + } } async Task> Run(IRequest request, CancellationToken cancellationToken, Func preprocessResponseBody = null) diff --git a/Octokit/Http/HttpClientAdapter.cs b/Octokit/Http/HttpClientAdapter.cs index 6e0b8354d3..0e54074dff 100644 --- a/Octokit/Http/HttpClientAdapter.cs +++ b/Octokit/Http/HttpClientAdapter.cs @@ -83,25 +83,24 @@ protected virtual async Task BuildResponse(HttpResponseMessage respon "application/x-gzip" , "application/octet-stream"}; - using (var content = responseMessage.Content) + var content = responseMessage.Content; + if (content != null) { - if (content != null) - { - contentType = GetContentMediaType(responseMessage.Content); + contentType = GetContentMediaType(content); - if (contentType != null && (contentType.StartsWith("image/") || binaryContentTypes + if (contentType != null && (contentType.StartsWith("image/") || binaryContentTypes .Any(item => item.Equals(contentType, StringComparison.OrdinalIgnoreCase)))) - { - responseBody = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - } - else - { - responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - } - - if (!(preprocessResponseBody is null)) - responseBody = preprocessResponseBody(responseBody); + { + responseBody = await content.ReadAsStreamAsync().ConfigureAwait(false); } + else + { + responseBody = await content.ReadAsStringAsync().ConfigureAwait(false); + content.Dispose(); + } + + if (!(preprocessResponseBody is null)) + responseBody = preprocessResponseBody(responseBody); } var responseHeaders = responseMessage.Headers.ToDictionary(h => h.Key, h => h.Value.First()); diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index d6e60b3985..da4a541f1c 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -71,6 +72,15 @@ public interface IApiConnection /// Thrown when an API error occurs. Task GetRaw(Uri uri, IDictionary parameters); + /// + /// Gets the raw stream of the API resource at the specified URI. + /// + /// URI of the API resource to get + /// Parameters to add to the API request + /// The API resource's raw stream or null if the points to a directory. + /// Thrown when an API error occurs. + Task GetRawStream(Uri uri, IDictionary parameters); + /// /// Gets all API resources in the list at the specified URI. /// diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 2696d8ee4e..8301b64c0d 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -29,6 +30,15 @@ public interface IConnection : IApiInfoProvider /// representing the received HTTP response /// The property will be null if the points to a directory instead of a file Task> GetRaw(Uri uri, IDictionary parameters); + + /// + /// Performs an asynchronous HTTP GET request that expects a containing raw data. + /// + /// URI endpoint to send request to + /// Querystring parameters for the request + /// representing the received HTTP response + /// The property will be null if the points to a directory instead of a file + Task> GetRawStream(Uri uri, IDictionary parameters); /// /// Performs an asynchronous HTTP GET request. diff --git a/Octokit/Http/IResponse.cs b/Octokit/Http/IResponse.cs index fc10d83630..716d62c08a 100644 --- a/Octokit/Http/IResponse.cs +++ b/Octokit/Http/IResponse.cs @@ -25,7 +25,7 @@ public interface IApiResponse public interface IResponse { /// - /// Raw response body. Typically a string, but when requesting images, it will be a byte array. + /// Raw response body. Typically a string, but when requesting images, or binary files, it will be a Stream. /// object Body { get; } diff --git a/Octokit/Http/Response.cs b/Octokit/Http/Response.cs index 49c99912db..541d6d8281 100644 --- a/Octokit/Http/Response.cs +++ b/Octokit/Http/Response.cs @@ -35,9 +35,7 @@ public Response(HttpStatusCode statusCode, object body, IDictionary - /// Raw response body. Typically a string, but when requesting images, it will be a byte array. - /// + /// public object Body { get; private set; } /// /// Information about the API. diff --git a/Octokit/Models/Request/ListArtifactsRequest.cs b/Octokit/Models/Request/ListArtifactsRequest.cs new file mode 100644 index 0000000000..eaf6095851 --- /dev/null +++ b/Octokit/Models/Request/ListArtifactsRequest.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + /// + /// Used to filter requests for lists of artifacts. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class ListArtifactsRequest : RequestParameters + { + /// + /// Filter artifacts by name. + /// + public string Name { get; set; } + + /// + /// How many results to return per page (maximum 100). + /// + [Parameter(Key = "per_page")] + public int PerPage { get; set; } = 30; + + /// + /// What page to retrieve. + /// + [Parameter(Key = "page")] + public int Page { get; set; } = 1; + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Page: {0}, PerPage: {1} ", Page, PerPage); + } +} diff --git a/Octokit/Models/Response/Artifact.cs b/Octokit/Models/Response/Artifact.cs new file mode 100644 index 0000000000..1f6be53771 --- /dev/null +++ b/Octokit/Models/Response/Artifact.cs @@ -0,0 +1,83 @@ +using System; +using System.Diagnostics; +using System.Globalization; + +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public class Artifact +{ + public Artifact() + { + } + + public Artifact(long id, string nodeId, string name, int sizeInBytes, string url, string archiveDownloadUrl, bool expired, DateTime createdAt, DateTime expiresAt, DateTime updatedAt, ArtifactWorkflowRun workflowRun) + { + Id = id; + NodeId = nodeId; + Name = name; + SizeInBytes = sizeInBytes; + Url = url; + ArchiveDownloadUrl = archiveDownloadUrl; + Expired = expired; + CreatedAt = createdAt; + ExpiresAt = expiresAt; + UpdatedAt = updatedAt; + WorkflowRun = workflowRun; + } + + /// + /// The artifact Id + /// + public long Id { get; private set; } + + /// + /// The artifact node Id + /// + public string NodeId { get; private set; } + + /// + /// The name of the artifact + /// + public string Name { get; private set; } + + /// + /// The size of the artifact in bytes + /// + public int SizeInBytes { get; private set; } + + /// + /// The url for retrieving the artifact information + /// + public string Url { get; private set; } + + /// + /// The url for downloading the artifact contents + /// + public string ArchiveDownloadUrl { get; private set; } + + /// + /// True if the artifact has expired + /// + public bool Expired { get; private set; } + + /// + /// The date and time when the artifact was created + /// + public DateTime CreatedAt { get; private set; } + + /// + /// The date and time when the artifact expires + /// + public DateTime ExpiresAt { get; private set; } + + /// + /// The date and time when the artifact was last updated + /// + public DateTime UpdatedAt { get; private set; } + + /// + /// The workflow from where the artifact was created + /// + public ArtifactWorkflowRun WorkflowRun { get; private set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Id: {0}", Id); +} \ No newline at end of file diff --git a/Octokit/Models/Response/ArtifactWorkflowRun.cs b/Octokit/Models/Response/ArtifactWorkflowRun.cs new file mode 100644 index 0000000000..a39a1b6a34 --- /dev/null +++ b/Octokit/Models/Response/ArtifactWorkflowRun.cs @@ -0,0 +1,46 @@ +using System.Diagnostics; +using System.Globalization; + +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public class ArtifactWorkflowRun +{ + public ArtifactWorkflowRun() + { + } + + public ArtifactWorkflowRun(long id, long repositoryId, long headRepositoryId, string headBranch, string headSha) + { + Id = id; + RepositoryId = repositoryId; + HeadRepositoryId = headRepositoryId; + HeadBranch = headBranch; + HeadSha = headSha; + } + + /// + /// The workflow run Id + /// + public long Id { get; private set; } + + /// + /// The repository Id + /// + public long RepositoryId { get; private set; } + + /// + /// The head repository Id + /// + public long HeadRepositoryId { get; private set; } + + /// + /// The head branch + /// + public string HeadBranch { get; private set; } + + /// + /// The head Sha + /// + public string HeadSha { get; private set; } + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Id: {0}", Id); +} diff --git a/Octokit/Models/Response/ListArtifactsResponse.cs b/Octokit/Models/Response/ListArtifactsResponse.cs new file mode 100644 index 0000000000..0d6a9db2be --- /dev/null +++ b/Octokit/Models/Response/ListArtifactsResponse.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public class ListArtifactsResponse +{ + public ListArtifactsResponse() + { + } + + public ListArtifactsResponse(int totalCount, IReadOnlyList artifacts) + { + TotalCount = totalCount; + Artifacts = artifacts; + } + + /// + /// The number of artifacts found + /// + public int TotalCount { get; private set; } + + /// + /// The list of found artifacts + /// + public IReadOnlyList Artifacts { get; private set; } = new List(); + + internal string DebuggerDisplay => string.Format(CultureInfo.InvariantCulture, "Artifacts: {0}", TotalCount); +} \ No newline at end of file