Skip to content

Commit 287c096

Browse files
committed
implementing requested changes
1 parent 28e1a3b commit 287c096

12 files changed

+211
-279
lines changed

src/Microsoft.OpenApi.OData.Reader/Common/RequestBodyRequirementAnalyzer.cs

Lines changed: 0 additions & 177 deletions
This file was deleted.

src/Microsoft.OpenApi.OData.Reader/EdmModelOpenApiExtensions.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

6+
using System;
67
using System.Collections.Generic;
8+
using System.Linq;
79
using Microsoft.OData.Edm;
810
using Microsoft.OData.Edm.Validation;
911
using Microsoft.OpenApi.OData.Common;
1012
using Microsoft.OpenApi.OData.Edm;
1113
using Microsoft.OpenApi.OData.Generator;
14+
using Microsoft.OpenApi.OData.Vocabulary.Core;
1215

1316
namespace Microsoft.OpenApi.OData
1417
{
@@ -54,5 +57,162 @@ public static OpenApiDocument ConvertToOpenApi(this IEdmModel model, OpenApiConv
5457
ODataContext context = new(model, settings);
5558
return context.CreateDocument();
5659
}
60+
61+
/// <summary>
62+
/// Determines if a request body should be required for an OData action.
63+
/// </summary>
64+
/// <param name="action">The EDM action.</param>
65+
/// <returns>True if the request body should be required, false otherwise.</returns>
66+
public static bool ShouldRequestBodyBeRequired(this IEdmAction action)
67+
{
68+
if (action == null)
69+
{
70+
return true; // Safe default
71+
}
72+
73+
// Get non-binding parameters
74+
var parameters = action.IsBound
75+
? action.Parameters.Skip(1)
76+
: action.Parameters;
77+
78+
// If no parameters, body is already null (existing behavior handles this)
79+
if (!parameters.Any())
80+
{
81+
return true; // Won't matter since body will be null
82+
}
83+
84+
// Check if any parameter is non-nullable and not optional
85+
return parameters.Any(p => !p.Type.IsNullable && p is not IEdmOptionalParameter);
86+
}
87+
88+
/// <summary>
89+
/// Determines if a request body should be required for an entity or complex type.
90+
/// </summary>
91+
/// <param name="structuredType">The EDM structured type.</param>
92+
/// <param name="isUpdateOperation">Whether this is an update operation (excludes key properties).</param>
93+
/// <param name="model">The EDM model for additional context.</param>
94+
/// <returns>True if the request body should be required, false otherwise.</returns>
95+
public static bool ShouldRequestBodyBeRequired(
96+
this IEdmStructuredType structuredType,
97+
bool isUpdateOperation,
98+
IEdmModel? model = null)
99+
{
100+
if (structuredType == null)
101+
{
102+
return true; // Safe default
103+
}
104+
105+
return !AreAllPropertiesOptional(structuredType, isUpdateOperation, model);
106+
}
107+
108+
/// <summary>
109+
/// Checks if all properties in a structured type are optional.
110+
/// </summary>
111+
/// <param name="structuredType">The EDM structured type.</param>
112+
/// <param name="excludeKeyProperties">Whether to exclude key properties from analysis (for update operations).</param>
113+
/// <param name="model">The EDM model for additional context.</param>
114+
/// <returns>True if all properties are optional, false if any are required.</returns>
115+
private static bool AreAllPropertiesOptional(
116+
IEdmStructuredType structuredType,
117+
bool excludeKeyProperties,
118+
IEdmModel? model = null)
119+
{
120+
if (structuredType == null)
121+
{
122+
return false;
123+
}
124+
125+
// Collect all properties including inherited ones
126+
var allProperties = new List<IEdmProperty>();
127+
128+
// Get properties from current type and all base types
129+
IEdmStructuredType currentType = structuredType;
130+
while (currentType != null)
131+
{
132+
allProperties.AddRange(currentType.DeclaredStructuralProperties());
133+
allProperties.AddRange(currentType.DeclaredNavigationProperties());
134+
currentType = currentType.BaseType;
135+
}
136+
137+
// If no properties, consider optional (empty body)
138+
if (allProperties.Count == 0)
139+
{
140+
return true;
141+
}
142+
143+
// Get key property names if we need to exclude them
144+
HashSet<string>? keyNames = null;
145+
if (excludeKeyProperties && structuredType is IEdmEntityType entityType)
146+
{
147+
keyNames = new HashSet<string>(entityType.Key().Select(static k => k.Name), StringComparer.Ordinal);
148+
}
149+
150+
// Check if ALL remaining properties are optional
151+
foreach (var property in allProperties)
152+
{
153+
// Skip key properties if requested
154+
if (keyNames != null && keyNames.Contains(property.Name))
155+
{
156+
continue;
157+
}
158+
159+
// Skip computed properties (read-only)
160+
if (model != null && property is IEdmStructuralProperty &&
161+
(model.GetBoolean(property, CoreConstants.Computed) ?? false))
162+
{
163+
continue;
164+
}
165+
166+
// If this property is required, the body must be required
167+
if (!property.IsPropertyOptional())
168+
{
169+
return false;
170+
}
171+
}
172+
173+
return true;
174+
}
175+
176+
/// <summary>
177+
/// Checks if an individual property is optional.
178+
/// </summary>
179+
/// <param name="property">The EDM property.</param>
180+
/// <returns>True if the property is optional, false if required.</returns>
181+
private static bool IsPropertyOptional(this IEdmProperty property)
182+
{
183+
if (property == null)
184+
{
185+
return false;
186+
}
187+
188+
// Structural properties (primitive, enum, complex)
189+
if (property is IEdmStructuralProperty structuralProp)
190+
{
191+
// Has default value = optional
192+
if (!string.IsNullOrEmpty(structuralProp.DefaultValueString))
193+
{
194+
return true;
195+
}
196+
197+
// Type is nullable = optional
198+
if (structuralProp.Type.IsNullable)
199+
{
200+
return true;
201+
}
202+
203+
// Otherwise required
204+
return false;
205+
}
206+
207+
// Navigation properties
208+
if (property is IEdmNavigationProperty navProp)
209+
{
210+
// Navigation properties are optional if nullable
211+
return navProp.Type.IsNullable;
212+
}
213+
214+
// Unknown property type, treat as required (safe default)
215+
return false;
216+
}
57217
}
58218
}

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiRequestBodyGenerator.cs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ internal static class OpenApiRequestBodyGenerator
7777
OpenApiRequestBody requestBody = new OpenApiRequestBody
7878
{
7979
Description = "Action parameters",
80-
Required = RequestBodyRequirementAnalyzer.ShouldRequestBodyBeRequired(action),
80+
Required = action.ShouldRequestBodyBeRequired(),
8181
Content = new Dictionary<string, IOpenApiMediaType>()
8282
};
8383

@@ -159,23 +159,5 @@ private static OpenApiRequestBody CreateRefPutRequestBody(OpenApiDocument docume
159159
}
160160
};
161161
}
162-
163-
/// <summary>
164-
/// Determines if a request body should be required based on the schema properties.
165-
/// </summary>
166-
/// <param name="structuredType">The EDM structured type.</param>
167-
/// <param name="isUpdateOperation">Whether this is an update operation (excludes key properties).</param>
168-
/// <param name="model">The EDM model for additional context.</param>
169-
/// <returns>True if the request body should be required, false otherwise.</returns>
170-
internal static bool DetermineIfRequestBodyRequired(
171-
IEdmStructuredType structuredType,
172-
bool isUpdateOperation,
173-
IEdmModel? model = null)
174-
{
175-
return RequestBodyRequirementAnalyzer.ShouldRequestBodyBeRequired(
176-
structuredType,
177-
isUpdateOperation,
178-
model);
179-
}
180162
}
181163
}

0 commit comments

Comments
 (0)