From 1d5ab5e6cedbc594427e12cae87bc580ed398d92 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 10 May 2023 15:54:10 +0200 Subject: [PATCH 01/21] Fixed the http attribute handling within ODataJsonLightBatchPayloadItemPropertiesCache.cs --- ...sonLightBatchPayloadItemPropertiesCache.cs | 92 +++++++++++++--- .../ODataJsonLightBatchReaderTests.cs | 103 ++++++++++++++++++ 2 files changed, 177 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 0180311144..19f927e4ed 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -227,14 +227,26 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + string propertyValueString = this.jsonReader.ReadStringValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } } break; case PropertyNameStatus: { - jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } } break; @@ -250,7 +262,11 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } } break; @@ -267,7 +283,8 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + object headerValueObject = this.jsonReader.ReadPrimitiveValue(); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -275,12 +292,20 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } this.jsonReader.ReadEndObject(); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -294,7 +319,12 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } } break; @@ -365,16 +395,24 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); + string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } break; case PropertyNameStatus: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } break; @@ -390,7 +428,11 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } break; @@ -407,7 +449,8 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -415,13 +458,21 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -435,7 +486,12 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } break; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index e17e1b77e4..4156457e0f 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,109 @@ await DoReadAsync( isResponse: false); } + [Fact] + public async Task ReadBatchRequestWithDuplicateAttributesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"GET\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicateHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithNullHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchResponseAsync() { From 3842542efa42e3ac86b4ee8ccc57071444a96061 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 10 May 2023 15:54:10 +0200 Subject: [PATCH 02/21] Fixed the http attribute handling within ODataJsonLightBatchPayloadItemPropertiesCache.cs --- ...sonLightBatchPayloadItemPropertiesCache.cs | 92 +++++++++++++--- .../ODataJsonLightBatchReaderTests.cs | 103 ++++++++++++++++++ 2 files changed, 177 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 0180311144..19f927e4ed 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -227,14 +227,26 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + string propertyValueString = this.jsonReader.ReadStringValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } } break; case PropertyNameStatus: { - jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } } break; @@ -250,7 +262,11 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } } break; @@ -267,7 +283,8 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + object headerValueObject = this.jsonReader.ReadPrimitiveValue(); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -275,12 +292,20 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } this.jsonReader.ReadEndObject(); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -294,7 +319,12 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } } break; @@ -365,16 +395,24 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); + string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } break; case PropertyNameStatus: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } break; @@ -390,7 +428,11 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } break; @@ -407,7 +449,8 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + string headerValue = headerValueObject?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -415,13 +458,21 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -435,7 +486,12 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } break; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index e17e1b77e4..4156457e0f 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,109 @@ await DoReadAsync( isResponse: false); } + [Fact] + public async Task ReadBatchRequestWithDuplicateAttributesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"GET\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicateHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithNullHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + + Assert.True(true, "The test was successful, because no ArgumentException was thrown"); + } + catch (ArgumentException ex) + { + Assert.True(false, ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchResponseAsync() { From f50a630943e2c04c75c6bff356e6039f21242157 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 15 May 2023 14:14:12 +0200 Subject: [PATCH 03/21] Switched http attribute test assert true to false to highlight test failure due to an Exception --- .../JsonLight/ODataJsonLightBatchReaderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 4156457e0f..5d8d743626 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -392,7 +392,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } catch (ArgumentException ex) { - Assert.True(false, ex.Message); + Assert.False(true, ex.Message); } }, isResponse: false); @@ -426,7 +426,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } catch (ArgumentException ex) { - Assert.True(false, ex.Message); + Assert.False(true, ex.Message); } }, isResponse: false); @@ -460,7 +460,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } catch (ArgumentException ex) { - Assert.True(false, ex.Message); + Assert.False(true, ex.Message); } }, isResponse: false); From 8a70688a2ff410d86de779f2d4a61df2442be2ad Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 19 Jun 2023 18:44:15 +0200 Subject: [PATCH 04/21] Fixed duplicate json property handling ODataJsonLightBatchPayloadItemPropertiesCache, throwing an exception for duplicate properties, except header properties. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 97 ++++++------------- .../Microsoft.OData.Core.cs | 1 + .../Parameterized.Microsoft.OData.Core.cs | 8 ++ .../ODataJsonLightBatchReaderTests.cs | 35 ------- 4 files changed, 40 insertions(+), 101 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 19f927e4ed..ddc713c302 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -220,6 +220,12 @@ private void ScanJsonProperties() // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(this.jsonReader.ReadPropertyName()); + // Throw an ODataException, if a duplicate json property was detected + if (jsonProperties.ContainsKey(propertyName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + } + switch (propertyName) { case PropertyNameId: @@ -227,26 +233,14 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - string propertyValueString = this.jsonReader.ReadStringValue(); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueString); - } + jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); } break; case PropertyNameStatus: { - object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueObject); - } + jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); } break; @@ -262,11 +256,7 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, dependsOnIds); - } + jsonProperties.Add(propertyName, dependsOnIds); } break; @@ -292,20 +282,19 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - // Skip duplicate http headers if (!headers.ContainsKeyOrdinal(headerName)) { headers.Add(headerName, headerValue); } + else + { + headers[propertyName] = headerValue; + } } this.jsonReader.ReadEndObject(); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, headers); - } + jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { @@ -319,12 +308,7 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, bodyContentStream); - } + jsonProperties.Add(propertyName, bodyContentStream); } break; @@ -389,30 +373,28 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(await this.asynchronousJsonReader.ReadPropertyNameAsync().ConfigureAwait(false)); + // Throw an ODataException, if a duplicate json property was detected + if (jsonProperties.ContainsKey(propertyName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + } + switch (propertyName) { case PropertyNameId: case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueString); - } + jsonProperties.Add( + propertyName, + await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); break; case PropertyNameStatus: - object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueObject); - } + jsonProperties.Add( + propertyName, + await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); break; @@ -428,11 +410,7 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, dependsOnIds); - } + jsonProperties.Add(propertyName, dependsOnIds); break; @@ -458,21 +436,13 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - // Skip duplicate http headers - if (!headers.ContainsKeyOrdinal(headerName)) - { - headers.Add(headerName, headerValue); - } + headers[headerName] = headerValue; } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, headers); - } + jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { @@ -486,12 +456,7 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, bodyContentStream); - } + jsonProperties.Add(propertyName, bodyContentStream); break; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index d1412751c7..e2eaa291ac 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -193,6 +193,7 @@ internal sealed class TextRes { internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType = "ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader = "ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader"; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index fa27bc4b55..bcee9f8429 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1647,6 +1647,14 @@ internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitRea return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached, p0); } + /// + /// A string like "Duplicate property name '{0}' for message in batch." + /// + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(object p0) + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch, p0); + } + /// /// A string like "Unknown property name '{0}' for message in batch." /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 5d8d743626..7fb8a14a11 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,41 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public async Task ReadBatchRequestWithDuplicateAttributesAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"GET\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - } - } - - Assert.True(true, "The test was successful, because no ArgumentException was thrown"); - } - catch (ArgumentException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public async Task ReadBatchRequestWithDuplicateHeadersAsync() { From 0707d6c486d18e8003ad726c36b31c0ca6da18f7 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Tue, 20 Jun 2023 08:14:20 +0200 Subject: [PATCH 05/21] Little optimization at the json property header handling. --- ...ataJsonLightBatchPayloadItemPropertiesCache.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index ddc713c302..153c10b21e 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,8 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - object headerValueObject = this.jsonReader.ReadPrimitiveValue(); - string headerValue = headerValueObject?.ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) @@ -282,14 +281,7 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - if (!headers.ContainsKeyOrdinal(headerName)) - { - headers.Add(headerName, headerValue); - } - else - { - headers[propertyName] = headerValue; - } + headers[propertyName] = headerValue; } this.jsonReader.ReadEndObject(); @@ -427,8 +419,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); - string headerValue = headerValueObject?.ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) From bcffbfc6760b9ed02e4d579bb0222559dc3a86b6 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 21 Jun 2023 11:36:17 +0200 Subject: [PATCH 06/21] Minor fix, replacing duplicate http headers correctly in the non async method --- .../JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 153c10b21e..eae05ee791 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -281,7 +281,7 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers[propertyName] = headerValue; + headers[headerName] = headerValue; } this.jsonReader.ReadEndObject(); From 22cc32a2185ecca4f6ac0907f10febbfb6efbfab Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Fri, 23 Jun 2023 08:37:59 +0200 Subject: [PATCH 07/21] Changed the handling of duplicate json property headers, which will now also throw an exception. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 16 +++++++-- .../Microsoft.OData.Core.cs | 1 + .../Parameterized.Microsoft.OData.Core.cs | 8 +++++ .../ODataJsonLightBatchReaderTests.cs | 34 ------------------- 4 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index eae05ee791..ab88b956da 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -275,13 +275,19 @@ private void ScanJsonProperties() string headerName = this.jsonReader.ReadPropertyName(); string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); + // Throw an ODataException, if a duplicate header was detected + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + } + // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - headers[headerName] = headerValue; + headers.Add(headerName, headerValue); } this.jsonReader.ReadEndObject(); @@ -421,13 +427,19 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() .ConfigureAwait(false); string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); + // Throw an ODataException, if a duplicate header was detected + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + } + // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - headers[headerName] = headerValue; + headers.Add(headerName, headerValue); } await this.asynchronousJsonReader.ReadEndObjectAsync() diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index e2eaa291ac..5fdb424c6a 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -195,6 +195,7 @@ internal sealed class TextRes { internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType = "ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader = "ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader"; internal const string ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse = "ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse"; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index bcee9f8429..80af159f10 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1663,6 +1663,14 @@ internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownProp return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0); } + /// + /// A string like "Duplicate header name '{0}' for message in batch." + /// + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(object p0) + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch, p0); + } + /// /// A string like "Unexpected reader.NodeType: {0}." /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 7fb8a14a11..d53a3c6b39 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,40 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public async Task ReadBatchRequestWithDuplicateHeadersAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - } - } - - Assert.True(true, "The test was successful, because no ArgumentException was thrown"); - } - catch (ArgumentException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public async Task ReadBatchRequestWithNullHeadersAsync() { From 3a63962669c097b1ef75a765c02cd25a4e866fcb Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 26 Jun 2023 10:36:31 +0200 Subject: [PATCH 08/21] Renamed exception messages & added them to the resource file --- ...JsonLightBatchPayloadItemPropertiesCache.cs | 8 ++++---- .../Microsoft.OData.Core.cs | 4 ++-- .../Microsoft.OData.Core.txt | 2 ++ .../Parameterized.Microsoft.OData.Core.cs | 18 +++++++++--------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index ab88b956da..c526644e6a 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -223,7 +223,7 @@ private void ScanJsonProperties() // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); } switch (propertyName) @@ -278,7 +278,7 @@ private void ScanJsonProperties() // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } // Remember the Content-Type header value. @@ -374,7 +374,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); } switch (propertyName) @@ -430,7 +430,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } // Remember the Content-Type header value. diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index 5fdb424c6a..637fd0bc56 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -193,9 +193,9 @@ internal sealed class TextRes { internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; - internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; - internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType = "ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType"; internal const string ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader = "ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader"; internal const string ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse = "ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse"; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt index fc88bb09b3..7da6f31a80 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -207,6 +207,8 @@ ODataBatchReaderStream_UnexpectedEndOfInput=Encountered an unexpected end of inp ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached=Too many white spaces after a boundary delimiter and before the terminating line resource set. For security reasons, the total number of characters for a boundary including white spaces must not exceed {0}. ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch=Unknown property name '{0}' for message in batch. +ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch=Duplicate property name '{0}' for request in batch. +ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch=Duplicate header name '{0}' for request in batch. ODataJsonLightBatchBodyContentReaderStream_UnexpectedNodeType=Unexpected reader.NodeType: {0}. ODataJsonLightBatchBodyContentReaderStream_UnsupportedContentTypeInHeader=Unknown/undefined type, new type that needs to be supported: {0}? diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index 80af159f10..3d5b11e4e9 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1648,27 +1648,27 @@ internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitRea } /// - /// A string like "Duplicate property name '{0}' for message in batch." + /// A string like "Unknown property name '{0}' for message in batch." /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(object p0) + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch(object p0) { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch, p0); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0); } /// - /// A string like "Unknown property name '{0}' for message in batch." + /// A string like "Duplicate property name '{0}' for request in batch." /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch(object p0) + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(object p0) { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch, p0); } /// - /// A string like "Duplicate header name '{0}' for message in batch." + /// A string like "Duplicate header name '{0}' for request in batch." /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(object p0) + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(object p0) { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch, p0); + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch, p0); } /// From d2aa56a4f0677f642ed478f75d2601c2192a57e4 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 3 Jul 2023 16:16:00 +0200 Subject: [PATCH 09/21] Improved ODataJsonLightBatchReaderTests for NULL value headers and added test cases for duplicate headers and properties --- .../ODataJsonLightBatchReaderTests.cs | 80 ++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index d53a3c6b39..5bf136effe 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -383,11 +383,13 @@ await SetupJsonLightBatchReaderAndRunTestAsync( { if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) { - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); } } - - Assert.True(true, "The test was successful, because no ArgumentException was thrown"); } catch (ArgumentException ex) { @@ -397,6 +399,78 @@ await SetupJsonLightBatchReaderAndRunTestAsync( isResponse: false); } + [Fact] + public async Task ReadBatchRequestWithDuplicatePropertiesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"atomicityGroup\": \"g1\"," + + "\"atomicityGroup\": \"g2\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); + } + catch (ODataException ex) + { + // Verify that the correct duplicate property has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicateHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + } + } + Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); + } + catch (ODataException ex) + { + // Verify that the correct duplicate header has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchResponseAsync() { From 23d04e9ae0768b9417dd9b60482d26d2c7ecadb0 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 10 Jul 2023 12:59:19 +0200 Subject: [PATCH 10/21] Added synchronous unit tests, added AssertThrowsAsync to duplicate json property unit tests --- .../ODataJsonLightBatchReaderTests.cs | 158 +++++++++++++++--- 1 file changed, 133 insertions(+), 25 deletions(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 5bf136effe..982a06b056 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,42 @@ await DoReadAsync( isResponse: false); } + [Fact] + public void ReadBatchRequestWithNullHeaders() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + try + { + while (jsonLightBatchReader.Read()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); + } + } + } + catch (NullReferenceException ex) + { + Assert.False(true, ex.Message); + } + }, + isResponse: false); + } + [Fact] public async Task ReadBatchRequestWithNullHeadersAsync() { @@ -391,7 +427,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } } } - catch (ArgumentException ex) + catch (NullReferenceException ex) { Assert.False(true, ex.Message); } @@ -400,7 +436,7 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } [Fact] - public async Task ReadBatchRequestWithDuplicatePropertiesAsync() + public void ReadBatchRequestWithDuplicateProperties() { var payload = "{\"requests\": [{" + "\"id\": \"1\"," + @@ -411,29 +447,89 @@ public async Task ReadBatchRequestWithDuplicatePropertiesAsync() "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - await SetupJsonLightBatchReaderAndRunTestAsync( + var exception = Assert.Throws( + () => SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + while (jsonLightBatchReader.Read()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + jsonLightBatchReader.CreateOperationRequestMessage(); + } + } + Assert.False(true, "The test failed, because the duplicate header has not thrown an ODataException"); + }, + isResponse: false)); + + // Verify that the correct duplicate property has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), exception.Message); + } + + [Fact] + public async Task ReadBatchRequestWithDuplicatePropertiesAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"atomicityGroup\": \"g1\"," + + "\"atomicityGroup\": \"g2\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + var exception = await Assert.ThrowsAsync( + () => SetupJsonLightBatchReaderAndRunTestAsync( payload, async (jsonLightBatchReader) => { - try + while (await jsonLightBatchReader.ReadAsync()) { - while (await jsonLightBatchReader.ReadAsync()) + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - // The json properties are just iterated through and have no purpose for this test case - await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - } + // The json properties are just iterated through and have no purpose for this test case + await jsonLightBatchReader.CreateOperationRequestMessageAsync(); } - Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); } - catch (ODataException ex) + Assert.False(true, "The test failed, because the duplicate header has not thrown an ODataException"); + }, + isResponse: false)); + + // Verify that the correct duplicate property has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), exception.Message); + } + + [Fact] + public void ReadBatchRequestWithDuplicateHeaders() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + var exception = Assert.Throws( + () => SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + while (jsonLightBatchReader.Read()) { - // Verify that the correct duplicate property has raised the ODataException - Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch("ATOMICITYGROUP"), ex.Message); + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + // The json properties are just iterated through and have no purpose for this test case + jsonLightBatchReader.CreateOperationRequestMessage(); + } } + Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); }, - isResponse: false); + isResponse: false)); + + // Verify that the correct duplicate header has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), exception.Message); } [Fact] @@ -446,12 +542,11 @@ public async Task ReadBatchRequestWithDuplicateHeadersAsync() "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"duplicate-header\":\"value1\",\"duplicate-header\":\"value2\"}, " + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - await SetupJsonLightBatchReaderAndRunTestAsync( + var exception = await Assert.ThrowsAsync( + () => SetupJsonLightBatchReaderAndRunTestAsync( payload, async (jsonLightBatchReader) => { - try - { while (await jsonLightBatchReader.ReadAsync()) { if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) @@ -461,14 +556,11 @@ await SetupJsonLightBatchReaderAndRunTestAsync( } } Assert.False(true, "The test failed, because the duplicate header has thrown no ODataException"); - } - catch (ODataException ex) - { - // Verify that the correct duplicate header has raised the ODataException - Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), ex.Message); - } }, - isResponse: false); + isResponse: false)); + + // Verify that the correct duplicate header has raised the ODataException + Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), exception.Message); } [Fact] @@ -1085,6 +1177,22 @@ private async Task DoReadAsync( } } + /// + /// Sets up an ODataJsonLightBatchReader, then runs the given test code synchronously + /// + private void SetupJsonLightBatchReaderAndRunTest( + string payload, + Action func, + bool isResponse = true) + { + using (var jsonLightInputContext = CreateJsonLightInputContext(payload, isAsync: false, isResponse: isResponse)) + { + var jsonLightBatchReader = new ODataJsonLightBatchReader(jsonLightInputContext, synchronous: true); + + func(jsonLightBatchReader); + } + } + /// /// Sets up an ODataJsonLightBatchReader, then runs the given test code asynchronously /// From 6271298fe04251a3ef7c68abbdcc82ceece174d5 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 17 Jul 2023 11:38:29 +0200 Subject: [PATCH 11/21] reverted ODataJsonLightBatchPayloadItemPropertiesCache null value handling of JSON property and removed corresponding unit tests. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 4 +- .../ODataJsonLightBatchReaderTests.cs | 72 ------------------- 2 files changed, 2 insertions(+), 74 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index c526644e6a..38de20f84c 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,7 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -425,7 +425,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 982a06b056..1bf1432bf4 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,78 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public void ReadBatchRequestWithNullHeaders() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - SetupJsonLightBatchReaderAndRunTest( - payload, - (jsonLightBatchReader) => - { - try - { - while (jsonLightBatchReader.Read()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); - // Verify that the Property "null-header" exists and it's value is set to NULL - var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); - Assert.NotNull(nullHeaderProperty.Key); - Assert.Null(nullHeaderProperty.Value); - } - } - } - catch (NullReferenceException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - - [Fact] - public async Task ReadBatchRequestWithNullHeadersAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - // Verify that the Property "null-header" exists and it's value is set to NULL - var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); - Assert.NotNull(nullHeaderProperty.Key); - Assert.Null(nullHeaderProperty.Value); - } - } - } - catch (NullReferenceException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public void ReadBatchRequestWithDuplicateProperties() { From 9018593ac2c370b2e8df5e4c19bbd1e00801c9bc Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Tue, 18 Jul 2023 10:26:45 +0200 Subject: [PATCH 12/21] Fixed the JSON property handling of empty http headers, within ODataJsonLightBatchPayloadItemPropertiesCache, refs: https://github.com/OData/odata.net/issues/2656 --- ...sonLightBatchPayloadItemPropertiesCache.cs | 4 +- .../ODataJsonLightBatchReaderTests.cs | 72 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 38de20f84c..c526644e6a 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,7 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -425,7 +425,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 1bf1432bf4..982a06b056 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,78 @@ await DoReadAsync( isResponse: false); } + [Fact] + public void ReadBatchRequestWithNullHeaders() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + try + { + while (jsonLightBatchReader.Read()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); + } + } + } + catch (NullReferenceException ex) + { + Assert.False(true, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithNullHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); + } + } + } + catch (NullReferenceException ex) + { + Assert.False(true, ex.Message); + } + }, + isResponse: false); + } + [Fact] public void ReadBatchRequestWithDuplicateProperties() { From 1b32931e1ace2c19f02ea9884be7f8d4c802eec9 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Tue, 18 Jul 2023 10:36:58 +0200 Subject: [PATCH 13/21] Resolved merge conflicts from remote upstream branch --- ...sonLightBatchPayloadItemPropertiesCache.cs | 92 +++++++++++++++---- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 38de20f84c..06d9cc9377 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -233,14 +233,26 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + string propertyValueString = this.jsonReader.ReadStringValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } } break; case PropertyNameStatus: { - jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } } break; @@ -256,7 +268,11 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } } break; @@ -273,7 +289,8 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + object headerValueObject = this.jsonReader.ReadPrimitiveValue(); + string headerValue = headerValueObject?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -287,12 +304,20 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } this.jsonReader.ReadEndObject(); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -306,7 +331,12 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } } break; @@ -383,16 +413,24 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); + string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } break; case PropertyNameStatus: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } break; @@ -408,7 +446,11 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } break; @@ -425,7 +467,8 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + object headerValueObject = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + string headerValue = headerValueObject?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -439,13 +482,21 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -459,7 +510,12 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } break; From e435745db3533f5b160fe1294deb588e408c4773 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 10 May 2023 15:54:10 +0200 Subject: [PATCH 14/21] Fixed the http attribute handling within ODataJsonLightBatchPayloadItemPropertiesCache.cs --- ...sonLightBatchPayloadItemPropertiesCache.cs | 86 +++++++++++++++---- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index c526644e6a..f89a396216 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -233,14 +233,26 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + string propertyValueString = this.jsonReader.ReadStringValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } } break; case PropertyNameStatus: { - jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } } break; @@ -256,7 +268,11 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } } break; @@ -287,12 +303,20 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } this.jsonReader.ReadEndObject(); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -306,7 +330,12 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } } break; @@ -383,16 +412,24 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); + string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueString); + } break; case PropertyNameStatus: - jsonProperties.Add( - propertyName, - await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); + object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, propertyValueObject); + } break; @@ -408,7 +445,11 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, dependsOnIds); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, dependsOnIds); + } break; @@ -439,13 +480,21 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - headers.Add(headerName, headerValue); + // Skip duplicate http headers + if (!headers.ContainsKeyOrdinal(headerName)) + { + headers.Add(headerName, headerValue); + } } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - jsonProperties.Add(propertyName, headers); + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, headers); + } if (!this.isStreamPopulated && bodyContentStream != null) { @@ -459,7 +508,12 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - jsonProperties.Add(propertyName, bodyContentStream); + + // Skip duplicate json properties + if (!jsonProperties.ContainsKey(propertyName)) + { + jsonProperties.Add(propertyName, bodyContentStream); + } break; From 664601d8b899ecfd389c0d882295dc1f07240d26 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 19 Jun 2023 18:44:15 +0200 Subject: [PATCH 15/21] Fixed duplicate json property handling ODataJsonLightBatchPayloadItemPropertiesCache, throwing an exception for duplicate properties, except header properties. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 93 ++++--------------- .../Microsoft.OData.Core.cs | 1 + .../Parameterized.Microsoft.OData.Core.cs | 8 ++ 3 files changed, 29 insertions(+), 73 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index f89a396216..37cd105b5c 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -220,12 +220,6 @@ private void ScanJsonProperties() // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(this.jsonReader.ReadPropertyName()); - // Throw an ODataException, if a duplicate json property was detected - if (jsonProperties.ContainsKey(propertyName)) - { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); - } - switch (propertyName) { case PropertyNameId: @@ -233,26 +227,14 @@ private void ScanJsonProperties() case PropertyNameMethod: case PropertyNameUrl: { - string propertyValueString = this.jsonReader.ReadStringValue(); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueString); - } + jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); } break; case PropertyNameStatus: { - object propertyValueObject = this.jsonReader.ReadPrimitiveValue(); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueObject); - } + jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); } break; @@ -268,11 +250,7 @@ private void ScanJsonProperties() this.jsonReader.ReadEndArray(); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, dependsOnIds); - } + jsonProperties.Add(propertyName, dependsOnIds); } break; @@ -303,20 +281,19 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - // Skip duplicate http headers if (!headers.ContainsKeyOrdinal(headerName)) { headers.Add(headerName, headerValue); } + else + { + headers[propertyName] = headerValue; + } } this.jsonReader.ReadEndObject(); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, headers); - } + jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { @@ -330,12 +307,7 @@ private void ScanJsonProperties() case PropertyNameBody: { bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, bodyContentStream); - } + jsonProperties.Add(propertyName, bodyContentStream); } break; @@ -403,7 +375,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); } switch (propertyName) @@ -412,24 +384,16 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() case PropertyNameAtomicityGroup: case PropertyNameMethod: case PropertyNameUrl: - string propertyValueString = await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueString); - } + jsonProperties.Add( + propertyName, + await this.asynchronousJsonReader.ReadStringValueAsync().ConfigureAwait(false)); break; case PropertyNameStatus: - object propertyValueObject = await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, propertyValueObject); - } + jsonProperties.Add( + propertyName, + await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)); break; @@ -445,11 +409,7 @@ await this.asynchronousJsonReader.ReadStartArrayAsync() await this.asynchronousJsonReader.ReadEndArrayAsync() .ConfigureAwait(false); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, dependsOnIds); - } + jsonProperties.Add(propertyName, dependsOnIds); break; @@ -480,21 +440,13 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() contentTypeHeader = headerValue; } - // Skip duplicate http headers - if (!headers.ContainsKeyOrdinal(headerName)) - { - headers.Add(headerName, headerValue); - } + headers[headerName] = headerValue; } await this.asynchronousJsonReader.ReadEndObjectAsync() .ConfigureAwait(false); - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, headers); - } + jsonProperties.Add(propertyName, headers); if (!this.isStreamPopulated && bodyContentStream != null) { @@ -508,12 +460,7 @@ await bodyContentStream.PopulateCachedBodyContentAsync(contentTypeHeader) case PropertyNameBody: bodyContentStream = await CreateJsonPayloadBodyContentStreamAsync(contentTypeHeader) .ConfigureAwait(false); - - // Skip duplicate json properties - if (!jsonProperties.ContainsKey(propertyName)) - { - jsonProperties.Add(propertyName, bodyContentStream); - } + jsonProperties.Add(propertyName, bodyContentStream); break; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index 637fd0bc56..256bf24045 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -193,6 +193,7 @@ internal sealed class TextRes { internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; + internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch"; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index 3d5b11e4e9..e333385a42 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1647,6 +1647,14 @@ internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitRea return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached, p0); } + /// + /// A string like "Duplicate property name '{0}' for message in batch." + /// + internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(object p0) + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch, p0); + } + /// /// A string like "Unknown property name '{0}' for message in batch." /// From 013504c3b626fe69859b3bbf2306f9875f24819f Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Tue, 20 Jun 2023 08:14:20 +0200 Subject: [PATCH 16/21] Little optimization at the json property header handling. --- ...taJsonLightBatchPayloadItemPropertiesCache.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 37cd105b5c..5e4cc0175c 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -269,26 +269,13 @@ private void ScanJsonProperties() string headerName = this.jsonReader.ReadPropertyName(); string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); - // Throw an ODataException, if a duplicate header was detected - if (headers.ContainsKeyOrdinal(headerName)) - { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); - } - // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - if (!headers.ContainsKeyOrdinal(headerName)) - { - headers.Add(headerName, headerValue); - } - else - { - headers[propertyName] = headerValue; - } + headers[propertyName] = headerValue; } this.jsonReader.ReadEndObject(); @@ -427,6 +414,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) From 674dfbffae687d50b04b5b28626ce9d9139a855d Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Wed, 21 Jun 2023 11:36:17 +0200 Subject: [PATCH 17/21] Minor fix, replacing duplicate http headers correctly in the non async method --- .../JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 5e4cc0175c..e8780120f8 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -275,7 +275,7 @@ private void ScanJsonProperties() contentTypeHeader = headerValue; } - headers[propertyName] = headerValue; + headers[headerName] = headerValue; } this.jsonReader.ReadEndObject(); From 1283f7953d3e73b40089f394b9bdf3b59937014d Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Fri, 23 Jun 2023 08:37:59 +0200 Subject: [PATCH 18/21] Changed the handling of duplicate json property headers, which will now also throw an exception. --- ...taJsonLightBatchPayloadItemPropertiesCache.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index e8780120f8..dee662c75c 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -269,13 +269,19 @@ private void ScanJsonProperties() string headerName = this.jsonReader.ReadPropertyName(); string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); + // Throw an ODataException, if a duplicate header was detected + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + } + // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - headers[headerName] = headerValue; + headers.Add(headerName, headerValue); } this.jsonReader.ReadEndObject(); @@ -422,13 +428,19 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } + // Throw an ODataException, if a duplicate header was detected + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + } + // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { contentTypeHeader = headerValue; } - headers[headerName] = headerValue; + headers.Add(headerName, headerValue); } await this.asynchronousJsonReader.ReadEndObjectAsync() From 792aa941d8e5215f2db519296b9741b5a0039c4e Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 26 Jun 2023 10:36:31 +0200 Subject: [PATCH 19/21] Renamed exception messages & added them to the resource file --- ...taJsonLightBatchPayloadItemPropertiesCache.cs | 16 ++++++++-------- src/Microsoft.OData.Core/Microsoft.OData.Core.cs | 1 - .../Parameterized.Microsoft.OData.Core.cs | 8 -------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index dee662c75c..09b15ddcc6 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -220,6 +220,12 @@ private void ScanJsonProperties() // Convert to upper case to support case-insensitive request property names string propertyName = Normalize(this.jsonReader.ReadPropertyName()); + // Throw an ODataException, if a duplicate json property was detected + if (jsonProperties.ContainsKey(propertyName)) + { + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); + } + switch (propertyName) { case PropertyNameId: @@ -272,7 +278,7 @@ private void ScanJsonProperties() // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } // Remember the Content-Type header value. @@ -368,7 +374,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() // Throw an ODataException, if a duplicate json property was detected if (jsonProperties.ContainsKey(propertyName)) { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(propertyName)); + throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(propertyName)); } switch (propertyName) @@ -428,12 +434,6 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(headerName)); } - // Throw an ODataException, if a duplicate header was detected - if (headers.ContainsKeyOrdinal(headerName)) - { - throw new ODataException(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForMessageInBatch(headerName)); - } - // Remember the Content-Type header value. if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index 256bf24045..637fd0bc56 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -193,7 +193,6 @@ internal sealed class TextRes { internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; - internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch"; internal const string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch"; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index e333385a42..3d5b11e4e9 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -1647,14 +1647,6 @@ internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitRea return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached, p0); } - /// - /// A string like "Duplicate property name '{0}' for message in batch." - /// - internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch(object p0) - { - return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForMessageInBatch, p0); - } - /// /// A string like "Unknown property name '{0}' for message in batch." /// From b2fc48fca085dc7adfb189761b33a6615697d829 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Mon, 17 Jul 2023 11:38:29 +0200 Subject: [PATCH 20/21] reverted ODataJsonLightBatchPayloadItemPropertiesCache null value handling of JSON property and removed corresponding unit tests. --- ...sonLightBatchPayloadItemPropertiesCache.cs | 5 +- .../ODataJsonLightBatchReaderTests.cs | 72 ------------------- 2 files changed, 2 insertions(+), 75 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 09b15ddcc6..38de20f84c 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,7 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -425,8 +425,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 982a06b056..1bf1432bf4 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,78 +363,6 @@ await DoReadAsync( isResponse: false); } - [Fact] - public void ReadBatchRequestWithNullHeaders() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - SetupJsonLightBatchReaderAndRunTest( - payload, - (jsonLightBatchReader) => - { - try - { - while (jsonLightBatchReader.Read()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); - // Verify that the Property "null-header" exists and it's value is set to NULL - var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); - Assert.NotNull(nullHeaderProperty.Key); - Assert.Null(nullHeaderProperty.Value); - } - } - } - catch (NullReferenceException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - - [Fact] - public async Task ReadBatchRequestWithNullHeadersAsync() - { - var payload = "{\"requests\": [{" + - "\"id\": \"1\"," + - "\"method\": \"POST\"," + - "\"url\": \"http://tempuri.org/Customers\"," + - "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + - "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; - - await SetupJsonLightBatchReaderAndRunTestAsync( - payload, - async (jsonLightBatchReader) => - { - try - { - while (await jsonLightBatchReader.ReadAsync()) - { - if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) - { - var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); - // Verify that the Property "null-header" exists and it's value is set to NULL - var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); - Assert.NotNull(nullHeaderProperty.Key); - Assert.Null(nullHeaderProperty.Value); - } - } - } - catch (NullReferenceException ex) - { - Assert.False(true, ex.Message); - } - }, - isResponse: false); - } - [Fact] public void ReadBatchRequestWithDuplicateProperties() { From 060b700d016f55b20252f4111f5d7cd20efdc1f1 Mon Sep 17 00:00:00 2001 From: "Piaskowski, Adrian Jan | Eddy" Date: Tue, 18 Jul 2023 10:26:45 +0200 Subject: [PATCH 21/21] Fixed the JSON property handling of empty http headers, within ODataJsonLightBatchPayloadItemPropertiesCache, refs: https://github.com/OData/odata.net/issues/2656 --- ...sonLightBatchPayloadItemPropertiesCache.cs | 4 +- .../ODataJsonLightBatchReaderTests.cs | 72 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs index 38de20f84c..c526644e6a 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -273,7 +273,7 @@ private void ScanJsonProperties() while (this.jsonReader.NodeType != JsonNodeType.EndObject) { string headerName = this.jsonReader.ReadPropertyName(); - string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + string headerValue = this.jsonReader.ReadPrimitiveValue()?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) @@ -425,7 +425,7 @@ await this.asynchronousJsonReader.ReadStartObjectAsync() { string headerName = await this.asynchronousJsonReader.ReadPropertyNameAsync() .ConfigureAwait(false); - string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false)).ToString(); + string headerValue = (await this.asynchronousJsonReader.ReadPrimitiveValueAsync().ConfigureAwait(false))?.ToString(); // Throw an ODataException, if a duplicate header was detected if (headers.ContainsKeyOrdinal(headerName)) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs index 1bf1432bf4..982a06b056 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightBatchReaderTests.cs @@ -363,6 +363,78 @@ await DoReadAsync( isResponse: false); } + [Fact] + public void ReadBatchRequestWithNullHeaders() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + SetupJsonLightBatchReaderAndRunTest( + payload, + (jsonLightBatchReader) => + { + try + { + while (jsonLightBatchReader.Read()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + var operationRequestMessage = jsonLightBatchReader.CreateOperationRequestMessage(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); + } + } + } + catch (NullReferenceException ex) + { + Assert.False(true, ex.Message); + } + }, + isResponse: false); + } + + [Fact] + public async Task ReadBatchRequestWithNullHeadersAsync() + { + var payload = "{\"requests\": [{" + + "\"id\": \"1\"," + + "\"method\": \"POST\"," + + "\"url\": \"http://tempuri.org/Customers\"," + + "\"headers\": {\"odata-version\":\"4.0\",\"content-type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\",\"null-header\":null}, " + + "\"body\": {\"@odata.type\":\"#NS.Customer\",\"Id\":1,\"Name\":\"Customer 1\",\"Type\":\"Retail\"}}]}"; + + await SetupJsonLightBatchReaderAndRunTestAsync( + payload, + async (jsonLightBatchReader) => + { + try + { + while (await jsonLightBatchReader.ReadAsync()) + { + if (jsonLightBatchReader.State == ODataBatchReaderState.Operation) + { + var operationRequestMessage = await jsonLightBatchReader.CreateOperationRequestMessageAsync(); + // Verify that the Property "null-header" exists and it's value is set to NULL + var nullHeaderProperty = operationRequestMessage.Headers.FirstOrDefault(p => p.Key == "null-header"); + Assert.NotNull(nullHeaderProperty.Key); + Assert.Null(nullHeaderProperty.Value); + } + } + } + catch (NullReferenceException ex) + { + Assert.False(true, ex.Message); + } + }, + isResponse: false); + } + [Fact] public void ReadBatchRequestWithDuplicateProperties() {