Add support for delete operations (#198)

* Add support for delete operations

* Removing extra spaces to fix build break.

Co-authored-by: Aman Bhardwaj <amanbha@users.noreply.github.com>
This commit is contained in:
Ryan Nowak 2020-01-09 09:53:13 -08:00 committed by Aman Bhardwaj
parent c4f80f10b6
commit b02aa3f02b
5 changed files with 128 additions and 10 deletions

View File

@ -52,5 +52,13 @@ namespace Dapr
/// <typeparam name="TValue">The data type.</typeparam>
/// <returns>A <see cref="ValueTask" /> that will complete when the operation has completed.</returns>
public abstract ValueTask SaveStateAsync<TValue>(string key, TValue value, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes the value associated with the provided <paramref name="key" /> in the Dapr state store.
/// </summary>
/// <param name="key">The state key.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="ValueTask" /> that will complete when the operation has completed.</returns>
public abstract ValueTask DeleteStateAsync(string key, CancellationToken cancellationToken = default);
}
}

View File

@ -56,10 +56,20 @@ namespace Dapr
public TValue Value { get; set; }
/// <summary>
/// Saves the the current value of <see cref="Value" /> to the state store.
/// Deletes the entry assocated with <see cref="Key" /> in the state store.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task" /> that will complete when the operation has completed.</returns>
/// <returns>A <see cref="ValueTask" /> that will complete when the operation has completed.</returns>
public ValueTask DeleteAsync(CancellationToken cancellationToken = default)
{
return this.client.DeleteStateAsync(this.Key, cancellationToken);
}
/// <summary>
/// Saves the current value of <see cref="Value" /> to the state store.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="ValueTask" /> that will complete when the operation has completed.</returns>
public ValueTask SaveAsync(CancellationToken cancellationToken = default)
{
return this.client.SaveStateAsync(this.Key, this.Value, cancellationToken);

View File

@ -122,6 +122,44 @@ namespace Dapr
}
}
/// <summary>
/// Deletes the value associated with the provided <paramref name="key" /> in the Dapr state store.
/// </summary>
/// <param name="key">The state key.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="ValueTask" /> that will complete when the operation has completed.</returns>
public async override ValueTask DeleteStateAsync(string key, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("The value cannot be null or empty.", nameof(key));
}
// Docs: https://github.com/dapr/docs/blob/master/reference/api/state.md#delete-state
var url = this.client.BaseAddress == null ? $"http://localhost:{DefaultHttpPort}{StatePath}/{key}" : $"{StatePath}/{key}";
var request = new HttpRequestMessage(HttpMethod.Delete, url);
var response = await this.client.SendAsync(request, cancellationToken).ConfigureAwait(false);
// 200: success
//
// To avoid being overload coupled we handle a range of 2XX status codes in common use for DELETEs.
if ((int)response.StatusCode >= 200 && (int)response.StatusCode <= 204)
{
return;
}
if (response.Content != null)
{
var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new HttpRequestException($"Failed to delete state with status code '{response.StatusCode}': {error}.");
}
else
{
throw new HttpRequestException($"Failed to delete state with status code '{response.StatusCode}'.");
}
}
private static AsyncJsonContent<T> CreateContent<T>(T obj, JsonSerializerOptions serializerOptions)
{
return new AsyncJsonContent<T>(obj, serializerOptions);

View File

@ -27,15 +27,13 @@ namespace Dapr
public override ValueTask SaveStateAsync<TValue>(string key, TValue value, CancellationToken cancellationToken = default)
{
if (value == null)
{
this.State.Remove(key);
}
else
{
this.State[key] = value;
}
this.State[key] = value;
return new ValueTask(Task.CompletedTask);
}
public override ValueTask DeleteStateAsync(string key, CancellationToken cancellationToken = default)
{
this.State.Remove(key);
return new ValueTask(Task.CompletedTask);
}
}

View File

@ -175,6 +175,37 @@ namespace Dapr.Client.Test
await task;
}
[Fact]
public async Task DeleteStateAsync_CanDeleteState()
{
var httpClient = new TestHttpClient();
var client = new StateHttpClient(httpClient, new JsonSerializerOptions());
var task = client.DeleteStateAsync("test");
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue();
entry.Request.RequestUri.ToString().Should().Be(DeleteStateUrl(3500, "test"));
entry.Respond(new HttpResponseMessage(HttpStatusCode.OK));
await task;
}
[Fact]
public async Task DeleteStateAsync_ThrowsForNonSuccess()
{
var httpClient = new TestHttpClient();
var client = new StateHttpClient(httpClient, new JsonSerializerOptions());
var task = client.DeleteStateAsync("test");
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue();
entry.Request.RequestUri.ToString().Should().Be(DeleteStateUrl(3500, "test"));
entry.Respond(new HttpResponseMessage(HttpStatusCode.NotAcceptable));
await FluentActions.Awaiting(async () => await task).Should().ThrowAsync<HttpRequestException>();
}
[Fact]
public async Task GetStateEntryAsync_CanReadState()
{
@ -252,6 +283,34 @@ namespace Dapr.Client.Test
await task;
}
[Fact]
public async Task GetStateEntryAsync_CanDeleteState()
{
var httpClient = new TestHttpClient();
var client = new StateHttpClient(httpClient, new JsonSerializerOptions());
var task = client.GetStateEntryAsync<Widget>("test");
httpClient.Requests.TryDequeue(out var entry).Should().BeTrue();
entry.Request.RequestUri.ToString().Should().Be(GetStateUrl(3500, "test"));
entry.RespondWithJson(new Widget() { Size = "small", Color = "yellow", });
var state = await task;
state.Key.Should().Be("test");
state.Value.Size.Should().Be("small");
state.Value.Color.Should().Be("yellow");
state.Value.Color = "green";
var task2 = state.DeleteAsync();
httpClient.Requests.TryDequeue(out entry).Should().BeTrue();
entry.Request.RequestUri.ToString().Should().Be(DeleteStateUrl(3500, "test"));
entry.Respond(new HttpResponseMessage(HttpStatusCode.OK));
await task;
}
private static string GetStateUrl(int port, string key)
{
return $"http://localhost:{port}/v1.0/state/{key}";
@ -262,6 +321,11 @@ namespace Dapr.Client.Test
return $"http://localhost:{port}/v1.0/state";
}
private static string DeleteStateUrl(int port, string key)
{
return $"http://localhost:{port}/v1.0/state/{key}";
}
private class Widget
{
public string Size { get; set; }