[Fix] Generate paths for stream properties in base types of entities (#110)

* Get all properties declared in type def. including base types

* Update/refactor tests to validate stream props. of base types are captured

* Refactor Handler class to use encapsulation to define navigation sources
This commit is contained in:
Irvine Sunday 2021-08-18 16:33:26 +03:00 committed by GitHub
parent e8ecb37c96
commit e6a5e52e11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 75 deletions

View file

@ -219,7 +219,7 @@ namespace Microsoft.OpenApi.OData.Edm
Debug.Assert(currentPath != null); Debug.Assert(currentPath != null);
bool createValuePath = true; bool createValuePath = true;
foreach (IEdmStructuralProperty sp in entityType.DeclaredStructuralProperties()) foreach (IEdmStructuralProperty sp in entityType.StructuralProperties())
{ {
if (sp.Type.AsPrimitive().IsStream()) if (sp.Type.AsPrimitive().IsStream())
{ {
@ -228,7 +228,7 @@ namespace Microsoft.OpenApi.OData.Edm
currentPath.Pop(); currentPath.Pop();
} }
if (sp.Name.Equals("content", System.StringComparison.OrdinalIgnoreCase)) if (sp.Name.Equals("content", StringComparison.OrdinalIgnoreCase))
{ {
createValuePath = false; createValuePath = false;
} }

View file

@ -26,15 +26,9 @@ namespace Microsoft.OpenApi.OData.Operation
protected override void SetBasicInfo(OpenApiOperation operation) protected override void SetBasicInfo(OpenApiOperation operation)
{ {
// Summary // Summary
if (IsNavigationPropertyPath) operation.Summary = IsNavigationPropertyPath
{ ? $"Get media content for the navigation property {NavigationProperty.Name} from {NavigationSource.Name}"
operation.Summary = $"Get media content for the navigation property {NavigationProperty.Name} from {NavigationSource.Name}"; : $"Get media content for {NavigationSourceSegment.EntityType.Name} from {NavigationSourceSegment.Identifier}";
}
else
{
IEdmEntityType entityType = EntitySet.EntityType();
operation.Summary = $"Get media content for {entityType.Name} from {EntitySet.Name}";
}
// Description // Description
IEdmVocabularyAnnotatable annotatableElement = GetAnnotatableElement(); IEdmVocabularyAnnotatable annotatableElement = GetAnnotatableElement();
@ -73,9 +67,8 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/> /// <inheritdoc/>
protected override void SetSecurity(OpenApiOperation operation) protected override void SetSecurity(OpenApiOperation operation)
{ {
ReadRestrictionsType read = EntitySet != null IEdmVocabularyAnnotatable annotatableNavigationSource = (IEdmVocabularyAnnotatable)NavigationSourceSegment.NavigationSource;
? Context.Model.GetRecord<ReadRestrictionsType>(EntitySet, CapabilitiesConstants.ReadRestrictions) ReadRestrictionsType read = Context.Model.GetRecord<ReadRestrictionsType>(annotatableNavigationSource, CapabilitiesConstants.ReadRestrictions);
: Context.Model.GetRecord<ReadRestrictionsType>(Singleton, CapabilitiesConstants.ReadRestrictions);
if (read == null) if (read == null)
{ {
return; return;

View file

@ -21,14 +21,9 @@ namespace Microsoft.OpenApi.OData.Operation
internal abstract class MediaEntityOperationalHandler : NavigationPropertyOperationHandler internal abstract class MediaEntityOperationalHandler : NavigationPropertyOperationHandler
{ {
/// <summary> /// <summary>
/// Gets/sets the <see cref="IEdmEntitySet"/>. /// Gets/Sets the NavigationSource segment
/// </summary> /// </summary>
protected IEdmEntitySet EntitySet { get; private set; } protected ODataNavigationSourceSegment NavigationSourceSegment { get; private set; }
/// <summary>
/// Gets the <see cref="IEdmSingleton"/>.
/// </summary>
protected IEdmSingleton Singleton { get; private set; }
/// <summary> /// <summary>
/// Gets/Sets flag indicating whether path is navigation property path /// Gets/Sets flag indicating whether path is navigation property path
@ -39,13 +34,7 @@ namespace Microsoft.OpenApi.OData.Operation
protected override void Initialize(ODataContext context, ODataPath path) protected override void Initialize(ODataContext context, ODataPath path)
{ {
// The first segment will either be an EntitySet navigation source or a Singleton navigation source // The first segment will either be an EntitySet navigation source or a Singleton navigation source
ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; NavigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment;
EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet;
if (EntitySet == null)
{
Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton;
}
// Check whether path is a navigation property path // Check whether path is a navigation property path
IsNavigationPropertyPath = Path.Segments.Contains( IsNavigationPropertyPath = Path.Segments.Contains(
@ -67,9 +56,9 @@ namespace Microsoft.OpenApi.OData.Operation
} }
else else
{ {
string tagIdentifier = EntitySet.Name + "." + EntitySet.EntityType().Name; string tagIdentifier = NavigationSourceSegment.Identifier + "." + NavigationSourceSegment.EntityType.Name;
OpenApiTag tag = new OpenApiTag OpenApiTag tag = new()
{ {
Name = tagIdentifier Name = tagIdentifier
}; };
@ -102,7 +91,7 @@ namespace Microsoft.OpenApi.OData.Operation
IList<string> items = new List<string> IList<string> items = new List<string>
{ {
EntitySet?.Name ?? Singleton.Name NavigationSourceSegment.Identifier
}; };
ODataSegment lastSegment = Path.Segments.Last(c => c is ODataStreamContentSegment || c is ODataStreamPropertySegment); ODataSegment lastSegment = Path.Segments.Last(c => c is ODataStreamContentSegment || c is ODataStreamPropertySegment);
@ -112,7 +101,7 @@ namespace Microsoft.OpenApi.OData.Operation
{ {
if (!IsNavigationPropertyPath) if (!IsNavigationPropertyPath)
{ {
string typeName = EntitySet?.EntityType().Name ?? Singleton.EntityType().Name; string typeName = NavigationSourceSegment.EntityType.Name;
items.Add(typeName); items.Add(typeName);
items.Add(prefix + Utils.UpperFirstChar(identifier)); items.Add(prefix + Utils.UpperFirstChar(identifier));
} }
@ -185,7 +174,7 @@ namespace Microsoft.OpenApi.OData.Operation
/// <returns>The annotable element.</returns> /// <returns>The annotable element.</returns>
protected IEdmVocabularyAnnotatable GetAnnotatableElement() protected IEdmVocabularyAnnotatable GetAnnotatableElement()
{ {
IEdmEntityType entityType = EntitySet != null ? EntitySet.EntityType() : Singleton.EntityType(); IEdmEntityType entityType = NavigationSourceSegment.EntityType;
ODataSegment lastSegmentProp = Path.Segments.LastOrDefault(c => c is ODataStreamPropertySegment); ODataSegment lastSegmentProp = Path.Segments.LastOrDefault(c => c is ODataStreamPropertySegment);
if (lastSegmentProp == null) if (lastSegmentProp == null)
@ -214,7 +203,7 @@ namespace Microsoft.OpenApi.OData.Operation
private IEdmStructuralProperty GetStructuralProperty(IEdmEntityType entityType, string identifier) private IEdmStructuralProperty GetStructuralProperty(IEdmEntityType entityType, string identifier)
{ {
return entityType.DeclaredStructuralProperties().FirstOrDefault(x => x.Name.Equals(identifier)); return entityType.StructuralProperties().FirstOrDefault(x => x.Name.Equals(identifier));
} }
private IEdmNavigationProperty GetNavigationProperty(IEdmEntityType entityType, string identifier) private IEdmNavigationProperty GetNavigationProperty(IEdmEntityType entityType, string identifier)

View file

@ -26,15 +26,9 @@ namespace Microsoft.OpenApi.OData.Operation
protected override void SetBasicInfo(OpenApiOperation operation) protected override void SetBasicInfo(OpenApiOperation operation)
{ {
// Summary // Summary
if (IsNavigationPropertyPath) operation.Summary = IsNavigationPropertyPath
{ ? $"Update media content for the navigation property {NavigationProperty.Name} in {NavigationSource.Name}"
operation.Summary = $"Update media content for the navigation property {NavigationProperty.Name} in {NavigationSource.Name}"; : $"Update media content for {NavigationSourceSegment.EntityType.Name} in {NavigationSourceSegment.Identifier}";
}
else
{
string typeName = EntitySet.EntityType().Name;
operation.Summary = $"Update media content for {typeName} in {EntitySet.Name}";
}
// Description // Description
IEdmVocabularyAnnotatable annotatableElement = GetAnnotatableElement(); IEdmVocabularyAnnotatable annotatableElement = GetAnnotatableElement();
@ -79,9 +73,8 @@ namespace Microsoft.OpenApi.OData.Operation
/// <inheritdoc/> /// <inheritdoc/>
protected override void SetSecurity(OpenApiOperation operation) protected override void SetSecurity(OpenApiOperation operation)
{ {
UpdateRestrictionsType update = EntitySet != null IEdmVocabularyAnnotatable annotatableNavigationSource = (IEdmVocabularyAnnotatable)NavigationSourceSegment.NavigationSource;
? Context.Model.GetRecord<UpdateRestrictionsType>(EntitySet, CapabilitiesConstants.UpdateRestrictions) UpdateRestrictionsType update = Context.Model.GetRecord<UpdateRestrictionsType>(annotatableNavigationSource, CapabilitiesConstants.UpdateRestrictions);
: Context.Model.GetRecord<UpdateRestrictionsType>(Singleton, CapabilitiesConstants.UpdateRestrictions);
if (update == null || update.Permissions == null) if (update == null || update.Permissions == null)
{ {
return; return;

View file

@ -450,41 +450,52 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
} }
[Theory] [Theory]
[InlineData(true, "Logo")] [InlineData(true, "logo")]
[InlineData(false, "Logo")] [InlineData(false, "logo")]
[InlineData(true, "Content")] [InlineData(true, "content")]
[InlineData(false, "Content")] [InlineData(false, "content")]
public void GetPathsWithStreamPropertyAndWithEntityHasStreamWorks(bool hasStream, string streamPropName) public void GetPathsWithStreamPropertyAndWithEntityHasStreamWorks(bool hasStream, string streamPropName)
{ {
// Arrange // Arrange
IEdmModel model = GetEdmModel(hasStream, streamPropName); IEdmModel model = GetEdmModel(hasStream, streamPropName);
ODataPathProvider provider = new ODataPathProvider(); ODataPathProvider provider = new ODataPathProvider();
var settings = new OpenApiConvertSettings(); var settings = new OpenApiConvertSettings();
const string TodosContentPath = "/todos({id})/content";
const string TodosValuePath = "/todos({id})/$value";
const string TodosLogoPath = "/todos({id})/logo";
// Act // Act
var paths = provider.GetPaths(model,settings); var paths = provider.GetPaths(model, settings);
// Assert // Assert
Assert.NotNull(paths); Assert.NotNull(paths);
Assert.Contains("/catalog/content", paths.Select(p => p.GetPathItemName()));
Assert.Contains("/catalog/thumbnailPhoto", paths.Select(p => p.GetPathItemName()));
Assert.Contains("/me/photo/$value", paths.Select(p => p.GetPathItemName()));
if (hasStream && !streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)) if (streamPropName.Equals("logo"))
{ {
Assert.Equal(7, paths.Count()); if (hasStream)
Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/$value", "/Todos({Id})/Logo" }, {
paths.Select(p => p.GetPathItemName())); Assert.Equal(12, paths.Count());
Assert.Contains(TodosValuePath, paths.Select(p => p.GetPathItemName()));
Assert.Contains(TodosLogoPath, paths.Select(p => p.GetPathItemName()));
Assert.DoesNotContain(TodosContentPath, paths.Select(p => p.GetPathItemName()));
}
else
{
Assert.Equal(11, paths.Count());
Assert.Contains(TodosLogoPath, paths.Select(p => p.GetPathItemName()));
Assert.DoesNotContain(TodosContentPath, paths.Select(p => p.GetPathItemName()));
Assert.DoesNotContain(TodosValuePath, paths.Select(p => p.GetPathItemName()));
}
} }
else if ((hasStream && streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)) || else if (streamPropName.Equals("content"))
(!hasStream && streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)))
{ {
Assert.Equal(6, paths.Count()); Assert.Equal(11, paths.Count());
Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/Content" }, Assert.Contains(TodosContentPath, paths.Select(p => p.GetPathItemName()));
paths.Select(p => p.GetPathItemName())); Assert.DoesNotContain(TodosLogoPath, paths.Select(p => p.GetPathItemName()));
} Assert.DoesNotContain(TodosValuePath, paths.Select(p => p.GetPathItemName()));
else // !hasStream && !streamPropName.Equals("Content")
{
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()));
} }
} }
@ -576,13 +587,13 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
string template = @"<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx""> string template = @"<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx"">
<edmx:DataServices> <edmx:DataServices>
<Schema Namespace=""microsoft.graph"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> <Schema Namespace=""microsoft.graph"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
<EntityType Name=""Todo"" HasStream=""{0}""> <EntityType Name=""todo"" HasStream=""{0}"">
<Key> <Key>
<PropertyRef Name=""Id"" /> <PropertyRef Name=""id"" />
</Key> </Key>
<Property Name=""Id"" Type=""Edm.Int32"" Nullable=""false"" /> <Property Name=""id"" Type=""Edm.Int32"" Nullable=""false"" />
<Property Name=""{1}"" Type=""Edm.Stream""/> <Property Name=""{1}"" Type=""Edm.Stream""/>
<Property Name = ""Description"" Type = ""Edm.String"" /> <Property Name = ""description"" Type = ""Edm.String"" />
</EntityType> </EntityType>
<EntityType Name=""user"" OpenType=""true""> <EntityType Name=""user"" OpenType=""true"">
<NavigationProperty Name = ""photo"" Type = ""microsoft.graph.profilePhoto"" ContainsTarget = ""true"" /> <NavigationProperty Name = ""photo"" Type = ""microsoft.graph.profilePhoto"" ContainsTarget = ""true"" />
@ -591,9 +602,17 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
<Property Name = ""height"" Type = ""Edm.Int32"" /> <Property Name = ""height"" Type = ""Edm.Int32"" />
<Property Name = ""width"" Type = ""Edm.Int32"" /> <Property Name = ""width"" Type = ""Edm.Int32"" />
</EntityType > </EntityType >
<EntityType Name=""document"">
<Property Name=""content"" Type=""Edm.Stream""/>
<Property Name=""thumbnailPhoto"" Type=""Edm.Stream""/>
</EntityType>
<EntityType Name=""catalog"" BaseType=""microsoft.graph.document"">
<NavigationProperty Name=""reports"" Type = ""Collection(microsoft.graph.report)"" />
</EntityType>
<EntityContainer Name =""GraphService""> <EntityContainer Name =""GraphService"">
<EntitySet Name=""Todos"" EntityType=""microsoft.graph.Todo"" /> <EntitySet Name=""todos"" EntityType=""microsoft.graph.todo"" />
<Singleton Name=""me"" Type=""microsoft.graph.user"" /> <Singleton Name=""me"" Type=""microsoft.graph.user"" />
<Singleton Name=""catalog"" Type=""microsoft.graph.catalog"" />
</EntityContainer> </EntityContainer>
</Schema> </Schema>
</edmx:DataServices> </edmx:DataServices>

View file

@ -55,13 +55,13 @@ namespace Microsoft.OpenApi.OData.Operation.Tests
Assert.NotNull(me); Assert.NotNull(me);
IEdmEntityType todo = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "Todo"); IEdmEntityType todo = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "Todo");
IEdmStructuralProperty sp = todo.DeclaredStructuralProperties().First(c => c.Name == "Logo"); IEdmStructuralProperty sp = todo.StructuralProperties().First(c => c.Name == "Logo");
ODataPath path = new ODataPath(new ODataNavigationSourceSegment(todos), ODataPath path = new ODataPath(new ODataNavigationSourceSegment(todos),
new ODataKeySegment(todos.EntityType()), new ODataKeySegment(todos.EntityType()),
new ODataStreamPropertySegment(sp.Name)); new ODataStreamPropertySegment(sp.Name));
IEdmEntityType user = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "user"); IEdmEntityType user = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "user");
IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); IEdmNavigationProperty navProperty = user.NavigationProperties().First(c => c.Name == "photo");
ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me), ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me),
new ODataNavigationPropertySegment(navProperty), new ODataNavigationPropertySegment(navProperty),
new ODataStreamContentSegment()); new ODataStreamContentSegment());

View file

@ -52,13 +52,13 @@ namespace Microsoft.OpenApi.OData.Operation.Tests
Assert.NotNull(todos); Assert.NotNull(todos);
IEdmEntityType todo = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "Todo"); IEdmEntityType todo = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "Todo");
IEdmStructuralProperty sp = todo.DeclaredStructuralProperties().First(c => c.Name == "Logo"); IEdmStructuralProperty sp = todo.StructuralProperties().First(c => c.Name == "Logo");
ODataPath path = new ODataPath(new ODataNavigationSourceSegment(todos), ODataPath path = new ODataPath(new ODataNavigationSourceSegment(todos),
new ODataKeySegment(todos.EntityType()), new ODataKeySegment(todos.EntityType()),
new ODataStreamPropertySegment(sp.Name)); new ODataStreamPropertySegment(sp.Name));
IEdmEntityType user = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "user"); IEdmEntityType user = model.SchemaElements.OfType<IEdmEntityType>().First(c => c.Name == "user");
IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); IEdmNavigationProperty navProperty = user.NavigationProperties().First(c => c.Name == "photo");
ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me), ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me),
new ODataNavigationPropertySegment(navProperty), new ODataNavigationPropertySegment(navProperty),
new ODataStreamContentSegment()); new ODataStreamContentSegment());