From 9e35332327b4f8be922a48e84fd2ed76ac1ca3ea Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:19:26 +0300 Subject: [PATCH 01/17] Generate stream paths for navigation properties --- src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs index 18c1791e..32adaafa 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs @@ -284,6 +284,10 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr } } } + + // Get possible navigation property stream paths + RetrieveMediaEntityStreamPaths(navEntityType, currentPath); + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { currentPath.Pop(); From bf234c9594aca289072121db05ca2d3c4a38c4ec Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:20:02 +0300 Subject: [PATCH 02/17] Refactor out stream path generation into separate function --- .../Edm/ODataPathProvider.cs | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs index 32adaafa..49ff1ef9 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs @@ -184,14 +184,44 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource } // media entity + RetrieveMediaEntityStreamPaths(entityType, path); + + // navigation property + foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties()) + { + if (CanFilter(np)) + { + RetrieveNavigationPropertyPaths(np, path); + } + } + + if (entitySet != null) + { + path.Pop(); // end of entity + } + + path.Pop(); // end of navigation source. + Debug.Assert(path.Any() == false); + } + + /// + /// Retrieves the paths for a media entity stream. + /// + /// The entity type. + /// The current OData path. + private void RetrieveMediaEntityStreamPaths(IEdmEntityType entityType, ODataPath currentPath) + { + Debug.Assert(entityType != null); + Debug.Assert(currentPath != null); + bool createValuePath = true; foreach (IEdmStructuralProperty sp in entityType.DeclaredStructuralProperties()) { if (sp.Type.AsPrimitive().IsStream()) { - path.Push(new ODataStreamPropertySegment(sp.Name)); - AppendPath(path.Clone()); - path.Pop(); + currentPath.Push(new ODataStreamPropertySegment(sp.Name)); + AppendPath(currentPath.Clone()); + currentPath.Pop(); } if (sp.Name.Equals("content", System.StringComparison.OrdinalIgnoreCase)) @@ -205,27 +235,10 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource */ if (createValuePath && entityType.HasStream) { - path.Push(new ODataStreamContentSegment()); - AppendPath(path.Clone()); - path.Pop(); - } - - // navigation property - foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties()) - { - if (CanFilter(np)) - { - RetrieveNavigationPropertyPaths(np, path); - } - } - - if (entitySet != null) - { - path.Pop(); // end of entity + currentPath.Push(new ODataStreamContentSegment()); + AppendPath(currentPath.Clone()); + currentPath.Pop(); } - - path.Pop(); // end of navigation source. - Debug.Assert(path.Any() == false); } /// From da8a2d2370b04f52bc819e53160d7481e38a42b3 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:32:56 +0300 Subject: [PATCH 03/17] Create base class for media entity operations handler --- .../MediaEntityOperationalHandler.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs new file mode 100644 index 00000000..e1c7afdd --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs @@ -0,0 +1,114 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for operation of media entity. + /// + internal abstract class MediaEntityOperationalHandler : NavigationPropertyOperationHandler + { + /// + /// Gets/sets the . + /// + protected IEdmEntitySet EntitySet { get; private set; } + + /// + /// Gets the . + /// + protected IEdmSingleton Singleton { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + // The first segment will either be an entity set navigation source or a singleton navigation source. + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet; + + if (EntitySet == null) + { + // Singleton + base.Initialize(context, path); + Singleton = NavigationSource as IEdmSingleton; + } + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + if (EntitySet == null) + { + // Singleton + base.SetTags(operation); + } + else // Entityset + { + string tagIdentifier = EntitySet.Name + "." + EntitySet.EntityType().Name; + + OpenApiTag tag = new OpenApiTag + { + Name = tagIdentifier + }; + + // Use an extension for TOC (Table of Content) + tag.Extensions.Add(Constants.xMsTocType, new OpenApiString("page")); + + operation.Tags.Add(tag); + + Context.AppendTag(tag); + } + } + + /// + protected override void SetExtensions(OpenApiOperation operation) + { + base.SetExtensions(operation); + } + + /// + /// Retrieves the operation Id for a navigation property stream path. + /// + /// The http method identifier name. + /// The stream segment identifier name. + /// + protected string GetOperationId(string prefix, string identifier) + { + Utils.CheckArgumentNull(prefix, nameof(prefix)); + Utils.CheckArgumentNull(identifier, nameof(identifier)); + + IList items = new List + { + NavigationSource.Name + }; + + var lastpath = Path.Segments.Last(c => c is ODataStreamContentSegment || c is ODataStreamPropertySegment); + foreach (var segment in Path.Segments.Skip(1)) + { + if (segment == lastpath) + { + items.Add(prefix + Utils.UpperFirstChar(identifier)); + break; + } + else + { + if (segment is ODataNavigationPropertySegment npSegment) + { + items.Add(npSegment.NavigationProperty.Name); + } + } + } + + return string.Join(".", items); + } + } +} From 39645b154fdb104b016162c03d617107a73e0ea2 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:37:15 +0300 Subject: [PATCH 04/17] Add new stream segments conditional checks when creating a NavPropPath --- .../Operation/NavigationPropertyOperationHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs index dab1643f..c183fa26 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs @@ -62,7 +62,8 @@ protected override void Initialize(ODataContext context, ODataPath path) NavigationProperty = path.OfType().Last().NavigationProperty; NavigationPropertyPath = string.Join("/", - Path.Segments.Where(s => !(s is ODataKeySegment || s is ODataNavigationSourceSegment)).Select(e => e.Identifier)); + Path.Segments.Where(s => !(s is ODataKeySegment || s is ODataNavigationSourceSegment + || s is ODataStreamContentSegment || s is ODataStreamPropertySegment)).Select(e => e.Identifier)); IEdmEntitySet entitySet = NavigationSource as IEdmEntitySet; IEdmSingleton singleton = NavigationSource as IEdmSingleton; @@ -115,7 +116,7 @@ protected override void SetTags(OpenApiOperation operation) } } } - + string name = string.Join(".", items); OpenApiTag tag = new OpenApiTag { From df4ac10e1ffa64c62654daac5ab52cf5a20a3400 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:41:52 +0300 Subject: [PATCH 05/17] Add Get and Put operation ids for single-valued nav. prop. paths --- .../MediaEntityGetOperationHandler.cs | 29 +++++++++++++++---- .../MediaEntityPutOperationHandler.cs | 29 +++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs index fa6cf701..de1d802f 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs @@ -17,7 +17,7 @@ namespace Microsoft.OpenApi.OData.Operation /// /// Retrieve a media content for an Entity /// - internal class MediaEntityGetOperationHandler : EntitySetOperationHandler + internal class MediaEntityGetOperationHandler : MediaEntityOperationalHandler { /// public override OperationType OperationType => OperationType.Get; @@ -25,16 +25,31 @@ internal class MediaEntityGetOperationHandler : EntitySetOperationHandler /// protected override void SetBasicInfo(OpenApiOperation operation) { - string typeName = EntitySet.EntityType().Name; - // Summary - operation.Summary = $"Get media content for {typeName} from {EntitySet.Name}"; + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.Summary = $"Get media content for {typeName} from {EntitySet.Name}"; + } + else + { + operation.Summary = $"Get media content for the navigation property {NavigationProperty.Name} from {NavigationSource.Name}"; + } // OperationId if (Context.Settings.EnableOperationId) { string identifier = Path.LastSegment.Kind == ODataSegmentKind.StreamContent ? "Content" : Path.LastSegment.Identifier; - operation.OperationId = EntitySet.Name + "." + typeName + ".Get" + Utils.UpperFirstChar(identifier); + + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = $"{EntitySet.Name}.{typeName}.Get{Utils.UpperFirstChar(identifier)}"; + } + else // Singleton + { + operation.OperationId = GetOperationId("Get", identifier); + } } base.SetBasicInfo(operation); @@ -86,7 +101,9 @@ protected override void SetResponses(OpenApiOperation operation) /// protected override void SetSecurity(OpenApiOperation operation) { - ReadRestrictionsType read = Context.Model.GetRecord(EntitySet, CapabilitiesConstants.ReadRestrictions); + ReadRestrictionsType read = EntitySet != null + ? Context.Model.GetRecord(EntitySet, CapabilitiesConstants.ReadRestrictions) + : Context.Model.GetRecord(Singleton, CapabilitiesConstants.ReadRestrictions); if (read == null) { return; diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs index a8ca7cf9..205e006e 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs @@ -17,7 +17,7 @@ namespace Microsoft.OpenApi.OData.Operation /// /// Update a media content for an Entity /// - internal class MediaEntityPutOperationHandler : EntitySetOperationHandler + internal class MediaEntityPutOperationHandler : MediaEntityOperationalHandler { /// public override OperationType OperationType => OperationType.Put; @@ -25,16 +25,31 @@ internal class MediaEntityPutOperationHandler : EntitySetOperationHandler /// protected override void SetBasicInfo(OpenApiOperation operation) { - string typeName = EntitySet.EntityType().Name; - // Summary - operation.Summary = $"Update media content for {typeName} in {EntitySet.Name}"; + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.Summary = $"Update media content for {typeName} in {EntitySet.Name}"; + } + else + { + operation.Summary = $"Update media content for the navigation property {NavigationProperty.Name} in {NavigationSource.Name}"; + } // OperationId if (Context.Settings.EnableOperationId) { string identifier = Path.LastSegment.Kind == ODataSegmentKind.StreamContent ? "Content" : Path.LastSegment.Identifier; - operation.OperationId = EntitySet.Name + "." + typeName + ".Update" + Utils.UpperFirstChar(identifier); + + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = $"{EntitySet.Name}.{typeName}.Update{Utils.UpperFirstChar(identifier)}"; + } + else + { + operation.OperationId = GetOperationId("Update", identifier); + } } base.SetBasicInfo(operation); @@ -92,7 +107,9 @@ protected override void SetResponses(OpenApiOperation operation) /// protected override void SetSecurity(OpenApiOperation operation) { - UpdateRestrictionsType update = Context.Model.GetRecord(EntitySet, CapabilitiesConstants.UpdateRestrictions); + UpdateRestrictionsType update = EntitySet != null + ? Context.Model.GetRecord(EntitySet, CapabilitiesConstants.UpdateRestrictions) + : Context.Model.GetRecord(Singleton, CapabilitiesConstants.UpdateRestrictions); if (update == null || update.Permissions == null) { return; From 1deb55d925e29c6f5aa2b45cbe36d3482be3bef0 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:44:59 +0300 Subject: [PATCH 06/17] Update handler with support for both Entityset/Singleton stream paths --- .../PathItem/MediaEntityPathItemHandler.cs | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs index 24d75c5c..d7ec6c34 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -12,15 +13,28 @@ namespace Microsoft.OpenApi.OData.PathItem /// /// Create a for a media entity. /// - internal class MediaEntityPathItemHandler : EntitySetPathItemHandler + internal class MediaEntityPathItemHandler : PathItemHandler { /// protected override ODataPathKind HandleKind => ODataPathKind.MediaEntity; + /// + /// Gets the entity set. + /// + protected IEdmEntitySet EntitySet { get; private set; } + + /// + /// Gets the singleton. + /// + protected IEdmSingleton Singleton { get; private set; } + /// protected override void SetOperations(OpenApiPathItem item) { - ReadRestrictionsType read = Context.Model.GetRecord(EntitySet); + ReadRestrictionsType read = EntitySet != null + ? Context.Model.GetRecord(EntitySet) + : Context.Model.GetRecord(Singleton); + if (read == null || (read.ReadByKeyRestrictions == null && read.IsReadable) || (read.ReadByKeyRestrictions != null && read.ReadByKeyRestrictions.IsReadable)) @@ -28,11 +42,29 @@ protected override void SetOperations(OpenApiPathItem item) AddOperation(item, OperationType.Get); } - UpdateRestrictionsType update = Context.Model.GetRecord(EntitySet); + UpdateRestrictionsType update = EntitySet != null + ? Context.Model.GetRecord(EntitySet) + : Context.Model.GetRecord(Singleton); + if (update == null || update.IsUpdatable) { AddOperation(item, OperationType.Put); } } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + base.Initialize(context, path); + + // The first segment could be an entity set segment or a singleton segment. + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + + EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet; + if (EntitySet == null) + { + Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton; + } + } } } From 468bf78c7fa87cc3b5dbd9fd28ad777533308be8 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 2 Sep 2020 23:46:23 +0300 Subject: [PATCH 07/17] Update tests to validate the stream paths updates --- .../Edm/ODataPathProviderTests.cs | 30 ++++++---- .../MediaEntityGetOperationHandlerTests.cs | 36 ++++++++++-- .../MediaEntityPutOperationHandlerTests.cs | 19 ++++++ .../MediaEntityPathItemHandlerTests.cs | 58 ++++++++++++++++--- 4 files changed, 120 insertions(+), 23 deletions(-) diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs index a740a7d9..e189e94a 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs @@ -44,7 +44,7 @@ public void GetPathsForGraphBetaModelReturnsAllPaths() // Assert Assert.NotNull(paths); - Assert.Equal(4457, paths.Count()); + Assert.Equal(4544, paths.Count()); } [Fact] @@ -192,7 +192,7 @@ public void GetPathsWithContainedNavigationPropertytWorks() "; - + string entitySet = @""; IEdmModel model = GetEdmModel(entityType, entitySet); ODataPathProvider provider = new ODataPathProvider(); @@ -229,21 +229,21 @@ public void GetPathsWithStreamPropertyAndWithEntityHasStreamWorks(bool hasStream if (hasStream && !streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)) { - Assert.Equal(4, paths.Count()); - Assert.Equal(new[] { "/Todos", "/Todos({Id})", "/Todos({Id})/$value", "/Todos({Id})/Logo" }, + Assert.Equal(7, paths.Count()); + Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/$value", "/Todos({Id})/Logo" }, paths.Select(p => p.GetPathItemName())); } else if ((hasStream && streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)) || (!hasStream && streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase))) { - Assert.Equal(3, paths.Count()); - Assert.Equal(new[] { "/Todos", "/Todos({Id})", "/Todos({Id})/Content" }, + Assert.Equal(6, paths.Count()); + Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/Content" }, paths.Select(p => p.GetPathItemName())); } else // !hasStream && !streamPropName.Equals("Content") { - Assert.Equal(3, paths.Count()); - Assert.Equal(new[] { "/Todos", "/Todos({Id})", "/Todos({Id})/Logo" }, + Assert.Equal(6, paths.Count()); + Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/Logo"}, paths.Select(p => p.GetPathItemName())); } } @@ -282,9 +282,17 @@ private static IEdmModel GetEdmModel(bool hasStream, string streamPropName) - - - + + + + + + + + + + + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs index 1e9f2522..ac6edb15 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs @@ -30,7 +30,9 @@ public void CreateMediaEntityGetOperationReturnsCorrectOperation(bool enableOper ODataContext context = new ODataContext(model, settings); IEdmEntitySet todos = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton me = model.EntityContainer.FindSingleton("me"); Assert.NotNull(todos); + Assert.NotNull(me); IEdmEntityType todo = model.SchemaElements.OfType().First(c => c.Name == "Todo"); IEdmStructuralProperty sp = todo.DeclaredStructuralProperties().First(c => c.Name == "Logo"); @@ -38,33 +40,51 @@ public void CreateMediaEntityGetOperationReturnsCorrectOperation(bool enableOper new ODataKeySegment(todos.EntityType()), new ODataStreamPropertySegment(sp.Name)); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + // Act var getOperation = _operationalHandler.CreateOperation(context, path); + var getOperation2 = _operationalHandler.CreateOperation(context, path2); // Assert Assert.NotNull(getOperation); + Assert.NotNull(getOperation2); Assert.Equal("Get media content for Todo from Todos", getOperation.Summary); + Assert.Equal("Get media content for the navigation property photo from me", getOperation2.Summary); Assert.NotNull(getOperation.Tags); + Assert.NotNull(getOperation2.Tags); + var tag = Assert.Single(getOperation.Tags); + var tag2 = Assert.Single(getOperation2.Tags); Assert.Equal("Todos.Todo", tag.Name); + Assert.Equal("me.profilePhoto", tag2.Name); Assert.NotNull(getOperation.Responses); + Assert.NotNull(getOperation2.Responses); Assert.Equal(2, getOperation.Responses.Count); + Assert.Equal(2, getOperation2.Responses.Count); Assert.Equal(new[] { "200", "default" }, getOperation.Responses.Select(r => r.Key)); + Assert.Equal(new[] { "200", "default" }, getOperation2.Responses.Select(r => r.Key)); if (enableOperationId) { Assert.Equal("Todos.Todo.GetLogo", getOperation.OperationId); + Assert.Equal("me.photo.GetContent", getOperation2.OperationId); } else { Assert.Null(getOperation.OperationId); + Assert.Null(getOperation2.OperationId); } } public static IEdmModel GetEdmModel() { - const string modelText = @" + string modelText = @" @@ -74,9 +94,17 @@ public static IEdmModel GetEdmModel() - - - + + + + + + + + + + + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs index 14fe92d4..88533c86 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs @@ -28,6 +28,7 @@ public void CreateEntityPutOperationReturnsCorrectOperation(bool enableOperation ODataContext context = new ODataContext(model, settings); IEdmEntitySet todos = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton me = model.EntityContainer.FindSingleton("me"); Assert.NotNull(todos); IEdmEntityType todo = model.SchemaElements.OfType().First(c => c.Name == "Todo"); @@ -36,27 +37,45 @@ public void CreateEntityPutOperationReturnsCorrectOperation(bool enableOperation new ODataKeySegment(todos.EntityType()), new ODataStreamPropertySegment(sp.Name)); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + // Act var getOperation = _operationalHandler.CreateOperation(context, path); + var getOperation2 = _operationalHandler.CreateOperation(context, path2); // Assert Assert.NotNull(getOperation); + Assert.NotNull(getOperation2); Assert.Equal("Update media content for Todo in Todos", getOperation.Summary); + Assert.Equal("Update media content for the navigation property photo in me", getOperation2.Summary); Assert.NotNull(getOperation.Tags); + Assert.NotNull(getOperation2.Tags); + var tag = Assert.Single(getOperation.Tags); + var tag2 = Assert.Single(getOperation2.Tags); Assert.Equal("Todos.Todo", tag.Name); + Assert.Equal("me.profilePhoto", tag2.Name); Assert.NotNull(getOperation.Responses); + Assert.NotNull(getOperation2.Responses); Assert.Equal(2, getOperation.Responses.Count); + Assert.Equal(2, getOperation2.Responses.Count); Assert.Equal(new[] { "204", "default" }, getOperation.Responses.Select(r => r.Key)); + Assert.Equal(new[] { "204", "default" }, getOperation2.Responses.Select(r => r.Key)); if (enableOperationId) { Assert.Equal("Todos.Todo.UpdateLogo", getOperation.OperationId); + Assert.Equal("me.photo.UpdateContent", getOperation2.OperationId); } else { Assert.Null(getOperation.OperationId); + Assert.Null(getOperation2.OperationId); } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs index 53f164f4..ee20c584 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs @@ -59,8 +59,10 @@ public void CreateMediaEntityPathItemReturnsCorrectItem() // Arrange IEdmModel model = GetEdmModel(""); ODataContext context = new ODataContext(model); - var entitySet = model.EntityContainer.FindEntitySet("Todos"); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton singleton = model.EntityContainer.FindSingleton("me"); Assert.NotNull(entitySet); // guard + Assert.NotNull(singleton); IEdmEntityType entityType = entitySet.EntityType(); IEdmStructuralProperty sp = entityType.DeclaredStructuralProperties().First(c => c.Name == "Logo"); @@ -68,14 +70,30 @@ public void CreateMediaEntityPathItemReturnsCorrectItem() new ODataKeySegment(entityType), new ODataStreamPropertySegment(sp.Name)); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(singleton), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + // Act var pathItem = _pathItemHandler.CreatePathItem(context, path); + var pathItem2 = _pathItemHandler.CreatePathItem(context, path2); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem2); Assert.NotNull(pathItem.Operations); + Assert.NotNull(pathItem2.Operations); Assert.NotEmpty(pathItem.Operations); + Assert.NotEmpty(pathItem2.Operations); Assert.Equal(2, pathItem.Operations.Count); + Assert.Equal(2, pathItem2.Operations.Count); Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Put }, pathItem.Operations.Select(o => o.Key)); + Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Put }, + pathItem2.Operations.Select(o => o.Key)); } [Theory] @@ -127,9 +145,10 @@ private void VerifyPathItemOperationsForStreamPropertySegment(string annotation, Assert.NotNull(entitySet); // guard IEdmEntityType entityType = entitySet.EntityType(); + IEdmStructuralProperty sp = entityType.DeclaredStructuralProperties().First(c => c.Name == "Logo"); ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entityType), - new ODataStreamContentSegment()); + new ODataStreamPropertySegment(sp.Name)); // Act var pathItem = _pathItemHandler.CreatePathItem(context, path); @@ -148,23 +167,35 @@ private void VerifyPathItemOperationsForStreamContentSegment(string annotation, IEdmModel model = GetEdmModel(annotation); ODataContext context = new ODataContext(model); IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton singleton = model.EntityContainer.FindSingleton("me"); Assert.NotNull(entitySet); // guard + Assert.NotNull(singleton); IEdmEntityType entityType = entitySet.EntityType(); - IEdmStructuralProperty sp = entityType.DeclaredStructuralProperties().First(c => c.Name == "Logo"); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(singleton), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entityType), - new ODataStreamPropertySegment(sp.Name)); + new ODataStreamContentSegment()); // Act var pathItem = _pathItemHandler.CreatePathItem(context, path); + var pathItem2 = _pathItemHandler.CreatePathItem(context, path2); // Assert Assert.NotNull(pathItem); + Assert.NotNull(pathItem2); Assert.NotNull(pathItem.Operations); + Assert.NotNull(pathItem2.Operations); Assert.NotEmpty(pathItem.Operations); + Assert.NotEmpty(pathItem2.Operations); Assert.Equal(expected, pathItem.Operations.Select(e => e.Key)); + Assert.Equal(expected, pathItem2.Operations.Select(e => e.Key)); } private IEdmModel GetEdmModel(string annotation) @@ -180,11 +211,22 @@ private IEdmModel GetEdmModel(string annotation) - - + + + + + + + + + + - - {0} + + {0} + + + {0} From 654ba30bcd1457fde23a4d55cb3871093bae9066 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 3 Sep 2020 00:01:38 +0300 Subject: [PATCH 08/17] Revert removed modifier --- .../Operation/MediaEntityGetOperationHandlerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs index ac6edb15..9d6bff92 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs @@ -84,7 +84,7 @@ public void CreateMediaEntityGetOperationReturnsCorrectOperation(bool enableOper public static IEdmModel GetEdmModel() { - string modelText = @" + const string modelText = @" From 4b0a872ff905bead6a1acea61035eb1fcd2c8fa0 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 7 Sep 2020 19:46:05 +0300 Subject: [PATCH 09/17] Add support for creating a media entity --- .../EntitySetPostOperationHandler.cs | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs index 5233ff25..a2d38b92 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -62,13 +62,25 @@ protected override void SetRequestBody(OpenApiOperation operation) }; } - // The requestBody field contains a Request Body Object for the request body - // that references the schema of the entity set’s entity type in the global schemas. - operation.RequestBody = new OpenApiRequestBody + IDictionary content; + + if (EntitySet.EntityType().HasStream) { - Required = true, - Description = "New entity", - Content = new Dictionary + // Support creating a media entity + content = new Dictionary + { + { + // TODO: Read the AcceptableMediaType annotation from CSDL + Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType + { + Schema = schema + } + } + }; + } + else + { + content = new Dictionary { { Constants.ApplicationJsonMediaType, new OpenApiMediaType @@ -76,7 +88,16 @@ protected override void SetRequestBody(OpenApiOperation operation) Schema = schema } } - } + }; + } + + // The requestBody field contains a Request Body Object for the request body + // that references the schema of the entity set’s entity type in the global schemas. + operation.RequestBody = new OpenApiRequestBody + { + Required = true, + Description = "New entity", + Content = content }; base.SetRequestBody(operation); @@ -104,6 +125,35 @@ protected override void SetResponses(OpenApiOperation operation) }; } + IDictionary content; + + if (EntitySet.EntityType().HasStream) + { + // Support creating a media entity + content = new Dictionary + { + { + // TODO: Read the AcceptableMediaType annotation from CSDL + Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType + { + Schema = schema + } + } + }; + } + else + { + content = new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = schema + } + } + }; + } + operation.Responses = new OpenApiResponses { { @@ -111,16 +161,7 @@ protected override void SetResponses(OpenApiOperation operation) new OpenApiResponse { Description = "Created entity", - Content = new Dictionary - { - { - Constants.ApplicationJsonMediaType, - new OpenApiMediaType - { - Schema = schema - } - } - } + Content = content } } }; From b8c74b3f4e68f2b085675795c784c2d7ea787237 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 7 Sep 2020 21:25:20 +0300 Subject: [PATCH 10/17] Update tests to validate POST operation for creating media entity --- .../EntitySetGetOperationHandlerTests.cs | 8 +++--- .../EntitySetPostOperationHandlerTests.cs | 28 ++++++++++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs index 6529d9f3..e921b39e 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs @@ -326,12 +326,12 @@ public void CreateEntitySetGetOperationReturnsSecurityForReadRestrictions(bool e } } - public static IEdmModel GetEdmModel(string annotation) + public static IEdmModel GetEdmModel(string annotation, bool hasStream = false) { const string template = @" - + @@ -341,12 +341,12 @@ public static IEdmModel GetEdmModel(string annotation) - {0} + {1} "; - string modelText = string.Format(template, annotation); + string modelText = string.Format(template, hasStream, annotation); IEdmModel model; IEnumerable errors; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs index ad15568d..4206ebef 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs @@ -6,6 +6,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -17,12 +18,14 @@ public class EntitySetPostOperationHandlerTests private EntitySetPostOperationHandler _operationHandler = new EntitySetPostOperationHandler(); [Theory] - [InlineData(true)] - [InlineData(false)] - public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOperationId) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOperationId, bool hasStream) { // Arrange - IEdmModel model = EntitySetGetOperationHandlerTests.GetEdmModel(""); + IEdmModel model = EntitySetGetOperationHandlerTests.GetEdmModel("", hasStream); IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers"); OpenApiConvertSettings settings = new OpenApiConvertSettings { @@ -47,6 +50,23 @@ public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOpera Assert.NotNull(post.Responses); Assert.Equal(2, post.Responses.Count); + if (hasStream) + { + // TODO: Read the AcceptableMediaType annotation from model + Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationOctetStreamMediaType)); + + Assert.NotNull(post.RequestBody); + // TODO: Read the AcceptableMediaType annotation from model + Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationOctetStreamMediaType)); + } + else + { + Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationJsonMediaType)); + + Assert.NotNull(post.RequestBody); + Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationJsonMediaType)); + } + if (enableOperationId) { Assert.Equal("Customers.Customer.CreateCustomer", post.OperationId); From 323ae20eb789b9606b8af41af8cfc73e3c062e72 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 7 Sep 2020 21:25:40 +0300 Subject: [PATCH 11/17] Comment update --- .../Operation/EntitySetPostOperationHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs index a2d38b92..b219489d 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -70,7 +70,7 @@ protected override void SetRequestBody(OpenApiOperation operation) content = new Dictionary { { - // TODO: Read the AcceptableMediaType annotation from CSDL + // TODO: Read the AcceptableMediaType annotation from model Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType { Schema = schema @@ -133,7 +133,7 @@ protected override void SetResponses(OpenApiOperation operation) content = new Dictionary { { - // TODO: Read the AcceptableMediaType annotation from CSDL + // TODO: Read the AcceptableMediaType annotation from model Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType { Schema = schema From d6dc0a37e24db63c9a4dfa558577e70b4cfc1f7a Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 9 Sep 2020 11:24:36 +0300 Subject: [PATCH 12/17] Refactor out common code into private methods --- .../EntitySetPostOperationHandler.cs | 164 +++++++----------- 1 file changed, 66 insertions(+), 98 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs index b219489d..e67714a0 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -43,61 +43,13 @@ protected override void SetBasicInfo(OpenApiOperation operation) /// protected override void SetRequestBody(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForRequestBody) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = EntitySet.EntityType().FullName() - } - }; - } - - IDictionary content; - - if (EntitySet.EntityType().HasStream) - { - // Support creating a media entity - content = new Dictionary - { - { - // TODO: Read the AcceptableMediaType annotation from model - Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType - { - Schema = schema - } - } - }; - } - else - { - content = new Dictionary - { - { - Constants.ApplicationJsonMediaType, new OpenApiMediaType - { - Schema = schema - } - } - }; - } - // The requestBody field contains a Request Body Object for the request body // that references the schema of the entity set’s entity type in the global schemas. operation.RequestBody = new OpenApiRequestBody { Required = true, Description = "New entity", - Content = content + Content = GetContentDescription() }; base.SetRequestBody(operation); @@ -106,54 +58,6 @@ protected override void SetRequestBody(OpenApiOperation operation) /// protected override void SetResponses(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForResponses) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = EntitySet.EntityType().FullName() - } - }; - } - - IDictionary content; - - if (EntitySet.EntityType().HasStream) - { - // Support creating a media entity - content = new Dictionary - { - { - // TODO: Read the AcceptableMediaType annotation from model - Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType - { - Schema = schema - } - } - }; - } - else - { - content = new Dictionary - { - { - Constants.ApplicationJsonMediaType, new OpenApiMediaType - { - Schema = schema - } - } - }; - } - operation.Responses = new OpenApiResponses { { @@ -161,7 +65,7 @@ protected override void SetResponses(OpenApiOperation operation) new OpenApiResponse { Description = "Created entity", - Content = content + Content = GetContentDescription() } } }; @@ -200,5 +104,69 @@ protected override void AppendCustomParameters(OpenApiOperation operation) AppendCustomParameters(operation, insert.CustomHeaders, ParameterLocation.Header); } } + + /// + /// Get the entity content description. + /// + /// The entity content description. + private IDictionary GetContentDescription() + { + OpenApiSchema schema = GetEntitySchema(); + + if (EntitySet.EntityType().HasStream) + { + // Support creating a media entity + return new Dictionary + { + { + // TODO: Read the AcceptableMediaType annotation from model + Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType + { + Schema = schema + } + } + }; + } + else + { + return new Dictionary + { + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = schema + } + } + }; + } + } + + /// + /// Get the entity schema. + /// + /// The entity schema. + private OpenApiSchema GetEntitySchema() + { + OpenApiSchema schema = null; + + if (Context.Settings.EnableDerivedTypesReferencesForRequestBody) + { + schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); + } + + if (schema == null) + { + schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + }; + } + + return schema; + } } } From e3ba126a70fe61f5c7de6599033013f221d9dfa4 Mon Sep 17 00:00:00 2001 From: Irvine Date: Wed, 9 Sep 2020 17:29:51 +0300 Subject: [PATCH 13/17] Remove unnecessary schema definition --- .../MediaEntityGetOperationHandler.cs | 22 +++++-------------- .../MediaEntityPutOperationHandler.cs | 22 +++++-------------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs index de1d802f..d0c8fa41 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs @@ -58,22 +58,6 @@ protected override void SetBasicInfo(OpenApiOperation operation) /// protected override void SetResponses(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForResponses) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Type = "string", - Format = "binary" - }; - } - operation.Responses = new OpenApiResponses { { @@ -87,7 +71,11 @@ protected override void SetResponses(OpenApiOperation operation) Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType { - Schema = schema + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs index 205e006e..d89dbdeb 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs @@ -58,22 +58,6 @@ protected override void SetBasicInfo(OpenApiOperation operation) /// protected override void SetRequestBody(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForRequestBody) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Type = "string", - Format = "binary" - }; - } - operation.RequestBody = new OpenApiRequestBody { Required = true, @@ -83,7 +67,11 @@ protected override void SetRequestBody(OpenApiOperation operation) { Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType { - Schema = schema + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } } } } From 43089093e0bbebe51f112f6fe8a83e6005e8b0e9 Mon Sep 17 00:00:00 2001 From: Irvine Date: Wed, 9 Sep 2020 21:13:35 +0300 Subject: [PATCH 14/17] Add application/json content type for RequestBody in media entity POST --- .../Operation/EntitySetPostOperationHandler.cs | 10 ++++++++++ .../Operation/EntitySetPostOperationHandlerTests.cs | 1 + 2 files changed, 11 insertions(+) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs index e67714a0..71f5e890 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -121,6 +121,16 @@ private IDictionary GetContentDescription() { // TODO: Read the AcceptableMediaType annotation from model Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } + } + }, + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType { Schema = schema } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs index 4206ebef..a8723fb8 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs @@ -58,6 +58,7 @@ public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOpera Assert.NotNull(post.RequestBody); // TODO: Read the AcceptableMediaType annotation from model Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationOctetStreamMediaType)); + Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationJsonMediaType)); } else { From 77333d7a6f0b8d0eb791afb6bb875da4679f5b69 Mon Sep 17 00:00:00 2001 From: Irvine Date: Wed, 9 Sep 2020 21:48:04 +0300 Subject: [PATCH 15/17] Add application/json content in RequestBody --- .../MediaEntityPutOperationHandler.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs index d89dbdeb..863b3ad7 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs @@ -58,6 +58,25 @@ protected override void SetBasicInfo(OpenApiOperation operation) /// protected override void SetRequestBody(OpenApiOperation operation) { + OpenApiSchema schema = null; + + if (Context.Settings.EnableDerivedTypesReferencesForRequestBody) + { + schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); + } + + if (schema == null) + { + schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet != null ? EntitySet.EntityType().FullName() : Singleton.EntityType().FullName() + } + }; + } + operation.RequestBody = new OpenApiRequestBody { Required = true, @@ -73,6 +92,13 @@ protected override void SetRequestBody(OpenApiOperation operation) Format = "binary" } } + }, + { + Constants.ApplicationJsonMediaType, + new OpenApiMediaType + { + Schema = schema + } } } }; From c3e4e0d895732d427aae307288b51a7328e1f17b Mon Sep 17 00:00:00 2001 From: Irvine Date: Wed, 9 Sep 2020 21:58:58 +0300 Subject: [PATCH 16/17] Nitpick: format code --- .../Operation/MediaEntityPutOperationHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs index 863b3ad7..a6a33a68 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs @@ -94,8 +94,7 @@ protected override void SetRequestBody(OpenApiOperation operation) } }, { - Constants.ApplicationJsonMediaType, - new OpenApiMediaType + Constants.ApplicationJsonMediaType, new OpenApiMediaType { Schema = schema } From 1889069f5071f66b557b330c4d273fd08e96975a Mon Sep 17 00:00:00 2001 From: Irvine Date: Wed, 9 Sep 2020 23:02:57 +0300 Subject: [PATCH 17/17] Add application/json content type for media entities RequestBody/Response elements --- .../EntitySetPostOperationHandler.cs | 42 ++++++------------- .../EntitySetPostOperationHandlerTests.cs | 1 + 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs index 71f5e890..9c87c52f 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -112,43 +112,27 @@ protected override void AppendCustomParameters(OpenApiOperation operation) private IDictionary GetContentDescription() { OpenApiSchema schema = GetEntitySchema(); + var content = new Dictionary(); if (EntitySet.EntityType().HasStream) { - // Support creating a media entity - return new Dictionary + // TODO: Read the AcceptableMediaType annotation from model + content.Add(Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType { + Schema = new OpenApiSchema { - // TODO: Read the AcceptableMediaType annotation from model - Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "string", - Format = "binary" - } - } - }, - { - Constants.ApplicationJsonMediaType, new OpenApiMediaType - { - Schema = schema - } + Type = "string", + Format = "binary" } - }; + }); } - else + + content.Add(Constants.ApplicationJsonMediaType, new OpenApiMediaType { - return new Dictionary - { - { - Constants.ApplicationJsonMediaType, new OpenApiMediaType - { - Schema = schema - } - } - }; - } + Schema = schema + }); + + return content; } /// diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs index a8723fb8..8f5fb295 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs @@ -54,6 +54,7 @@ public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOpera { // TODO: Read the AcceptableMediaType annotation from model Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationOctetStreamMediaType)); + Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationJsonMediaType)); Assert.NotNull(post.RequestBody); // TODO: Read the AcceptableMediaType annotation from model