Skip to content

Commit

Permalink
Fixed the http attribute handling of Batch Requests (#2669)
Browse files Browse the repository at this point in the history
  • Loading branch information
apiaskowski authored Jul 17, 2023
1 parent 4414000 commit 75fa5a7
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -269,6 +275,12 @@ 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))
{
Expand Down Expand Up @@ -359,6 +371,12 @@ 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_DuplicatePropertyForRequestInBatch(propertyName));
}

switch (propertyName)
{
case PropertyNameId:
Expand Down Expand Up @@ -409,6 +427,12 @@ 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_DuplicateHeaderForRequestInBatch(headerName));
}

// Remember the Content-Type header value.
if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.OrdinalIgnoreCase))
{
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ internal sealed class TextRes {
internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput";
internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached";
internal const string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch = "ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch";
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";
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}?
Expand Down
16 changes: 16 additions & 0 deletions src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,22 @@ internal static string ODataJsonLightBatchPayloadItemPropertiesCache_UnknownProp
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_UnknownPropertyForMessageInBatch, p0);
}

/// <summary>
/// A string like "Duplicate property name '{0}' for request in batch."
/// </summary>
internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch(object p0)
{
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicatePropertyForRequestInBatch, p0);
}

/// <summary>
/// A string like "Duplicate header name '{0}' for request in batch."
/// </summary>
internal static string ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch(object p0)
{
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch, p0);
}

/// <summary>
/// A string like "Unexpected reader.NodeType: {0}."
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,134 @@ await DoReadAsync(
isResponse: false);
}

[Fact]
public void ReadBatchRequestWithDuplicateProperties()
{
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 = Assert.Throws<ODataException>(
() => 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<ODataException>(
() => SetupJsonLightBatchReaderAndRunTestAsync(
payload,
async (jsonLightBatchReader) =>
{
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 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<ODataException>(
() => 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 thrown no ODataException");
},
isResponse: false));

// Verify that the correct duplicate header has raised the ODataException
Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), exception.Message);
}

[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\"}}]}";

var exception = await Assert.ThrowsAsync<ODataException>(
() => SetupJsonLightBatchReaderAndRunTestAsync(
payload,
async (jsonLightBatchReader) =>
{
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");
},
isResponse: false));

// Verify that the correct duplicate header has raised the ODataException
Assert.Equal(Strings.ODataJsonLightBatchPayloadItemPropertiesCache_DuplicateHeaderForRequestInBatch("duplicate-header"), exception.Message);
}

[Fact]
public async Task ReadBatchResponseAsync()
{
Expand Down Expand Up @@ -977,6 +1105,22 @@ private async Task DoReadAsync(
}
}

/// <summary>
/// Sets up an ODataJsonLightBatchReader, then runs the given test code synchronously
/// </summary>
private void SetupJsonLightBatchReaderAndRunTest(
string payload,
Action<ODataJsonLightBatchReader> func,
bool isResponse = true)
{
using (var jsonLightInputContext = CreateJsonLightInputContext(payload, isAsync: false, isResponse: isResponse))
{
var jsonLightBatchReader = new ODataJsonLightBatchReader(jsonLightInputContext, synchronous: true);

func(jsonLightBatchReader);
}
}

/// <summary>
/// Sets up an ODataJsonLightBatchReader, then runs the given test code asynchronously
/// </summary>
Expand Down

0 comments on commit 75fa5a7

Please sign in to comment.