Skip to content

Commit 7341bc2

Browse files
Copilotsouvikghosh04CopilotRubenCerna2079
authored
Mark stored procedure parameters without defaults as required in OpenAPI (#3687)
## Why make this change? - Closes #3498. The generated OpenAPI document for stored procedure entities omitted the `required` array on the `{entity}_sp_request` schema, so parameters that must be supplied (e.g. `firstName`, `lastName`) were not flagged as required. ## What is this change? - **`CreateSpRequestComponentSchema`** (`OpenApiDocumentor.cs`): a parameter is now marked required when it is explicitly flagged in config **or** has no default value. Previously only an explicit config `required: true` populated the array, which is almost never set, so the array was always empty. - Mirrors the existing `IsRequestBodyRequired` logic (`!HasConfigDefault`), keeping body-level and property-level required semantics consistent. An explicit config `required: false` is still honored. ```csharp if (def.Required ?? !def.HasConfigDefault) { required.Add(parameter); } ``` ## How was this tested? - [x] Integration Tests - [ ] Unit Tests Extended `StoredProcedureGeneration.ValidateRequestBodyContents` to assert the request body schema's `required` set contains the expected parameters. Verified the assertion fails when the fix is reverted. ## Sample Request(s) `GET /api/openapi` — request body schema component for a stored procedure with parameters `title`, `publisher_name` (neither having a default): ```json "sp1_sp_request": { "type": "object", "properties": { "title": { "type": "string" }, "publisher_name": { "type": "string" } }, "required": [ "title", "publisher_name" ] } ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Souvik Ghosh <souvikofficial04@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com>
1 parent a5a4879 commit 7341bc2

2 files changed

Lines changed: 125 additions & 8 deletions

File tree

src/Core/Services/OpenAPI/OpenApiDocumentor.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ private static void AddCustomHeadersToOperation(OpenApiOperation operation)
680680

681681
/// <summary>
682682
/// This method adds the input parameters from the stored procedure definition to the OpenApi operation parameters.
683-
/// A input parameter will be marked REQUIRED if default value is not available.
683+
/// A input parameter will be marked REQUIRED when it is explicitly flagged in config or when no default value is available.
684684
/// </summary>
685685
private static void AddStoredProcedureInputParameters(OpenApiOperation operation, StoredProcedureDefinition spDefinition)
686686
{
@@ -690,7 +690,7 @@ private static void AddStoredProcedureInputParameters(OpenApiOperation operation
690690
GetOpenApiQueryParameter(
691691
name: paramKey,
692692
description: "Input parameter for stored procedure arguments",
693-
required: false,
693+
required: parameterDefinition.Required ?? !parameterDefinition.HasConfigDefault,
694694
type: TypeHelper.GetJsonDataTypeFromSystemType(parameterDefinition.SystemType).ToString().ToLower()
695695
)
696696
);
@@ -1135,9 +1135,9 @@ private static bool IsRequestBodyRequired(SourceDefinition sourceDef, bool consi
11351135
StoredProcedureDefinition spDef = (StoredProcedureDefinition)sourceDef;
11361136
foreach (KeyValuePair<string, ParameterDefinition> parameterMetadata in spDef.Parameters)
11371137
{
1138-
// A parameter which does not have any of the following properties
1138+
// A parameter which is explicitly marked required or has no default value
11391139
// results in the body being required so that a value can be provided.
1140-
if (!parameterMetadata.Value.HasConfigDefault)
1140+
if (parameterMetadata.Value.Required ?? !parameterMetadata.Value.HasConfigDefault)
11411141
{
11421142
requestBodyRequired = true;
11431143
break;
@@ -1422,7 +1422,10 @@ private static OpenApiSchema CreateSpRequestComponentSchema(Dictionary<string, P
14221422
Default = def.Default is not null ? new OpenApiString(def.Default) : null
14231423
});
14241424

1425-
if (def.Required == true)
1425+
// A parameter is required when it is explicitly marked required in the runtime config,
1426+
// or when no default value is available, in which case a value must be provided in the
1427+
// request body. This mirrors the logic used by IsRequestBodyRequired.
1428+
if (def.Required ?? !def.HasConfigDefault)
14261429
{
14271430
required.Add(parameter);
14281431
}

src/Service.Tests/OpenApiDocumentor/StoredProcedureGeneration.cs

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,72 @@ public static void CreateEntities()
6363
Relationships: null,
6464
Description: "Represents a stored procedure for books");
6565

66+
// Entity whose "title" parameter has a config-provided default value.
67+
// Because a default is available, "title" must not be flagged as required, while
68+
// "publisher_id" (no default) must remain required.
69+
Entity entity2 = new(
70+
Source: new(
71+
Object: "insert_book",
72+
EntitySourceType.StoredProcedure,
73+
Parameters: new List<ParameterMetadata>
74+
{
75+
new() { Name = "title", Default = "Sample Title" }
76+
},
77+
KeyFields: null),
78+
Fields: null,
79+
GraphQL: new(Singular: null, Plural: null, Enabled: false),
80+
Rest: new(Methods: EntityRestOptions.DEFAULT_SUPPORTED_VERBS),
81+
Permissions: OpenApiTestBootstrap.CreateBasicPermissions(),
82+
Mappings: null,
83+
Relationships: null,
84+
Description: "Stored procedure with a parameter default");
85+
86+
// Entity whose "title" parameter is explicitly marked required: false in config.
87+
// Even though it has no default value, the explicit override must be honored so that only
88+
// "id" (no default, no override) remains required.
89+
Entity entity3 = new(
90+
Source: new(
91+
Object: "update_book_title",
92+
EntitySourceType.StoredProcedure,
93+
Parameters: new List<ParameterMetadata>
94+
{
95+
new() { Name = "title", Required = false }
96+
},
97+
KeyFields: null),
98+
Fields: null,
99+
GraphQL: new(Singular: null, Plural: null, Enabled: false),
100+
Rest: new(Methods: EntityRestOptions.DEFAULT_SUPPORTED_VERBS),
101+
Permissions: OpenApiTestBootstrap.CreateBasicPermissions(),
102+
Mappings: null,
103+
Relationships: null,
104+
Description: "Stored procedure with an explicit required override");
105+
106+
// Entity whose only parameter is explicitly marked required: false in config.
107+
// Because no parameter is required, requestBody.Required and the GET query parameter's
108+
// required flag must both be false, and the schema-level required set must be empty.
109+
Entity entity4 = new(
110+
Source: new(
111+
Object: "get_publisher_by_id",
112+
EntitySourceType.StoredProcedure,
113+
Parameters: new List<ParameterMetadata>
114+
{
115+
new() { Name = "id", Required = false }
116+
},
117+
KeyFields: null),
118+
Fields: null,
119+
GraphQL: new(Singular: null, Plural: null, Enabled: false),
120+
Rest: new(Methods: EntityRestOptions.DEFAULT_SUPPORTED_VERBS),
121+
Permissions: OpenApiTestBootstrap.CreateBasicPermissions(),
122+
Mappings: null,
123+
Relationships: null,
124+
Description: "Stored procedure with all parameters marked required: false");
125+
66126
Dictionary<string, Entity> entities = new()
67127
{
68-
{ "sp1", entity1 }
128+
{ "sp1", entity1 },
129+
{ "sp2", entity2 },
130+
{ "sp3", entity3 },
131+
{ "sp4", entity4 }
69132
};
70133

71134
_runtimeEntities = new(entities);
@@ -74,13 +137,23 @@ public static void CreateEntities()
74137
/// <summary>
75138
/// Validates that the generated request body references stored procedure parameters
76139
/// and not result set columns.
140+
/// Also validates that the request body schema component flags the expected parameters
141+
/// as required: a parameter is required when it has no default value and is not explicitly
142+
/// marked required: false in the runtime config.
143+
/// Also validates the body-level required flag on the OpenApiRequestBody object, which
144+
/// should be true when any parameter is required and false when all parameters are optional.
77145
/// </summary>
78146
/// <param name="entityName">Entity name</param>
79147
/// <param name="expectedParameters">Expected parameters in request body</param>
80148
/// <param name="expectedParametersJsonTypes">Expected parameter value types in request body.</param>
81-
[DataRow("sp1", new string[] { "title", "publisher_name" }, new string[] { "string", "string" }, DisplayName = "Validate request body parameters and parameter Json data types.")]
149+
/// <param name="expectedRequiredParameters">Expected parameters flagged as required in the schema component.</param>
150+
/// <param name="expectedRequestBodyRequired">Expected value of the body-level required flag.</param>
151+
[DataRow("sp1", new string[] { "title", "publisher_name" }, new string[] { "string", "string" }, new string[] { "title", "publisher_name" }, true, DisplayName = "Parameters without defaults are all required.")]
152+
[DataRow("sp2", new string[] { "title", "publisher_id" }, new string[] { "string", "integer" }, new string[] { "publisher_id" }, true, DisplayName = "Parameter with a config default is not required.")]
153+
[DataRow("sp3", new string[] { "id", "title" }, new string[] { "integer", "string" }, new string[] { "id" }, true, DisplayName = "Parameter explicitly marked required: false is not required.")]
154+
[DataRow("sp4", new string[] { "id" }, new string[] { "integer" }, new string[] { }, false, DisplayName = "All parameters marked required: false produces empty required set and optional request body.")]
82155
[DataTestMethod]
83-
public void ValidateRequestBodyContents(string entityName, string[] expectedParameters, string[] expectedParametersJsonTypes)
156+
public void ValidateRequestBodyContents(string entityName, string[] expectedParameters, string[] expectedParametersJsonTypes, string[] expectedRequiredParameters, bool expectedRequestBodyRequired)
84157
{
85158
Dictionary<OperationType, bool> configuredOperations = ResolveConfiguredOperations(_runtimeEntities[entityName]);
86159
foreach (OperationType opType in configuredOperations.Keys)
@@ -100,7 +173,17 @@ public void ValidateRequestBodyContents(string entityName, string[] expectedPara
100173
OpenApiReference schemaComponentReference = GetRequestBodyReference(requestBody);
101174
string expectedSchemaReferenceId = $"{entityName}{OpenApiDocumentor.SP_REQUEST_SUFFIX}";
102175

176+
// Validate the body-level required flag.
177+
Assert.AreEqual(expectedRequestBodyRequired, requestBody.Required, message: "Unexpected request body required value.");
178+
103179
ValidateOpenApiReferenceContents(schemaComponentReference, expectedSchemaReferenceId, expectedParameters, expectedParametersJsonTypes);
180+
181+
// Validate that the expected parameters are marked as required in the schema component.
182+
ISet<string> requiredParameters = _openApiDocument.Components.Schemas[expectedSchemaReferenceId].Required ?? new HashSet<string>();
183+
CollectionAssert.AreEquivalent(
184+
expectedRequiredParameters,
185+
requiredParameters.ToArray(),
186+
message: "Unexpected required parameters in request body schema component.");
104187
}
105188
}
106189

@@ -149,6 +232,37 @@ public void OpenApiDocumentor_TagsIncludeEntityDescription()
149232
$"Expected tag for '{entityName}' with description '{expectedDescription}' not found.");
150233
}
151234

235+
/// <summary>
236+
/// Validates that the generated GET operation query parameters for stored procedure entities
237+
/// have the correct required flag: a parameter is required when it has no config default and is
238+
/// not explicitly marked required: false; a parameter explicitly marked required: false is optional.
239+
/// </summary>
240+
/// <param name="entityName">Entity name.</param>
241+
/// <param name="expectedParameters">Expected query parameter names.</param>
242+
/// <param name="expectedRequired">Whether each corresponding query parameter is expected to be required.</param>
243+
[DataRow("sp1", new string[] { "title", "publisher_name" }, new bool[] { true, true }, DisplayName = "GET parameters without defaults are required.")]
244+
[DataRow("sp4", new string[] { "id" }, new bool[] { false }, DisplayName = "GET parameter explicitly marked required: false is not required.")]
245+
[DataTestMethod]
246+
public void ValidateGetQueryParameters(string entityName, string[] expectedParameters, bool[] expectedRequired)
247+
{
248+
OpenApiOperation getOperation = _openApiDocument.Paths["/" + entityName].Operations[OperationType.Get];
249+
Assert.IsNotNull(getOperation, "GET operation not found.");
250+
251+
// Filter to query parameters only (excludes the Authorization and X-MS-API-ROLE header parameters).
252+
List<OpenApiParameter> spParams = getOperation.Parameters
253+
.Where(p => p.In == ParameterLocation.Query)
254+
.ToList();
255+
256+
Assert.AreEqual(expectedParameters.Length, spParams.Count, "Unexpected number of GET query parameters.");
257+
258+
for (int i = 0; i < expectedParameters.Length; i++)
259+
{
260+
OpenApiParameter param = spParams.FirstOrDefault(p => p.Name == expectedParameters[i]);
261+
Assert.IsNotNull(param, $"Parameter '{expectedParameters[i]}' not found in GET query parameters.");
262+
Assert.AreEqual(expectedRequired[i], param.Required, $"Unexpected required value for GET query parameter '{expectedParameters[i]}'.");
263+
}
264+
}
265+
152266
/// <summary>
153267
/// Validates that the provided OpenApiReference object has the expected schema reference id
154268
/// and that that id is present in the list of component schema in the OpenApi document.

0 commit comments

Comments
 (0)