diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs index 3bd6ca5bdf..08270b3b08 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs @@ -7,12 +7,12 @@ namespace Microsoft.OData.JsonLight { #region Namespaces + using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; - using Microsoft.OData.Metadata; using Microsoft.OData.Edm; using Microsoft.OData.Json; + using Microsoft.OData.Metadata; #endregion Namespaces @@ -70,8 +70,8 @@ protected override bool ReadAtStartImplementation() this.jsonLightParameterDeserializer.ReadPayloadStart( ODataPayloadKind.Parameter, this.propertyAndAnnotationCollector, - /*isReadingNestedPayload*/false, - /*allowEmptyPayload*/true); + isReadingNestedPayload: false, + allowEmptyPayload: true); return this.ReadAtStartImplementationSynchronously(); } @@ -86,7 +86,7 @@ protected override bool ReadAtStartImplementation() /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. /// - protected override Task ReadAtStartImplementationAsync() + protected override async Task ReadAtStartImplementationAsync() { Debug.Assert(this.State == ODataParameterReaderState.Start, "this.State == ODataParameterReaderState.Start"); Debug.Assert(this.jsonLightParameterDeserializer.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None"); @@ -96,14 +96,14 @@ protected override Task ReadAtStartImplementationAsync() // The parameter payload looks like "{ param1 : value1, ..., paramN : valueN }", where each value can be primitive, complex, collection, entity, resource set or collection. // Position the reader on the first node - return this.jsonLightParameterDeserializer.ReadPayloadStartAsync( + await this.jsonLightParameterDeserializer.ReadPayloadStartAsync( ODataPayloadKind.Parameter, this.propertyAndAnnotationCollector, - /*isReadingNestedPayload*/false, - /*allowEmptyPayload*/true) + isReadingNestedPayload: false, + allowEmptyPayload: true).ConfigureAwait(false); - .FollowOnSuccessWith(t => - this.ReadAtStartImplementationSynchronously()); + return await this.ReadAtStartInternalImplementationAsync() + .ConfigureAwait(false); } /// @@ -131,9 +131,17 @@ protected override bool ReadNextParameterImplementation() /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. /// - protected override Task ReadNextParameterImplementationAsync() + protected override async Task ReadNextParameterImplementationAsync() { - return TaskUtils.GetTaskForSynchronousOperation(this.ReadNextParameterImplementationSynchronously); + Debug.Assert( + this.State != ODataParameterReaderState.Start && + this.State != ODataParameterReaderState.Exception && + this.State != ODataParameterReaderState.Completed, + "The current state must not be Start, Exception or Completed."); + + this.PopScope(this.State); + return await this.jsonLightParameterDeserializer.ReadNextParameterAsync(this.propertyAndAnnotationCollector) + .ConfigureAwait(false); } /// @@ -153,7 +161,17 @@ protected override ODataReader CreateResourceReader(IEdmStructuredType expectedR /// An to read the resource value of type . protected override Task CreateResourceReaderAsync(IEdmStructuredType expectedResourceType) { - return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceReaderSynchronously(expectedResourceType)); + Debug.Assert(expectedResourceType != null, "expectedResourceType != null"); + + return Task.FromResult( + new ODataJsonLightReader( + this.jsonLightInputContext, + navigationSource: null, + expectedResourceType: expectedResourceType, + readingResourceSet: false, + readingParameter: true, + readingDelta: false, + listener: this)); } /// @@ -173,7 +191,17 @@ protected override ODataReader CreateResourceSetReader(IEdmStructuredType expect /// An to read the resource set value of type . protected override Task CreateResourceSetReaderAsync(IEdmStructuredType expectedResourceType) { - return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceSetReaderSynchronously(expectedResourceType)); + Debug.Assert(expectedResourceType != null, "expectedResourceType != null"); + + return Task.FromResult( + new ODataJsonLightReader( + this.jsonLightInputContext, + navigationSource: null, + expectedResourceType: expectedResourceType, + readingResourceSet: true, + readingParameter: true, + readingDelta: false, + listener: this)); } /// @@ -203,7 +231,11 @@ protected override ODataCollectionReader CreateCollectionReader(IEdmTypeReferenc /// protected override Task CreateCollectionReaderAsync(IEdmTypeReference expectedItemTypeReference) { - return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateCollectionReaderSynchronously(expectedItemTypeReference)); + Debug.Assert(this.jsonLightInputContext.Model.IsUserModel(), "Should have verified that we created the parameter reader with a user model."); + Debug.Assert(expectedItemTypeReference != null, "expectedItemTypeReference != null"); + + return Task.FromResult( + new ODataJsonLightCollectionReader(this.jsonLightInputContext, expectedItemTypeReference, listener: this)); } /// @@ -288,5 +320,28 @@ private ODataCollectionReader CreateCollectionReaderSynchronously(IEdmTypeRefere Debug.Assert(expectedItemTypeReference != null, "expectedItemTypeReference != null"); return new ODataJsonLightCollectionReader(this.jsonLightInputContext, expectedItemTypeReference, this /*IODataReaderListener*/); } + + /// + /// Asynchronous implementation of the reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + private async Task ReadAtStartInternalImplementationAsync() + { + if (this.jsonLightInputContext.JsonReader.NodeType == JsonNodeType.EndOfInput) + { + this.PopScope(ODataParameterReaderState.Start); + this.EnterScope(ODataParameterReaderState.Completed, null, null); + return false; + } + + return await this.jsonLightParameterDeserializer.ReadNextParameterAsync(this.propertyAndAnnotationCollector) + .ConfigureAwait(false); + } } } diff --git a/src/Microsoft.OData.Core/ODataParameterReader.cs b/src/Microsoft.OData.Core/ODataParameterReader.cs index 3d3e824083..f176378b9a 100644 --- a/src/Microsoft.OData.Core/ODataParameterReader.cs +++ b/src/Microsoft.OData.Core/ODataParameterReader.cs @@ -7,7 +7,8 @@ namespace Microsoft.OData { #region Namespaces - using System.Diagnostics.CodeAnalysis; + + using System; using System.Threading.Tasks; #endregion Namespaces @@ -75,5 +76,50 @@ public abstract object Value /// Asynchronously reads the next item from the message payload. /// A task that when completed indicates whether more items were read. public abstract Task ReadAsync(); + + /// + /// This method asynchronously creates an to read the resource value when the state is ODataParameterReaderState.Resource. + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains an to read the resource value when the state is ODataParameterReaderState.Resource. + /// + /// + /// When the state is ODataParameterReaderState.Resource, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + public virtual Task CreateResourceReaderAsync() + { + throw new NotImplementedException(); + } + + /// + /// This method asynchronously creates an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + /// + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + /// + /// + /// When the state is ODataParameterReaderState.ResourceSet, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + public virtual Task CreateResourceSetReaderAsync() + { + throw new NotImplementedException(); + } + + /// Asynchronously creates an to read the collection value when the state is ODataParameterReaderState.Collection. + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains an to read the collection value when the state is ODataParameterReaderState.Collection. + /// + /// + /// When the state is ODataParameterReaderState.Collection, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + public virtual Task CreateCollectionReaderAsync() + { + throw new NotImplementedException(); + } } } diff --git a/src/Microsoft.OData.Core/ODataParameterReaderCore.cs b/src/Microsoft.OData.Core/ODataParameterReaderCore.cs index 2da68dd3dc..16d8b2a408 100644 --- a/src/Microsoft.OData.Core/ODataParameterReaderCore.cs +++ b/src/Microsoft.OData.Core/ODataParameterReaderCore.cs @@ -7,6 +7,7 @@ namespace Microsoft.OData { #region Namespaces + using System; using System.Collections.Generic; using System.Diagnostics; @@ -15,6 +16,7 @@ namespace Microsoft.OData using System.Threading.Tasks; using Microsoft.OData.Edm; using Microsoft.OData.Metadata; + #endregion Namespaces /// @@ -35,7 +37,7 @@ internal abstract class ODataParameterReaderCore : ODataParameterReader, IODataR private readonly HashSet parametersRead = new HashSet(StringComparer.Ordinal); /// Tracks the state of the sub-reader. - private SubReaderState subReaderState; + protected SubReaderState subReaderState; /// /// Constructor. @@ -53,7 +55,7 @@ protected ODataParameterReaderCore( } /// Enum to track the state of the sub-reader. - private enum SubReaderState + protected enum SubReaderState { /// No sub-reader has been created for the current parameter. None, @@ -182,7 +184,7 @@ public override ODataCollectionReader CreateCollectionReader() public override sealed bool Read() { this.VerifyCanRead(true); - return this.InterceptException(this.ReadSynchronously); + return this.InterceptException((thisParam) => thisParam.ReadSynchronously()); } /// @@ -192,7 +194,7 @@ public override sealed bool Read() public override sealed Task ReadAsync() { this.VerifyCanRead(false); - return this.ReadAsynchronously().FollowOnFaultWith(t => this.EnterScope(ODataParameterReaderState.Exception, null, null)); + return this.InterceptExceptionAsync((thisParam) => thisParam.ReadAsynchronously()); } /// @@ -432,6 +434,26 @@ protected virtual Task ReadAsynchronously() return TaskUtils.GetTaskForSynchronousOperation(this.ReadImplementation); } + /// + /// Verifies that one of CreateResourceReader(), CreateResourceSetReader(), CreateCollectionReader(), + /// CreateResourceReaderAsync(), CreateResourceSetReaderAsync() or CreateCollectionReaderAsync() can be called. + /// + /// The expected state of the reader. + protected void VerifyCanCreateSubReader(ODataParameterReaderState expectedState) + { + this.inputContext.VerifyNotDisposed(); + if (this.State != expectedState) + { + throw new ODataException(Strings.ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState(ODataParameterReaderCore.GetCreateReaderMethodName(expectedState), this.State)); + } + + if (this.subReaderState != SubReaderState.None) + { + Debug.Assert(this.Name != null, "this.Name != null"); + throw new ODataException(Strings.ODataParameterReaderCore_CreateReaderAlreadyCalled(ODataParameterReaderCore.GetCreateReaderMethodName(expectedState), this.Name)); + } + } + /// /// Gets the corresponding create reader method name for the given state. /// @@ -444,21 +466,26 @@ private static string GetCreateReaderMethodName(ODataParameterReaderState state) } /// - /// Verifies that one of CreateResourceReader(), CreateResourceSetReader() or CreateCollectionReader() can be called. + /// Catch any exception thrown by the action passed in; in the exception case move the reader into + /// state ExceptionThrown and then rethrow the exception. /// - /// The expected state of the reader. - private void VerifyCanCreateSubReader(ODataParameterReaderState expectedState) + /// The type returned from the to execute. + /// The action to execute. + /// The result of executing the . + private T InterceptException(Func action) { - this.inputContext.VerifyNotDisposed(); - if (this.State != expectedState) + try { - throw new ODataException(Strings.ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState(ODataParameterReaderCore.GetCreateReaderMethodName(expectedState), this.State)); + return action(this); } - - if (this.subReaderState != SubReaderState.None) + catch (Exception e) { - Debug.Assert(this.Name != null, "this.Name != null"); - throw new ODataException(Strings.ODataParameterReaderCore_CreateReaderAlreadyCalled(ODataParameterReaderCore.GetCreateReaderMethodName(expectedState), this.Name)); + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.EnterScope(ODataParameterReaderState.Exception, null, null); + } + + throw; } } @@ -466,14 +493,17 @@ private void VerifyCanCreateSubReader(ODataParameterReaderState expectedState) /// Catch any exception thrown by the action passed in; in the exception case move the reader into /// state ExceptionThrown and then rethrow the exception. /// - /// The type returned from the to execute. + /// The type returned from the /// The action to execute. - /// The result of executing the . - private T InterceptException(Func action) + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains the result of executing the . + /// + private async Task InterceptExceptionAsync(Func> action) { try { - return action(); + return await action(this).ConfigureAwait(false); } catch (Exception e) { diff --git a/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs b/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs index bcdaf1eb8b..1bb9c1181d 100644 --- a/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs +++ b/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs @@ -7,10 +7,11 @@ namespace Microsoft.OData { #region Namespaces + using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.OData.Edm; + #endregion Namespaces /// @@ -30,6 +31,72 @@ protected ODataParameterReaderCoreAsync( { } + /// + /// This method asynchronously creates an to read the resource value when the state is ODataParameterReaderState.Resource. + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains an to read the resource value when the state is ODataParameterReaderState.Resource. + /// + /// + /// When the state is ODataParameterReaderState.Resource, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + /// + public override async Task CreateResourceReaderAsync() + { + this.VerifyCanCreateSubReader(ODataParameterReaderState.Resource); + this.subReaderState = SubReaderState.Active; + Debug.Assert(this.Name != null, "this.Name != null"); + Debug.Assert(this.Value == null, "this.Value == null"); + IEdmStructuredType expectedResourceType = (IEdmStructuredType)this.GetParameterTypeReference(this.Name).Definition; + return await this.CreateResourceReaderAsync(expectedResourceType) + .ConfigureAwait(false); + } + + /// + /// This method asynchronously creates an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + /// + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + /// + /// + /// When the state is ODataParameterReaderState.ResourceSet, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + public override async Task CreateResourceSetReaderAsync() + { + this.VerifyCanCreateSubReader(ODataParameterReaderState.ResourceSet); + this.subReaderState = SubReaderState.Active; + Debug.Assert(this.Name != null, "this.Name != null"); + Debug.Assert(this.Value == null, "this.Value == null"); + IEdmStructuredType expectedResourceType = (IEdmStructuredType)((IEdmCollectionType)this.GetParameterTypeReference(this.Name).Definition).ElementType.Definition; + return await this.CreateResourceSetReaderAsync(expectedResourceType) + .ConfigureAwait(false); + } + + /// + /// This method asynchronously creates an to read the collection value when the state is ODataParameterReaderState.Collection. + /// + /// + /// A task that represents the asynchronous operation. + /// The value of the TResult parameter contains an to read the collection value when the state is ODataParameterReaderState.Collection. + /// + /// + /// When the state is ODataParameterReaderState.Collection, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + public override async Task CreateCollectionReaderAsync() + { + this.VerifyCanCreateSubReader(ODataParameterReaderState.Collection); + this.subReaderState = SubReaderState.Active; + Debug.Assert(this.Name != null, "this.Name != null"); + Debug.Assert(this.Value == null, "this.Value == null"); + IEdmTypeReference expectedItemTypeReference = ((IEdmCollectionType)this.GetParameterTypeReference(this.Name).Definition).ElementType; + return await this.CreateCollectionReaderAsync(expectedItemTypeReference) + .ConfigureAwait(false); + } + /// /// Implementation of the parameter reader logic when in state 'Start'. /// @@ -69,50 +136,42 @@ protected ODataParameterReaderCoreAsync( /// A task that when completed indicates whether more items were read. /// The base class already implements this but only for fully synchronous readers, the implementation here /// allows fully asynchronous readers. - protected override Task ReadAsynchronously() + protected override async Task ReadAsynchronously() { + bool result; + switch (this.State) { case ODataParameterReaderState.Start: -#if DEBUG - return this.ReadAtStartImplementationAsync() - .FollowOnSuccessWith(t => - { - Debug.Assert( - this.State == ODataParameterReaderState.Value || - this.State == ODataParameterReaderState.Resource || - this.State == ODataParameterReaderState.ResourceSet || - this.State == ODataParameterReaderState.Collection || - this.State == ODataParameterReaderState.Completed, - "ReadAtStartImplementationAsync should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); - return t.Result; - }); -#else - return this.ReadAtStartImplementationAsync(); -#endif + result = await this.ReadAtStartImplementationAsync() + .ConfigureAwait(false); + Debug.Assert( + this.State == ODataParameterReaderState.Value || + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || + this.State == ODataParameterReaderState.Collection || + this.State == ODataParameterReaderState.Completed, + "ReadAtStartImplementationAsync should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); + + return result; case ODataParameterReaderState.Value: // fall through case ODataParameterReaderState.Resource: case ODataParameterReaderState.ResourceSet: case ODataParameterReaderState.Collection: this.OnParameterCompleted(); -#if DEBUG - return this.ReadNextParameterImplementationAsync() - .FollowOnSuccessWith(t => - { - Debug.Assert( - this.State == ODataParameterReaderState.Value || - this.State == ODataParameterReaderState.Resource || - this.State == ODataParameterReaderState.ResourceSet || - this.State == ODataParameterReaderState.Collection || - this.State == ODataParameterReaderState.Completed, - "ReadNextParameterImplementationAsync should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); - return t.Result; - }); -#else - return this.ReadNextParameterImplementationAsync(); -#endif + result = await this.ReadNextParameterImplementationAsync() + .ConfigureAwait(false); + + Debug.Assert( + this.State == ODataParameterReaderState.Value || + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || + this.State == ODataParameterReaderState.Collection || + this.State == ODataParameterReaderState.Completed, + "ReadNextParameterImplementationAsync should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); + return result; case ODataParameterReaderState.Exception: // fall through case ODataParameterReaderState.Completed: Debug.Assert(false, "This case should have been caught earlier."); diff --git a/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt index d15221a3be..d0135c4e35 100644 --- a/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI.Unshipped.txt @@ -2344,6 +2344,9 @@ virtual Microsoft.OData.ODataOutputContext.WriteError(Microsoft.OData.ODataError virtual Microsoft.OData.ODataOutputContext.WriteErrorAsync(Microsoft.OData.ODataError odataError, bool includeDebugInformation) -> System.Threading.Tasks.Task virtual Microsoft.OData.ODataOutputContext.WriteProperty(Microsoft.OData.ODataProperty odataProperty) -> void virtual Microsoft.OData.ODataOutputContext.WritePropertyAsync(Microsoft.OData.ODataProperty odataProperty) -> System.Threading.Tasks.Task +virtual Microsoft.OData.ODataParameterReader.CreateCollectionReaderAsync() -> System.Threading.Tasks.Task +virtual Microsoft.OData.ODataParameterReader.CreateResourceReaderAsync() -> System.Threading.Tasks.Task +virtual Microsoft.OData.ODataParameterReader.CreateResourceSetReaderAsync() -> System.Threading.Tasks.Task virtual Microsoft.OData.ODataPayloadValueConverter.ConvertFromPayloadValue(object value, Microsoft.OData.Edm.IEdmTypeReference edmTypeReference) -> object virtual Microsoft.OData.ODataPayloadValueConverter.ConvertToPayloadValue(object value, Microsoft.OData.Edm.IEdmTypeReference edmTypeReference) -> object virtual Microsoft.OData.ODataPropertyInfo.PrimitiveTypeKind.get -> Microsoft.OData.Edm.EdmPrimitiveTypeKind diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightParameterReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightParameterReaderTests.cs index 657f1f9880..f8683a734b 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightParameterReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightParameterReaderTests.cs @@ -9,7 +9,9 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using Microsoft.OData.Edm; +using Microsoft.OData.JsonLight; using Microsoft.Test.OData.Utils.ODataLibTest; using Xunit; @@ -525,7 +527,7 @@ public void ReadFeedAndProperty() var item = Assert.Single(result.Values); Assert.Equal("property", item.Key); - Assert.Equal("value", item.Value); + Assert.Equal("value", item.Value); } [Fact] @@ -669,6 +671,1011 @@ public void ReadContainedFeed() Assert.Equal("TestName", entry.Properties.ElementAt(1).Value); } + [Fact] + public async Task ReadEmptyParameterPayloadAsync() + { + var payload = "{}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + async (jsonLightParameterReader) => + { + await DoReadAsync(jsonLightParameterReader); + + Assert.Equal(ODataParameterReaderState.Completed, jsonLightParameterReader.State); + }); + } + + [Fact] + public async Task ReadPrimitiveParameterAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + + var payload = "{\"rating\":4}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync(jsonLightParameterReader, verifyValueAction: (value) => Assert.Equal(4, value))); + } + + [Fact] + public async Task ReadEmptyNullableNonOptionalParameterAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(true)); + + var payload = "{ }"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync(jsonLightParameterReader)); + } + + [Fact] + public async Task ReadEmptyNonNullableNonOptionalParameterAsync_ThrowsException() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + + var payload = "{ }"; + + var exception = await Assert.ThrowsAsync( + () => SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync(jsonLightParameterReader))); + + Assert.Equal( + Strings.ODataParameterReaderCore_ParametersMissingInPayload("ActionImport", "rating"), + exception.Message); + } + + [Fact] + public async Task ReadOptionalParameterAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + this.action.AddOptionalParameter("comment", EdmCoreModel.Instance.GetString(false)); + + var payload = "{\"rating\":4,\"comment\":\"Great product!\"}"; + + var valueActionStack = new Stack(new object[] { "Great product!", 4 }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => + { + Assert.NotEmpty(valueActionStack); + Assert.Equal(valueActionStack.Pop(), value); + })); + + Assert.Empty(valueActionStack); + } + + [Fact] + public async Task ReadParameterPayalodWithMissingOptionalParameterAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + this.action.AddOptionalParameter("comment", EdmCoreModel.Instance.GetString(false)); + + var payload = "{\"rating\":4}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync(jsonLightParameterReader, verifyValueAction: (value) => Assert.Equal(4, value))); + } + + [Fact] + public async Task ReadMultiplePrimitiveParametersAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + this.action.AddParameter("comment", EdmCoreModel.Instance.GetString(false)); + + var payload = "{\"rating\":4,\"comment\":\"Great product!\"}"; + + var valueActionStack = new Stack(new object[] { "Great product!", 4 }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => + { + Assert.NotEmpty(valueActionStack); + Assert.Equal(valueActionStack.Pop(), value); + })); + + Assert.Empty(valueActionStack); + } + + [Fact] + public async Task ReadPrimitiveCollectionParameterAsync() + { + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); + this.action.AddParameter("ratings", parameterEdmTypeReference); + + var payload = "{\"ratings\":[4,3]}"; + + var collectionItemsStack = new Stack(new int[] { 3, 4 }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyCollectionAction: async (collectionReader) => + { + while (await collectionReader.ReadAsync()) + { + switch(collectionReader.State) + { + case ODataCollectionReaderState.Value: + Assert.NotEmpty(collectionItemsStack); + Assert.Equal(collectionItemsStack.Pop(), collectionReader.Item); + break; + } + } + })); + } + + [Fact] + public async Task ReadRandomlyOrderedParametersAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + this.action.AddParameter("comment", EdmCoreModel.Instance.GetString(false)); + + var payload = "{\"comment\":\"Great product!\",\"rating\":4}"; + + var verifyValueActionStack = new Stack(new object[] { 4, "Great product!" }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => + { + Assert.NotEmpty(verifyValueActionStack); + Assert.Equal(verifyValueActionStack.Pop(), value); + })); + + Assert.Empty(verifyValueActionStack); + } + + [Fact] + public async Task ReadPrimitiveAndPrimitiveCollectionParametersAsync() + { + this.action.AddParameter("comment", EdmCoreModel.Instance.GetString(false)); + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetInt32(false))); + this.action.AddParameter("ratings", parameterEdmTypeReference); + + var payload = "{\"comment\":\"Great product!\",\"ratings\":[4,3]}"; + + var collectionItemsStack = new Stack(new int[] { 3, 4 }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => Assert.Equal("Great product!", value), + verifyCollectionAction: async (collectionReader) => + { + while (await collectionReader.ReadAsync()) + { + switch (collectionReader.State) + { + case ODataCollectionReaderState.Value: + Assert.NotEmpty(collectionItemsStack); + Assert.Equal(collectionItemsStack.Pop(), collectionReader.Item); + break; + } + } + })); + } + + [Fact] + public async Task ReadTypeDefinitionAndTypeDefinitionCollectionParametersAsync() + { + var moneyTypeDefinition = new EdmTypeDefinition("NS", "Money", EdmPrimitiveTypeKind.Decimal); + this.referencedModel.AddElement(moneyTypeDefinition); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmTypeDefinitionReference(moneyTypeDefinition, false))); + this.action.AddParameter("price", new EdmTypeDefinitionReference(moneyTypeDefinition, false)); + this.action.AddParameter("discounts", parameterEdmTypeReference); + + var payload = "{\"price\":13.5,\"discounts\":[1.5,2.5]}"; + + var collectionItemsStack = new Stack(new decimal[] { 2.5M, 1.5M }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => Assert.Equal(13.5M, value), + verifyCollectionAction: async (collectionReader) => + { + while (await collectionReader.ReadAsync()) + { + switch (collectionReader.State) + { + case ODataCollectionReaderState.Value: + Assert.NotEmpty(collectionItemsStack); + Assert.Equal(collectionItemsStack.Pop(), collectionReader.Item); + break; + } + } + })); + } + + [Fact] + public async Task ReadParameterWithTypeDetectionAsync() + { + this.action.AddParameter("rating", EdmCoreModel.Instance.GetDouble(false)); + + var payload = "{\"rating\":3.5}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => + { + Assert.IsType(value); + Assert.Equal(3.5D, value); + })); + } + + [Fact] + public async Task ReadComplexParameterAsync() + { + var addressComplexType = new EdmComplexType("NS", "Address"); + addressComplexType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(addressComplexType); + + this.action.AddParameter("address", new EdmComplexTypeReference(addressComplexType, false)); + + var payload = "{\"address\":{\"Street\":\"One Way\"}}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Address", resource.TypeName); + var streetProperty = Assert.Single(resource.Properties); + Assert.Equal("Street", streetProperty.Name); + Assert.Equal("One Way", streetProperty.Value); + }))); + } + + [Fact] + public async Task ReadDerivedComplexParameterAsync() + { + var addressComplexType = new EdmComplexType("NS", "Address"); + addressComplexType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String); + var buildingAddressComplexType = new EdmComplexType("NS", "BuildingAddress", addressComplexType); + buildingAddressComplexType.AddStructuralProperty("Building", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(buildingAddressComplexType); + + this.action.AddParameter("address", new EdmComplexTypeReference(addressComplexType, false)); + + var payload = "{\"address\":{\"@odata.type\":\"#NS.BuildingAddress\",\"Building\":\"Studio A\",\"Street\":\"One Way\"}}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.BuildingAddress", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Building", properties[0].Name); + Assert.Equal("Studio A", properties[0].Value); + Assert.Equal("Street", properties[1].Name); + Assert.Equal("One Way", properties[1].Value); + }))); + } + + [Fact] + public async Task ReadComplexCollectionParameterAsync() + { + var addressComplexType = new EdmComplexType("NS", "Address"); + addressComplexType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(addressComplexType); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmComplexTypeReference(addressComplexType, false))); + this.action.AddParameter("addresses", parameterEdmTypeReference); + + var payload = "{\"addresses\":[{\"Street\":\"One Way\"},{\"Street\":\"Two Way\"}]}"; + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Address", resource.TypeName); + var streetProperty = Assert.Single(resource.Properties); + Assert.Equal("Street", streetProperty.Name); + Assert.Equal("Two Way", streetProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Address", resource.TypeName); + var streetProperty = Assert.Single(resource.Properties); + Assert.Equal("Street", streetProperty.Name); + Assert.Equal("One Way", streetProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }))); + + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadDerivedComplexCollectionParameterAsync() + { + var addressComplexType = new EdmComplexType("NS", "Address"); + addressComplexType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String); + var buildingAddressComplexType = new EdmComplexType("NS", "BuildingAddress", addressComplexType); + buildingAddressComplexType.AddStructuralProperty("Building", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(buildingAddressComplexType); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmComplexTypeReference(addressComplexType, false))); + this.action.AddParameter("addresses", parameterEdmTypeReference); + + var payload = "{\"addresses\":[{\"@odata.type\":\"#NS.BuildingAddress\",\"Building\":\"Studio A\",\"Street\":\"One Way\"},{\"Street\":\"Two Way\"}]}"; + + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Address", resource.TypeName); + var streetProperty = Assert.Single(resource.Properties); + Assert.Equal("Street", streetProperty.Name); + Assert.Equal("Two Way", streetProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.BuildingAddress", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Building", properties[0].Name); + Assert.Equal("Studio A", properties[0].Value); + Assert.Equal("Street", properties[1].Name); + Assert.Equal("One Way", properties[1].Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }))); + + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadEntityParameterAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + customerEntityType.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + + var payload = "{\"customer\":{\"Id\":1,\"Name\":\"Sue\"}}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal(1, properties[0].Value); + Assert.Equal("Name", properties[1].Name); + Assert.Equal("Sue", properties[1].Value); + }))); + } + + [Fact] + public async Task ReadNullEntityParameterAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + + var payload = "{\"customer\":null}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => Assert.Null(resource)))); + } + + [Fact] + public async Task ReadDerivedEntityParameterAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + var enterpriseCustomerEntityType = new EdmEntityType("NS", "EnterpriseCustomer", customerEntityType); + enterpriseCustomerEntityType.AddStructuralProperty("CreditLimit", EdmPrimitiveTypeKind.Decimal); + this.referencedModel.AddElement(enterpriseCustomerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + + var payload = "{\"customer\":{\"@odata.type\":\"#NS.EnterpriseCustomer\",\"Id\":1,\"CreditLimit\":170}}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.EnterpriseCustomer", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal(1, properties[0].Value); + Assert.Equal("CreditLimit", properties[1].Name); + Assert.Equal(170M, properties[1].Value); + }))); + } + + [Fact] + public async Task ReadOpenEntityParameterAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer", baseType: null, isAbstract: false, isOpen: true); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + + var payload = "{\"customer\":{\"Id\":1,\"DynamicProp@odata.type\":\"#Edm.Decimal\",\"DynamicProp\":310}}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal(1, properties[0].Value); + Assert.Equal("DynamicProp", properties[1].Name); + Assert.Equal(310M, properties[1].Value); + }))); + } + + [Fact] + public async Task ReadEntityCollectionParameterAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + customerEntityType.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(customerEntityType); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customerEntityType, false))); + this.action.AddParameter("customers", parameterEdmTypeReference); + + var payload = "{\"customers\":[{\"Id\":1,\"Name\":\"Sue\"}]}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal(1, properties[0].Value); + Assert.Equal("Name", properties[1].Name); + Assert.Equal("Sue", properties[1].Value); + }))); + } + + [Fact] + public async Task ReadMultipleEntityCollectionParametersAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.referencedModel.AddElement(customerEntityType); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customerEntityType, false))); + this.action.AddParameter("customer1", parameterEdmTypeReference); + this.action.AddParameter("customer2", parameterEdmTypeReference); + + var payload = "{\"customer1\":[{\"Id\":1}],\"customer2\":[{\"Id\":2}]}"; + + var resourceSetCount = 0; + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(2, idProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: async (jsonLightReader) => + { + resourceSetCount++; + await DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }); + })); + + Assert.Equal(2, resourceSetCount); + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadDerivedEntityCollectionParametersAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + var enterpriseCustomerEntityType = new EdmEntityType("NS", "EnterpriseCustomer", customerEntityType); + enterpriseCustomerEntityType.AddStructuralProperty("CreditLimit", EdmPrimitiveTypeKind.Decimal); + this.referencedModel.AddElement(enterpriseCustomerEntityType); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customerEntityType, false))); + this.action.AddParameter("customer1", parameterEdmTypeReference); + this.action.AddParameter("customer2", parameterEdmTypeReference); + + var payload = "{\"customer1\":[{\"Id\":1}],\"customer2\":[{\"@odata.type\":\"#NS.EnterpriseCustomer\",\"Id\":2,\"CreditLimit\":130}]}"; + + var resourceSetCount = 0; + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.EnterpriseCustomer", resource.TypeName); + var properties = resource.Properties.ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal(2, properties[0].Value); + Assert.Equal("CreditLimit", properties[1].Name); + Assert.Equal(130M, properties[1].Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: async (jsonLightReader) => + { + resourceSetCount++; + await DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }); + })); + + Assert.Equal(2, resourceSetCount); + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadPrimitiveAndEntityParametersAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + this.action.AddParameter("rating", EdmCoreModel.Instance.GetInt32(false)); + + var payload = "{\"customer\":{\"Id\":1},\"rating\":4}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => Assert.Equal(4, value), + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }))); + } + + [Fact] + public async Task ReadEnumAndEntityParametersAsync() + { + var colorEnumType = new EdmEnumType("NS", "Color"); + colorEnumType.AddMember("Black", new EdmEnumMemberValue(1)); + colorEnumType.AddMember("White", new EdmEnumMemberValue(2)); + this.referencedModel.AddElement(colorEnumType); + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + this.action.AddParameter("favoriteColor", new EdmEnumTypeReference(colorEnumType, false)); + + var payload = "{\"customer\":{\"Id\":1},\"favoriteColor\":\"Black\"}"; + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyValueAction: (value) => + { + var favoriteColor = Assert.IsType(value); + Assert.Equal("Black", favoriteColor.Value); + }, + verifyResourceAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }))); + } + + [Fact] + public async Task ReadComplexAndEntityParametersAsync() + { + var addressComplexType = new EdmComplexType("NS", "Address"); + addressComplexType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String); + this.referencedModel.AddElement(addressComplexType); + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + customerEntityType.AddStructuralProperty("PhysicalAddress", new EdmComplexTypeReference(addressComplexType, false)); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + this.action.AddParameter("address", new EdmComplexTypeReference(addressComplexType, false)); + + var payload = "{\"customer\":{\"Id\":1,\"PhysicalAddress\":{\"Street\":\"One Way\"}},\"address\":{\"Street\":\"Two Way\"}}"; + + var outerResourceCount = 0; + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Address", resource.TypeName); + var streetProperty = Assert.Single(resource.Properties); + Assert.Equal("Street", streetProperty.Name); + Assert.Equal("Two Way", streetProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Address", resource.TypeName); + var streetProperty = Assert.Single(resource.Properties); + Assert.Equal("Street", streetProperty.Name); + Assert.Equal("One Way", streetProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceAction: async (jsonLightReader) => + { + outerResourceCount++; + await DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }); + })); + + Assert.Equal(2, outerResourceCount); + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadEntityAndEntityCollectionParametersAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.referencedModel.AddElement(customerEntityType); + + this.action.AddParameter("customer", new EdmEntityTypeReference(customerEntityType, false)); + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customerEntityType, false))); + this.action.AddParameter("competitors", parameterEdmTypeReference); + + var payload = "{\"customer\":{\"Id\":1},\"competitors\":[{\"Id\":2}]}"; + + var resourceCount = 0; + var resourceSetCount = 0; + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(2, idProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: async (jsonLightReader) => + { + resourceSetCount++; + await DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }); + }, + verifyResourceAction: async (jsonLightReader) => + { + resourceCount++; + await DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }); + })); + + Assert.Equal(1, resourceSetCount); + Assert.Equal(1, resourceCount); + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadEntityCollectionParameterWithNestedEntityAsync() + { + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + var orderEntityType = new EdmEntityType("NS", "Order"); + orderEntityType.AddKeys(orderEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + orderEntityType.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo { Name = "Customer", Target = customerEntityType, TargetMultiplicity = EdmMultiplicity.One }); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(orderEntityType, false))); + this.action.AddParameter("orders", parameterEdmTypeReference); + + var payload = "{\"orders\":[{\"Id\":7,\"Customer\":{\"Id\":1}}]}"; + + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Order", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(7, idProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }))); + + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadEntityCollectionParameterWithNestedContainedEntityAsync() + { + var nextOfKinEntityType = new EdmEntityType("NS", "NextOfKin"); + nextOfKinEntityType.AddKeys(nextOfKinEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + customerEntityType.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo { Name = "NextOfKin", Target = nextOfKinEntityType, TargetMultiplicity = EdmMultiplicity.One, ContainsTarget = true }); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customerEntityType, false))); + this.action.AddParameter("customers", parameterEdmTypeReference); + + var payload = "{\"customers\":[{\"Id\":1,\"NextOfKin\":{\"Id\":13}}]}"; + + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.NextOfKin", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(13, idProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }))); + + Assert.Empty(verifyResourceActionStack); + } + + [Fact] + public async Task ReadEntityCollectionParameterWithNestedContainedEntityCollectionAsync() + { + var nextOfKinEntityType = new EdmEntityType("NS", "Subsidiary"); + nextOfKinEntityType.AddKeys(nextOfKinEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + customerEntityType.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo { Name = "Subsidiaries", Target = nextOfKinEntityType, TargetMultiplicity = EdmMultiplicity.Many, ContainsTarget = true }); + + var parameterEdmTypeReference = new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customerEntityType, false))); + this.action.AddParameter("customers", parameterEdmTypeReference); + + var payload = "{\"customers\":[{\"Id\":1,\"Subsidiaries\":[{\"Id\":13}]}]}"; + + var verifyResourceActionStack = new Stack>(); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Customer", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(1, idProperty.Value); + }); + verifyResourceActionStack.Push((resource) => + { + Assert.NotNull(resource); + Assert.Equal("NS.Subsidiary", resource.TypeName); + var idProperty = Assert.Single(resource.Properties); + Assert.Equal("Id", idProperty.Name); + Assert.Equal(13, idProperty.Value); + }); + + await SetupJsonLightParameterReaderAndRunTestAsync( + payload, + (jsonLightParameterReader) => DoReadAsync( + jsonLightParameterReader, + verifyResourceSetAction: (jsonLightReader) => DoReadAsync( + jsonLightReader, + verifyResourceAction: (resource) => + { + Assert.NotEmpty(verifyResourceActionStack); + var innerVerifyResourceAction = verifyResourceActionStack.Pop(); + innerVerifyResourceAction(resource); + }))); + + Assert.Empty(verifyResourceActionStack); + } + private ParameterReaderResult RunParameterReaderTest(string payload) { var message = new InMemoryMessage(); @@ -757,6 +1764,118 @@ private ParameterReaderResult RunParameterReaderTest(string payload) return parameterReaderResult; } + private ODataJsonLightInputContext CreateJsonLightInputContext(string payload, bool isAsync = false, bool isResponse = true) + { + var messageInfo = new ODataMessageInfo + { + MediaType = new ODataMediaType("application", "json"), +#if NETCOREAPP1_1 + Encoding = Encoding.GetEncoding(0), +#else + Encoding = Encoding.Default, +#endif + IsResponse = isResponse, + IsAsync = isAsync, + Model = this.model + }; + + return new ODataJsonLightInputContext(new StringReader(payload), messageInfo, new ODataMessageReaderSettings()); + } + + /// + /// Sets up an ODataJsonLightParameterReader, then runs the given test code asynchronously + /// + private async Task SetupJsonLightParameterReaderAndRunTestAsync( + string payload, + Func func) + { + using (var jsonLightInputContext = CreateJsonLightInputContext(payload, isAsync: true, isResponse: false)) + { + var jsonLightParameterReader = new ODataJsonLightParameterReader( + jsonLightInputContext, + this.action); + + await func(jsonLightParameterReader); + } + } + + private async Task DoReadAsync( + ODataJsonLightParameterReader jsonLightParameterReader, + Action verifyValueAction = null, + Func verifyCollectionAction = null, + Func verifyResourceAction = null, + Func verifyResourceSetAction = null) + { + while (await jsonLightParameterReader.ReadAsync()) + { + switch(jsonLightParameterReader.State) + { + case ODataParameterReaderState.Value: + if (verifyValueAction != null) + { + verifyValueAction(jsonLightParameterReader.Value); + } + + break; + case ODataParameterReaderState.Collection: + if (verifyCollectionAction != null) + { + await verifyCollectionAction(await jsonLightParameterReader.CreateCollectionReaderAsync()); + } + + break; + case ODataParameterReaderState.Resource: + if (verifyResourceAction != null) + { + await verifyResourceAction(await jsonLightParameterReader.CreateResourceReaderAsync() as ODataJsonLightReader); + } + + break; + case ODataParameterReaderState.ResourceSet: + if (verifyResourceSetAction != null) + { + await verifyResourceSetAction(await jsonLightParameterReader.CreateResourceSetReaderAsync() as ODataJsonLightReader); + } + + break; + default: + break; + } + } + } + private async Task DoReadAsync( + ODataJsonLightReader jsonLightReader, + Action verifyResourceSetAction = null, + Action verifyResourceAction = null) + { + while (await jsonLightReader.ReadAsync()) + { + switch (jsonLightReader.State) + { + case ODataReaderState.ResourceSetStart: + break; + case ODataReaderState.ResourceSetEnd: + if (verifyResourceSetAction != null) + { + verifyResourceSetAction(jsonLightReader.Item as ODataResourceSet); + } + + break; + case ODataReaderState.ResourceStart: + break; + case ODataReaderState.ResourceEnd: + if (verifyResourceAction != null) + { + verifyResourceAction(jsonLightReader.Item as ODataResource); + } + + break; + default: + break; + } + } + } + private class ParameterReaderResult { public IList> Values { get; set; }