diff --git a/arangodb-net-standard.Test/CollectionApi/CollectionApiClientTest.cs b/arangodb-net-standard.Test/CollectionApi/CollectionApiClientTest.cs index c54770da..29ebc6bc 100644 --- a/arangodb-net-standard.Test/CollectionApi/CollectionApiClientTest.cs +++ b/arangodb-net-standard.Test/CollectionApi/CollectionApiClientTest.cs @@ -1,13 +1,13 @@ -using ArangoDBNetStandard; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using ArangoDBNetStandard; using ArangoDBNetStandard.CollectionApi; using ArangoDBNetStandard.CollectionApi.Models; using ArangoDBNetStandard.DocumentApi.Models; using ArangoDBNetStandard.Transport; using Moq; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; using Xunit; namespace ArangoDBNetStandardTest.CollectionApi @@ -160,8 +160,9 @@ public async Task PostCollectionAsync_ShouldUseQueryParameter() mockTransport.Setup(x => x.PostAsync( It.IsAny(), - It.IsAny())) - .Returns((string uri, byte[] content) => + It.IsAny(), + It.IsAny())) + .Returns((string uri, byte[] content, WebHeaderCollection webHeaderCollection) => { requestUri = uri; return Task.FromResult(mockResponse.Object); diff --git a/arangodb-net-standard.Test/CursorApi/CursorApiClientTest.cs b/arangodb-net-standard.Test/CursorApi/CursorApiClientTest.cs index d868ef03..de874fd0 100644 --- a/arangodb-net-standard.Test/CursorApi/CursorApiClientTest.cs +++ b/arangodb-net-standard.Test/CursorApi/CursorApiClientTest.cs @@ -1,12 +1,12 @@ -using ArangoDBNetStandard; -using ArangoDBNetStandard.CursorApi; -using ArangoDBNetStandard.CursorApi.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; +using ArangoDBNetStandard; +using ArangoDBNetStandard.CursorApi; +using ArangoDBNetStandard.CursorApi.Models; using ArangoDBNetStandard.Serialization; using ArangoDBNetStandard.Transport; using Moq; @@ -187,7 +187,8 @@ public async Task PostCursorAsync_ShouldThrow_WhenErrorDeserializationFailed() mockTransport.Setup(x => x.PostAsync( It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .Returns(Task.FromResult(mockResponse.Object)); var cursorApi = new CursorApiClient(mockTransport.Object); @@ -215,6 +216,58 @@ public async Task PostCursorAsync_ShouldThrowException_WhenResponseDeserializati Assert.NotNull(ex.InnerException); } + [Fact] + public async Task PostCursorAsync_ShouldUseHeaderProperties() + { + // Mock the IApiClientTransport. + var mockTransport = new Mock(); + + // Mock the IApiClientResponse. + var mockResponse = new Mock(); + + // Mock the IApiClientResponseContent. + var mockResponseContent = new Mock(); + + // Setup the mocked api client response. + mockResponse.Setup(x => x.Content) + .Returns(mockResponseContent.Object); + mockResponse.Setup(x => x.IsSuccessStatusCode) + .Returns(true); + + // Setup the mocked api client transport. + WebHeaderCollection requestHeader = null; + mockTransport.Setup(x => x.PostAsync( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((string uri, byte[] content, WebHeaderCollection webHeaderCollection) => + { + requestHeader = webHeaderCollection; + return Task.FromResult(mockResponse.Object); + }); + + string transactionHeaderKey = "x-arango-trx-id"; + string dummyTransactionId = "dummy transaction Id"; + + // Call the method to create the cursor. + var apiClient = new CursorApiClient(mockTransport.Object); + await apiClient.PostCursorAsync( + new PostCursorBody + { + Query = "FOR doc IN [{ myProperty: CONCAT('This is a ', @testString) }] LIMIT 1 RETURN doc", + BindVars = new Dictionary { ["testString"] = "robbery" } + }, + new CursorHeaderProperties + { + TransactionId = dummyTransactionId + }); + + // Check that the header and values are there. + Assert.NotNull(requestHeader); + Assert.Contains(transactionHeaderKey, requestHeader.AllKeys); + Assert.Equal(dummyTransactionId, requestHeader.Get(transactionHeaderKey)); + } + [Fact] public async Task PutCursorAsync_ShouldSucceed() { diff --git a/arangodb-net-standard/CursorApi/CursorApiClient.cs b/arangodb-net-standard/CursorApi/CursorApiClient.cs index ff8ebedb..303d7353 100644 --- a/arangodb-net-standard/CursorApi/CursorApiClient.cs +++ b/arangodb-net-standard/CursorApi/CursorApiClient.cs @@ -1,9 +1,9 @@ -using ArangoDBNetStandard.CursorApi.Models; -using ArangoDBNetStandard.Serialization; -using ArangoDBNetStandard.Transport; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using ArangoDBNetStandard.CursorApi.Models; +using ArangoDBNetStandard.Serialization; +using ArangoDBNetStandard.Transport; namespace ArangoDBNetStandard.CursorApi { @@ -45,6 +45,25 @@ public CursorApiClient(IApiClientTransport client, IApiClientSerialization seria _client = client; } + /// + /// Method to get the header collection. + /// + /// The values. + /// values. + protected virtual WebHeaderCollection GetHeaderCollection(CursorHeaderProperties headerProperties) + { + var headerCollection = new WebHeaderCollection(); + if (headerProperties != null) + { + if (!string.IsNullOrWhiteSpace(headerProperties.TransactionId)) + { + headerCollection.Add("x-arango-trx-id", headerProperties.TransactionId); + } + } + + return headerCollection; + } + /// /// Execute an AQL query, creating a cursor which can be used to page query results. /// @@ -57,6 +76,7 @@ public CursorApiClient(IApiClientTransport client, IApiClientSerialization seria /// /// /// + /// Optional. The stream transaction Id. /// public virtual async Task> PostCursorAsync( string query, @@ -66,37 +86,48 @@ public virtual async Task> PostCursorAsync( long? batchSize = null, bool? cache = null, long? memoryLimit = null, - int? ttl = null) + int? ttl = null, + string transactionId = null) { - return await PostCursorAsync(new PostCursorBody + var headerProperties = new CursorHeaderProperties(); + if (!string.IsNullOrWhiteSpace(transactionId)) { - Query = query, - BindVars = bindVars, - Options = options, - Count = count, - BatchSize = batchSize, - Cache = cache, - MemoryLimit = memoryLimit, - Ttl = ttl - }).ConfigureAwait(false); + headerProperties.TransactionId = transactionId; + } + + return await PostCursorAsync( + new PostCursorBody + { + Query = query, + BindVars = bindVars, + Options = options, + Count = count, + BatchSize = batchSize, + Cache = cache, + MemoryLimit = memoryLimit, + Ttl = ttl + }, headerProperties).ConfigureAwait(false); } /// /// Execute an AQL query, creating a cursor which can be used to page query results. /// /// Object encapsulating options and parameters of the query. + /// Optional. Additional Header properties. /// public virtual async Task> PostCursorAsync( - PostCursorBody postCursorBody) + PostCursorBody postCursorBody, CursorHeaderProperties headerProperties = null) { var content = GetContent(postCursorBody, new ApiClientSerializationOptions(true, true)); - using (var response = await _client.PostAsync(_cursorApiPath, content).ConfigureAwait(false)) + var headerCollection = GetHeaderCollection(headerProperties); + using (var response = await _client.PostAsync(_cursorApiPath, content, headerCollection).ConfigureAwait(false)) { if (response.IsSuccessStatusCode) { var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); return DeserializeJsonFromStream>(stream); } + throw await GetApiErrorException(response).ConfigureAwait(false); } } @@ -116,6 +147,7 @@ public virtual async Task DeleteCursorAsync(string cursorI var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); return DeserializeJsonFromStream(stream); } + throw await GetApiErrorException(response).ConfigureAwait(false); } } @@ -136,6 +168,7 @@ public virtual async Task> PutCursorAsync(string cursorI var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); return DeserializeJsonFromStream>(stream); } + throw await GetApiErrorException(response).ConfigureAwait(false); } } diff --git a/arangodb-net-standard/CursorApi/ICursorApiClient.cs b/arangodb-net-standard/CursorApi/ICursorApiClient.cs index 0df0e628..5b36d84a 100644 --- a/arangodb-net-standard/CursorApi/ICursorApiClient.cs +++ b/arangodb-net-standard/CursorApi/ICursorApiClient.cs @@ -1,7 +1,6 @@ -using ArangoDBNetStandard.CursorApi.Models; -using ArangoDBNetStandard.Serialization; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using ArangoDBNetStandard.CursorApi.Models; namespace ArangoDBNetStandard.CursorApi { @@ -22,6 +21,7 @@ public interface ICursorApiClient /// /// /// + /// Optional. The stream transaction Id. /// Task> PostCursorAsync( string query, @@ -31,15 +31,17 @@ Task> PostCursorAsync( long? batchSize = null, bool? cache = null, long? memoryLimit = null, - int? ttl = null); + int? ttl = null, + string transactionId = null); /// /// Execute an AQL query, creating a cursor which can be used to page query results. /// /// Object encapsulating options and parameters of the query. + /// Optional. Additional Header properties. /// Task> PostCursorAsync( - PostCursorBody postCursorBody); + PostCursorBody postCursorBody, CursorHeaderProperties headerProperties); /// /// Deletes an existing cursor and frees the resources associated with it. diff --git a/arangodb-net-standard/CursorApi/Models/CursorHeaderProperties.cs b/arangodb-net-standard/CursorApi/Models/CursorHeaderProperties.cs new file mode 100644 index 00000000..0822e041 --- /dev/null +++ b/arangodb-net-standard/CursorApi/Models/CursorHeaderProperties.cs @@ -0,0 +1,13 @@ +namespace ArangoDBNetStandard.CursorApi.Models +{ + /// + /// Class representing the additional header properties for Cursor Api. + /// + public class CursorHeaderProperties + { + /// + /// Gets or sets the stream transaction Id. + /// + public string TransactionId { get; set; } + } +} diff --git a/arangodb-net-standard/Transport/Http/HttpApiTransport.cs b/arangodb-net-standard/Transport/Http/HttpApiTransport.cs index 37af1a79..c0dbbb9c 100644 --- a/arangodb-net-standard/Transport/Http/HttpApiTransport.cs +++ b/arangodb-net-standard/Transport/Http/HttpApiTransport.cs @@ -40,6 +40,22 @@ public HttpApiTransport(HttpClient client, HttpContentType contentType) _contentType = contentType; } + /// + /// Method to apply the additional headers. + /// + /// Object containing a dictionary of Header keys and values. + /// The header to update. + private static void ApplyHeaders(WebHeaderCollection webHeaderCollection, HttpHeaders headers) + { + if (webHeaderCollection != null) + { + foreach (var key in webHeaderCollection.AllKeys) + { + headers.Add(key, webHeaderCollection[key]); + } + } + } + /// /// Get an instance of that uses no authentication. /// @@ -197,11 +213,14 @@ public async Task DeleteAsync(string requestUri, byte[] cont /// /// /// The content of the request, must not be null. + /// Object containing a dictionary of Header keys and values. /// - public async Task PostAsync(string requestUri, byte[] content) + public async Task PostAsync( + string requestUri, byte[] content, WebHeaderCollection webHeaderCollection = null) { var httpContent = new ByteArrayContent(content); httpContent.Headers.ContentType = new MediaTypeHeaderValue(_contentTypeMap[_contentType]); + ApplyHeaders(webHeaderCollection, httpContent.Headers); var response = await _client.PostAsync(requestUri, httpContent).ConfigureAwait(false); return new HttpApiClientResponse(response); } @@ -212,7 +231,8 @@ public async Task PostAsync(string requestUri, byte[] conten /// /// The content of the request, must not be null. /// - public async Task PutAsync(string requestUri, byte[] content) + public async Task PutAsync( + string requestUri, byte[] content) { var httpContent = new ByteArrayContent(content); httpContent.Headers.ContentType = new MediaTypeHeaderValue(_contentTypeMap[_contentType]); diff --git a/arangodb-net-standard/Transport/IApiClientTransport.cs b/arangodb-net-standard/Transport/IApiClientTransport.cs index b7f900ac..480af710 100644 --- a/arangodb-net-standard/Transport/IApiClientTransport.cs +++ b/arangodb-net-standard/Transport/IApiClientTransport.cs @@ -14,8 +14,10 @@ public interface IApiClientTransport : IDisposable /// /// /// + /// Object containing a dictionary of Header keys and values. /// - Task PostAsync(string requestUri, byte[] content); + Task PostAsync( + string requestUri, byte[] content, WebHeaderCollection webHeaderCollection = null); /// /// Send a DELETE request. @@ -38,7 +40,8 @@ public interface IApiClientTransport : IDisposable /// /// /// - Task PutAsync(string requestUri, byte[] content); + Task PutAsync( + string requestUri, byte[] content); /// /// Send a GET request.