feat: implement GetFileInfoAsync, GetFileAsync, and DeleteFileAsync
This commit is contained in:
@@ -385,6 +385,14 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
public async Task<AnthropicResult<AnthropicFile>> CreateFileAsync(CreateFileRequest request, CancellationToken cancellationToken = default)
|
public async Task<AnthropicResult<AnthropicFile>> CreateFileAsync(CreateFileRequest request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var response = await SendFileRequestAsync(FilesEndpoint, request, cancellationToken);
|
var response = await SendFileRequestAsync(FilesEndpoint, request, cancellationToken);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var error = Deserialize<AnthropicError>(content) ?? new AnthropicError();
|
||||||
|
return AnthropicResult<AnthropicFile>.Failure(error, new AnthropicHeaders(response.Headers));
|
||||||
|
}
|
||||||
|
|
||||||
return await CreateResultAsync<AnthropicFile>(response);
|
return await CreateResultAsync<AnthropicFile>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +402,14 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
var pagingRequest = request ?? new PagingRequest();
|
var pagingRequest = request ?? new PagingRequest();
|
||||||
var endpoint = $"{FilesEndpoint}?{pagingRequest.ToQueryParameters()}";
|
var endpoint = $"{FilesEndpoint}?{pagingRequest.ToQueryParameters()}";
|
||||||
var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken);
|
var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var error = Deserialize<AnthropicError>(content) ?? new AnthropicError();
|
||||||
|
return AnthropicResult<Page<AnthropicFile>>.Failure(error, new AnthropicHeaders(response.Headers));
|
||||||
|
}
|
||||||
|
|
||||||
return await CreateResultAsync<Page<AnthropicFile>>(response);
|
return await CreateResultAsync<Page<AnthropicFile>>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +422,55 @@ public class AnthropicApiClient : IAnthropicApiClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<AnthropicResult<AnthropicFile>> GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var endpoint = $"{FilesEndpoint}/{fileId}";
|
||||||
|
var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var error = Deserialize<AnthropicError>(content) ?? new AnthropicError();
|
||||||
|
return AnthropicResult<AnthropicFile>.Failure(error, new AnthropicHeaders(response.Headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await CreateResultAsync<AnthropicFile>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<AnthropicResult<Stream>> GetFileAsync(string fileId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var endpoint = $"{FilesEndpoint}/{fileId}/content";
|
||||||
|
var response = await SendRequestAsync(endpoint, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var error = Deserialize<AnthropicError>(content) ?? new AnthropicError();
|
||||||
|
return AnthropicResult<Stream>.Failure(error, new AnthropicHeaders(response.Headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = await response.Content.ReadAsStreamAsync();
|
||||||
|
return AnthropicResult<Stream>.Success(stream, new AnthropicHeaders(response.Headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<AnthropicResult<AnthropicFileDeleteResponse>> DeleteFileAsync(string fileId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var endpoint = $"{FilesEndpoint}/{fileId}";
|
||||||
|
var response = await SendRequestAsync(endpoint, HttpMethod.Delete, cancellationToken);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var error = Deserialize<AnthropicError>(content) ?? new AnthropicError();
|
||||||
|
return AnthropicResult<AnthropicFileDeleteResponse>.Failure(error, new AnthropicHeaders(response.Headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await CreateResultAsync<AnthropicFileDeleteResponse>(response);
|
||||||
|
}
|
||||||
|
|
||||||
private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var pagingRequest = new PagingRequest(limit: limit);
|
var pagingRequest = new PagingRequest(limit: limit);
|
||||||
|
|||||||
@@ -135,4 +135,29 @@ public interface IAnthropicApiClient
|
|||||||
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
|
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
|
||||||
/// <returns>An asynchronous enumerable that yields the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicFile"/>.</returns>
|
/// <returns>An asynchronous enumerable that yields the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicFile"/>.</returns>
|
||||||
IAsyncEnumerable<AnthropicResult<Page<AnthropicFile>>> ListAllFilesAsync(int limit = 20, CancellationToken cancellationToken = default);
|
IAsyncEnumerable<AnthropicResult<Page<AnthropicFile>>> ListAllFilesAsync(int limit = 20, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a file's metadata by its ID asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileId">The ID of the file to get.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="AnthropicFile"/>.</returns>
|
||||||
|
Task<AnthropicResult<AnthropicFile>> GetFileInfoAsync(string fileId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a file's content by its ID asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileId">The ID of the file to get the content for.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is a stream containing the file content.</returns>
|
||||||
|
Task<AnthropicResult<Stream>> GetFileAsync(string fileId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a file by its ID asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileId">The ID of the file to delete.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="AnthropicFileDeleteResponse"/>.</returns>
|
||||||
|
Task<AnthropicResult<AnthropicFileDeleteResponse>> DeleteFileAsync(string fileId, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace AnthropicClient.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the response from deleting a file in the Anthropic API.
|
||||||
|
/// </summary>
|
||||||
|
public class AnthropicFileDeleteResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the file that was deleted.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the response type
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -744,4 +744,40 @@ public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndTo
|
|||||||
responses.Select(r => r.Value).SelectMany(p => p.Data)
|
responses.Select(r => r.Value).SelectMany(p => p.Data)
|
||||||
.Should().ContainSingle(f => f.Id == createdFile.Value.Id);
|
.Should().ContainSingle(f => f.Id == createdFile.Value.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFileInfoAsync_WhenCalled_ItShouldReturnFile()
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
httpClient.DefaultRequestHeaders.Add("anthropic-beta", "files-api-2025-04-14");
|
||||||
|
var client = CreateClient(httpClient);
|
||||||
|
|
||||||
|
var fileBytes = await File.ReadAllBytesAsync(TestFileHelper.GetTestFilePath("story.txt"));
|
||||||
|
var createFileRequest = new CreateFileRequest(fileBytes, "story.txt", "text/plain");
|
||||||
|
var createdFile = await client.CreateFileAsync(createFileRequest);
|
||||||
|
|
||||||
|
var result = await client.GetFileInfoAsync(createdFile.Value.Id);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeOfType<AnthropicFile>();
|
||||||
|
result.Value.Id.Should().Be(createdFile.Value.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteFileAsync_WhenCalled_ItShouldReturnDeleteResponse()
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
httpClient.DefaultRequestHeaders.Add("anthropic-beta", "files-api-2025-04-14");
|
||||||
|
var client = CreateClient(httpClient);
|
||||||
|
|
||||||
|
var fileBytes = await File.ReadAllBytesAsync(TestFileHelper.GetTestFilePath("story.txt"));
|
||||||
|
var createFileRequest = new CreateFileRequest(fileBytes, "story.txt", "text/plain");
|
||||||
|
var createdFile = await client.CreateFileAsync(createFileRequest);
|
||||||
|
|
||||||
|
var result = await client.DeleteFileAsync(createdFile.Value.Id);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeOfType<AnthropicFileDeleteResponse>();
|
||||||
|
result.Value.Id.Should().Be(createdFile.Value.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2167,4 +2167,88 @@ public class AnthropicApiClientTests : IntegrationTest
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFileInfoAsync_WhenCalled_ItShouldReturnFile()
|
||||||
|
{
|
||||||
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
||||||
|
|
||||||
|
_mockHttpMessageHandler
|
||||||
|
.WhenGetFileRequest(fileId)
|
||||||
|
.Respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
"application/json",
|
||||||
|
@"{
|
||||||
|
""created_at"": ""2023-11-07T05:31:56Z"",
|
||||||
|
""downloadable"": false,
|
||||||
|
""filename"": ""example.txt"",
|
||||||
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
||||||
|
""mime_type"": ""text/plain"",
|
||||||
|
""size_bytes"": 1234,
|
||||||
|
""type"": ""file""
|
||||||
|
}"
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await Client.GetFileInfoAsync(fileId);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeEquivalentTo(new AnthropicFile()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTimeOffset.Parse("2023-11-07T05:31:56Z"),
|
||||||
|
Downloadable = false,
|
||||||
|
Name = "example.txt",
|
||||||
|
Id = "file_013Zva2CMHLNnXjNJJKqJ2EF",
|
||||||
|
MimeType = "text/plain",
|
||||||
|
Size = 1234,
|
||||||
|
Type = "file"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFileAsync_WhenCalled_ItShouldReturnFileContent()
|
||||||
|
{
|
||||||
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
||||||
|
var fileContent = new MemoryStream(Encoding.UTF8.GetBytes("Example file content"));
|
||||||
|
|
||||||
|
_mockHttpMessageHandler
|
||||||
|
.WhenGetFileContentRequest(fileId)
|
||||||
|
.Respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
"application/octet-stream",
|
||||||
|
fileContent
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await Client.GetFileAsync(fileId);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeAssignableTo<Stream>();
|
||||||
|
|
||||||
|
using var streamReader = new StreamReader(result.Value);
|
||||||
|
var content = await streamReader.ReadToEndAsync();
|
||||||
|
content.Should().Be("Example file content");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteFileAsync_WhenCalled_ItShouldReturnDeletionResponse()
|
||||||
|
{
|
||||||
|
var fileId = "file_013Zva2CMHLNnXjNJJKqJ2EF";
|
||||||
|
|
||||||
|
_mockHttpMessageHandler
|
||||||
|
.WhenDeleteFileRequest(fileId)
|
||||||
|
.Respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
"application/json",
|
||||||
|
@"{
|
||||||
|
""id"": ""file_013Zva2CMHLNnXjNJJKqJ2EF"",
|
||||||
|
""type"": ""file_deleted""
|
||||||
|
}"
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await Client.DeleteFileAsync(fileId);
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
result.Value.Should().BeOfType<AnthropicFileDeleteResponse>();
|
||||||
|
result.Value.Id.Should().Be(fileId);
|
||||||
|
result.Value.Type.Should().Be("file_deleted");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -116,4 +116,22 @@ public static class MockHttpMessageHandlerExtensions
|
|||||||
return mockHttpMessageHandler
|
return mockHttpMessageHandler
|
||||||
.SetupBaseRequest(HttpMethod.Get, FilesEndpoint);
|
.SetupBaseRequest(HttpMethod.Get, FilesEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MockedRequest WhenGetFileRequest(this MockHttpMessageHandler mockHttpMessageHandler, string fileId)
|
||||||
|
{
|
||||||
|
return mockHttpMessageHandler
|
||||||
|
.SetupBaseRequest(HttpMethod.Get, $"{FilesEndpoint}/{fileId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MockedRequest WhenGetFileContentRequest(this MockHttpMessageHandler mockHttpMessageHandler, string fileId)
|
||||||
|
{
|
||||||
|
return mockHttpMessageHandler
|
||||||
|
.SetupBaseRequest(HttpMethod.Get, $"{FilesEndpoint}/{fileId}/content");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MockedRequest WhenDeleteFileRequest(this MockHttpMessageHandler mockHttpMessageHandler, string fileId)
|
||||||
|
{
|
||||||
|
return mockHttpMessageHandler
|
||||||
|
.SetupBaseRequest(HttpMethod.Delete, $"{FilesEndpoint}/{fileId}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
namespace AnthropicClient.Tests.Unit.Models;
|
||||||
|
|
||||||
|
public class AnthropicFileDeleteResponseTests : SerializationTest
|
||||||
|
{
|
||||||
|
private readonly string _testJson = @"{
|
||||||
|
""id"": ""file-12345"",
|
||||||
|
""type"": ""file_deleted""
|
||||||
|
}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||||
|
{
|
||||||
|
var response = new AnthropicFileDeleteResponse();
|
||||||
|
|
||||||
|
response.Id.Should().BeEmpty();
|
||||||
|
response.Type.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithValues_ItShouldInitializePropertiesWithValues()
|
||||||
|
{
|
||||||
|
var id = "file-12345";
|
||||||
|
var type = "file_deleted";
|
||||||
|
|
||||||
|
var response = new AnthropicFileDeleteResponse
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Type = type
|
||||||
|
};
|
||||||
|
|
||||||
|
response.Id.Should().Be(id);
|
||||||
|
response.Type.Should().Be(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonSerialization_WhenSerialized_ItShouldMatchExpectedJson()
|
||||||
|
{
|
||||||
|
var response = new AnthropicFileDeleteResponse
|
||||||
|
{
|
||||||
|
Id = "file-12345",
|
||||||
|
Type = "file_deleted"
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(response, JsonSerializationOptions.DefaultOptions);
|
||||||
|
|
||||||
|
JsonAssert.Equal(_testJson, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void JsonDeserialization_WhenDeserialized_ItShouldMatchExpectedObject()
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.Deserialize<AnthropicFileDeleteResponse>(_testJson, JsonSerializationOptions.DefaultOptions);
|
||||||
|
|
||||||
|
response.Should().NotBeNull();
|
||||||
|
response.Id.Should().Be("file-12345");
|
||||||
|
response.Type.Should().Be("file_deleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user