diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs
index 69d2e5bc..2bb9dd77 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs
@@ -194,5 +194,25 @@ internal static class Constants
/// count segment identifier
///
public const string CountSegmentIdentifier = "count";
+
+ ///
+ /// content string
+ ///
+ public const string Content = "content";
+
+ ///
+ /// Success string
+ ///
+ public const string Success = "Success";
+
+ ///
+ /// Created string
+ ///
+ public const string Created = "Created";
+
+ ///
+ /// error string
+ ///
+ public const string Error = "error";
}
}
diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/OpenApiOperationExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Common/OpenApiOperationExtensions.cs
index 36150489..3eb56130 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Common/OpenApiOperationExtensions.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Common/OpenApiOperationExtensions.cs
@@ -20,48 +20,49 @@ public static class OpenApiOperationExtensions
///
/// The operation.
/// The settings.
- /// Whether to add a 204 no content response.
+ /// Optional: Whether to add a 204 no content response.
/// Optional: The OpenAPI schema of the response.
public static void AddErrorResponses(this OpenApiOperation operation, OpenApiConvertSettings settings, bool addNoContent = false, OpenApiSchema schema = null)
{
- if (operation == null) {
- throw Error.ArgumentNull(nameof(operation));
- }
- if(settings == null) {
- throw Error.ArgumentNull(nameof(settings));
- }
-
- if(operation.Responses == null)
+ Utils.CheckArgumentNull(operation, nameof(operation));
+ Utils.CheckArgumentNull(settings, nameof(settings));
+
+ if (operation.Responses == null)
{
operation.Responses = new();
}
if (addNoContent)
- {
- if (settings.UseSuccessStatusCodeRange && schema != null)
+ {
+ if (settings.UseSuccessStatusCodeRange)
{
- OpenApiResponse response = new()
+ OpenApiResponse response = null;
+ if (schema != null)
{
- Content = new Dictionary
+ response = new()
{
+ Description = Constants.Success,
+ Content = new Dictionary
{
- Constants.ApplicationJsonMediaType,
- new OpenApiMediaType
{
- Schema = schema
+ Constants.ApplicationJsonMediaType,
+ new OpenApiMediaType
+ {
+ Schema = schema
+ }
}
}
- }
- };
- operation.Responses.Add(Constants.StatusCodeClass2XX, response);
+ };
+ }
+ operation.Responses.Add(Constants.StatusCodeClass2XX, response ?? Constants.StatusCodeClass2XX.GetResponse());
}
else
{
operation.Responses.Add(Constants.StatusCode204, Constants.StatusCode204.GetResponse());
}
- }
+ }
- if(settings.ErrorResponsesAsDefault)
+ if (settings.ErrorResponsesAsDefault)
{
operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
}
diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
index 90f8f240..0c9b8276 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
@@ -385,7 +385,7 @@ private void RetrieveMediaEntityStreamPaths(IEdmEntityType entityType, ODataPath
currentPath.Pop();
}
- if (sp.Name.Equals("content", StringComparison.OrdinalIgnoreCase))
+ if (sp.Name.Equals(Constants.Content, StringComparison.OrdinalIgnoreCase))
{
createValuePath = false;
}
diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs
index 46ec5596..e11a1a02 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs
@@ -27,19 +27,21 @@ internal static class OpenApiResponseGenerator
Reference = new OpenApiReference
{
Type = ReferenceType.Response,
- Id = "error"
+ Id = Constants.Error
}
}
},
- { Constants.StatusCode204, new OpenApiResponse { Description = "Success"} },
+ { Constants.StatusCode204, new OpenApiResponse { Description = Constants.Success} },
+ { Constants.StatusCode201, new OpenApiResponse { Description = Constants.Created} },
+ { Constants.StatusCodeClass2XX, new OpenApiResponse { Description = Constants.Success} },
{ Constants.StatusCodeClass4XX, new OpenApiResponse
{
UnresolvedReference = true,
Reference = new OpenApiReference
{
Type = ReferenceType.Response,
- Id = "error"
+ Id = Constants.Error
}
}
},
@@ -49,7 +51,7 @@ internal static class OpenApiResponseGenerator
Reference = new OpenApiReference
{
Type = ReferenceType.Response,
- Id = "error"
+ Id = Constants.Error
}
}
}
diff --git a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj
index b194e40c..89bd34d7 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj
+++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj
@@ -15,7 +15,7 @@
netstandard2.0
Microsoft.OpenApi.OData
true
- 1.3.0-preview2
+ 1.3.0-preview3
This package contains the codes you need to convert OData CSDL to Open API Document of Model.
© Microsoft Corporation. All rights reserved.
Microsoft OpenApi OData EDM
@@ -25,6 +25,7 @@
- Skips adding a $count path if a similar count() function path exists #347
- Checks whether path exists before adding it to the paths dictionary #343
- Strips namespace prefix from operation segments and aliases type cast segments #348
+- Return response status code 2XX for PUT operations of stream properties when UseSuccessStatusCodeRange is enabled #310
Microsoft.OpenApi.OData.Reader
..\..\tool\Microsoft.OpenApi.OData.snk
diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs
index f04af78c..ccfe15bd 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs
@@ -34,23 +34,23 @@ protected override void SetBasicInfo(OpenApiOperation operation)
// Description
if (LastSegmentIsStreamPropertySegment)
{
- IEdmVocabularyAnnotatable annotatable = GetAnnotatableElement();
+ (_, var property) = GetStreamElements();
string description;
- if (annotatable is IEdmNavigationProperty)
+ if (property is IEdmNavigationProperty)
{
- ReadRestrictionsType readRestriction = Context.Model.GetRecord(annotatable, CapabilitiesConstants.NavigationRestrictions)?
+ ReadRestrictionsType readRestriction = Context.Model.GetRecord(property, CapabilitiesConstants.NavigationRestrictions)?
.RestrictedProperties?.FirstOrDefault()?.ReadRestrictions;
description = LastSegmentIsKeySegment
? readRestriction?.ReadByKeyRestrictions?.Description
: readRestriction?.Description
- ?? Context.Model.GetDescriptionAnnotation(annotatable);
+ ?? Context.Model.GetDescriptionAnnotation(property);
}
else
{
// Structural property
- description = Context.Model.GetDescriptionAnnotation(annotatable);
+ description = Context.Model.GetDescriptionAnnotation(property);
}
operation.Description = description;
diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs
index 14043905..e53ad674 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs
@@ -145,11 +145,11 @@ protected IDictionary GetContentDescription()
};
// Fetch the respective AcceptableMediaTypes
- IEdmVocabularyAnnotatable annotatableElement = GetAnnotatableElement();
+ (_, var property) = GetStreamElements();
IEnumerable mediaTypes = null;
- if (annotatableElement != null)
+ if (property != null)
{
- mediaTypes = Context.Model.GetCollection(annotatableElement,
+ mediaTypes = Context.Model.GetCollection(property,
CoreConstants.AcceptableMediaTypes);
}
@@ -173,13 +173,13 @@ protected IDictionary GetContentDescription()
}
///
- /// Gets the annotatable stream property from the path segments.
+ /// Gets the stream property and entity type declaring the stream property.
///
- /// The annotatable stream property.
- protected IEdmVocabularyAnnotatable GetAnnotatableElement()
+ /// The stream property and entity type declaring the stream property.
+ protected (IEdmEntityType entityType, IEdmProperty property) GetStreamElements()
{
// Only ODataStreamPropertySegment is annotatable
- if (!LastSegmentIsStreamPropertySegment) return null;
+ if (!LastSegmentIsStreamPropertySegment) return (null, null);
// Retrieve the entity type of the segment before the stream property segment
var entityType = Path.Segments.ElementAtOrDefault(Path.Segments.Count - 2).EntityType;
@@ -192,7 +192,7 @@ protected IEdmVocabularyAnnotatable GetAnnotatableElement()
property = GetNavigationProperty(entityType, lastSegmentProp.Identifier);
}
- return property;
+ return (entityType, property);
}
private IEdmStructuralProperty GetStructuralProperty(IEdmEntityType entityType, string identifier)
diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs
index 9430a468..3a46fe73 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs
@@ -3,6 +3,7 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
+using System;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Vocabularies;
@@ -34,20 +35,20 @@ protected override void SetBasicInfo(OpenApiOperation operation)
// Description
if (LastSegmentIsStreamPropertySegment)
{
- IEdmVocabularyAnnotatable annotatable = GetAnnotatableElement();
+ (_, var property) = GetStreamElements();
string description;
- if (annotatable is IEdmNavigationProperty)
+ if (property is IEdmNavigationProperty)
{
- UpdateRestrictionsType updateRestriction = Context.Model.GetRecord(annotatable, CapabilitiesConstants.NavigationRestrictions)?
+ UpdateRestrictionsType updateRestriction = Context.Model.GetRecord(property, CapabilitiesConstants.NavigationRestrictions)?
.RestrictedProperties?.FirstOrDefault()?.UpdateRestrictions;
- description = updateRestriction?.Description ?? Context.Model.GetDescriptionAnnotation(annotatable);
+ description = updateRestriction?.Description ?? Context.Model.GetDescriptionAnnotation(property);
}
else
{
// Structural property
- description = Context.Model.GetDescriptionAnnotation(annotatable);
+ description = Context.Model.GetDescriptionAnnotation(property);
}
operation.Description = description;
@@ -77,7 +78,28 @@ protected override void SetRequestBody(OpenApiOperation operation)
///
protected override void SetResponses(OpenApiOperation operation)
{
- operation.AddErrorResponses(Context.Settings, true);
+ if (LastSegmentIsStreamPropertySegment && Path.LastSegment.Identifier.Equals(Constants.Content, StringComparison.OrdinalIgnoreCase))
+ {
+ // Get the entity type declaring this stream property.
+ (var entityType, _) = GetStreamElements();
+
+ OpenApiSchema schema = new()
+ {
+ UnresolvedReference = true,
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.Schema,
+ Id = entityType.FullName()
+ }
+ };
+
+ operation.AddErrorResponses(Context.Settings, addNoContent: true, schema: schema);
+ }
+ else
+ {
+ operation.AddErrorResponses(Context.Settings, true);
+ }
+
base.SetResponses(operation);
}
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs
index b67921bd..85f7fc52 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs
@@ -135,6 +135,7 @@ public static IEdmModel GetEdmModel(string annotation)
+
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs
index 8fdc3a93..7564e4b0 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs
@@ -17,9 +17,11 @@ public class MediaEntityPutOperationHandlerTests
private readonly MediaEntityPutOperationHandler _operationalHandler = new MediaEntityPutOperationHandler();
[Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void CreateMediaEntityPutOperationReturnsCorrectOperation(bool enableOperationId)
+ [InlineData(true, false)]
+ [InlineData(true, true)]
+ [InlineData(false, false)]
+ [InlineData(false, true)]
+ public void CreateMediaEntityPutOperationReturnsCorrectOperation(bool enableOperationId, bool useSuccessStatusCodeRange)
{
// Arrange
string qualifiedName = CoreConstants.AcceptableMediaTypes;
@@ -33,17 +35,18 @@ public void CreateMediaEntityPutOperationReturnsCorrectOperation(bool enableOper
";
// Assert
- VerifyMediaEntityPutOperation("", enableOperationId);
- VerifyMediaEntityPutOperation(annotation, enableOperationId);
+ VerifyMediaEntityPutOperation("", enableOperationId, useSuccessStatusCodeRange);
+ VerifyMediaEntityPutOperation(annotation, enableOperationId, useSuccessStatusCodeRange);
}
- private void VerifyMediaEntityPutOperation(string annotation, bool enableOperationId)
+ private void VerifyMediaEntityPutOperation(string annotation, bool enableOperationId, bool useSuccessStatusCodeRange)
{
// Arrange
IEdmModel model = MediaEntityGetOperationHandlerTests.GetEdmModel(annotation);
OpenApiConvertSettings settings = new OpenApiConvertSettings
{
- EnableOperationId = enableOperationId
+ EnableOperationId = enableOperationId,
+ UseSuccessStatusCodeRange = useSuccessStatusCodeRange
};
ODataContext context = new ODataContext(model, settings);
@@ -63,13 +66,20 @@ private void VerifyMediaEntityPutOperation(string annotation, bool enableOperati
new ODataNavigationPropertySegment(navProperty),
new ODataStreamContentSegment());
+ IEdmStructuralProperty sp2 = todo.StructuralProperties().First(c => c.Name == "Content");
+ ODataPath path3 = new(new ODataNavigationSourceSegment(todos),
+ new ODataKeySegment(todos.EntityType()),
+ new ODataStreamPropertySegment(sp2.Name));
+
// Act
var putOperation = _operationalHandler.CreateOperation(context, path);
var putOperation2 = _operationalHandler.CreateOperation(context, path2);
+ var putOperation3 = _operationalHandler.CreateOperation(context, path3);
// Assert
Assert.NotNull(putOperation);
Assert.NotNull(putOperation2);
+ Assert.NotNull(putOperation3);
Assert.Equal("Update Logo for Todo in Todos", putOperation.Summary);
Assert.Equal("Update media content for the navigation property photo in me", putOperation2.Summary);
Assert.NotNull(putOperation.Tags);
@@ -82,10 +92,28 @@ private void VerifyMediaEntityPutOperation(string annotation, bool enableOperati
Assert.NotNull(putOperation.Responses);
Assert.NotNull(putOperation2.Responses);
+ Assert.NotNull(putOperation3.Responses);
+
Assert.Equal(2, putOperation.Responses.Count);
Assert.Equal(2, putOperation2.Responses.Count);
- Assert.Equal(new[] { "204", "default" }, putOperation.Responses.Select(r => r.Key));
- Assert.Equal(new[] { "204", "default" }, putOperation2.Responses.Select(r => r.Key));
+ Assert.Equal(2, putOperation3.Responses.Count);
+
+ var statusCode = (useSuccessStatusCodeRange) ? Constants.StatusCodeClass2XX : Constants.StatusCode204;
+ Assert.Equal(new[] { statusCode, "default" }, putOperation.Responses.Select(r => r.Key));
+ Assert.Equal(new[] { statusCode, "default" }, putOperation2.Responses.Select(r => r.Key));
+ Assert.Equal(new[] { statusCode, "default" }, putOperation3.Responses.Select(r => r.Key));
+
+ // Test only for stream properties of identifier 'content'
+ if (useSuccessStatusCodeRange)
+ {
+ var referenceId = putOperation3.Responses[statusCode]?.Content[Constants.ApplicationJsonMediaType]?.Schema?.Reference.Id;
+ Assert.NotNull(referenceId);
+ Assert.Equal("microsoft.graph.Todo", referenceId);
+ }
+ else
+ {
+ Assert.Empty(putOperation3.Responses[statusCode].Content);
+ }
if (!string.IsNullOrEmpty(annotation))
{