diff --git a/src/Core/Services/OpenAPI/OpenApiDocumentor.cs b/src/Core/Services/OpenAPI/OpenApiDocumentor.cs
index e66968de13..0b090f4335 100644
--- a/src/Core/Services/OpenAPI/OpenApiDocumentor.cs
+++ b/src/Core/Services/OpenAPI/OpenApiDocumentor.cs
@@ -680,7 +680,7 @@ private static void AddCustomHeadersToOperation(OpenApiOperation operation)
///
/// This method adds the input parameters from the stored procedure definition to the OpenApi operation parameters.
- /// A input parameter will be marked REQUIRED if default value is not available.
+ /// A input parameter will be marked REQUIRED when it is explicitly flagged in config or when no default value is available.
///
private static void AddStoredProcedureInputParameters(OpenApiOperation operation, StoredProcedureDefinition spDefinition)
{
@@ -690,7 +690,7 @@ private static void AddStoredProcedureInputParameters(OpenApiOperation operation
GetOpenApiQueryParameter(
name: paramKey,
description: "Input parameter for stored procedure arguments",
- required: false,
+ required: parameterDefinition.Required ?? !parameterDefinition.HasConfigDefault,
type: TypeHelper.GetJsonDataTypeFromSystemType(parameterDefinition.SystemType).ToString().ToLower()
)
);
@@ -1135,9 +1135,9 @@ private static bool IsRequestBodyRequired(SourceDefinition sourceDef, bool consi
StoredProcedureDefinition spDef = (StoredProcedureDefinition)sourceDef;
foreach (KeyValuePair parameterMetadata in spDef.Parameters)
{
- // A parameter which does not have any of the following properties
+ // A parameter which is explicitly marked required or has no default value
// results in the body being required so that a value can be provided.
- if (!parameterMetadata.Value.HasConfigDefault)
+ if (parameterMetadata.Value.Required ?? !parameterMetadata.Value.HasConfigDefault)
{
requestBodyRequired = true;
break;
@@ -1422,7 +1422,10 @@ private static OpenApiSchema CreateSpRequestComponentSchema(Dictionary
+ {
+ new() { Name = "title", Default = "Sample Title" }
+ },
+ KeyFields: null),
+ Fields: null,
+ GraphQL: new(Singular: null, Plural: null, Enabled: false),
+ Rest: new(Methods: EntityRestOptions.DEFAULT_SUPPORTED_VERBS),
+ Permissions: OpenApiTestBootstrap.CreateBasicPermissions(),
+ Mappings: null,
+ Relationships: null,
+ Description: "Stored procedure with a parameter default");
+
+ // Entity whose "title" parameter is explicitly marked required: false in config.
+ // Even though it has no default value, the explicit override must be honored so that only
+ // "id" (no default, no override) remains required.
+ Entity entity3 = new(
+ Source: new(
+ Object: "update_book_title",
+ EntitySourceType.StoredProcedure,
+ Parameters: new List
+ {
+ new() { Name = "title", Required = false }
+ },
+ KeyFields: null),
+ Fields: null,
+ GraphQL: new(Singular: null, Plural: null, Enabled: false),
+ Rest: new(Methods: EntityRestOptions.DEFAULT_SUPPORTED_VERBS),
+ Permissions: OpenApiTestBootstrap.CreateBasicPermissions(),
+ Mappings: null,
+ Relationships: null,
+ Description: "Stored procedure with an explicit required override");
+
+ // Entity whose only parameter is explicitly marked required: false in config.
+ // Because no parameter is required, requestBody.Required and the GET query parameter's
+ // required flag must both be false, and the schema-level required set must be empty.
+ Entity entity4 = new(
+ Source: new(
+ Object: "get_publisher_by_id",
+ EntitySourceType.StoredProcedure,
+ Parameters: new List
+ {
+ new() { Name = "id", Required = false }
+ },
+ KeyFields: null),
+ Fields: null,
+ GraphQL: new(Singular: null, Plural: null, Enabled: false),
+ Rest: new(Methods: EntityRestOptions.DEFAULT_SUPPORTED_VERBS),
+ Permissions: OpenApiTestBootstrap.CreateBasicPermissions(),
+ Mappings: null,
+ Relationships: null,
+ Description: "Stored procedure with all parameters marked required: false");
+
Dictionary entities = new()
{
- { "sp1", entity1 }
+ { "sp1", entity1 },
+ { "sp2", entity2 },
+ { "sp3", entity3 },
+ { "sp4", entity4 }
};
_runtimeEntities = new(entities);
@@ -74,13 +137,23 @@ public static void CreateEntities()
///
/// Validates that the generated request body references stored procedure parameters
/// and not result set columns.
+ /// Also validates that the request body schema component flags the expected parameters
+ /// as required: a parameter is required when it has no default value and is not explicitly
+ /// marked required: false in the runtime config.
+ /// Also validates the body-level required flag on the OpenApiRequestBody object, which
+ /// should be true when any parameter is required and false when all parameters are optional.
///
/// Entity name
/// Expected parameters in request body
/// Expected parameter value types in request body.
- [DataRow("sp1", new string[] { "title", "publisher_name" }, new string[] { "string", "string" }, DisplayName = "Validate request body parameters and parameter Json data types.")]
+ /// Expected parameters flagged as required in the schema component.
+ /// Expected value of the body-level required flag.
+ [DataRow("sp1", new string[] { "title", "publisher_name" }, new string[] { "string", "string" }, new string[] { "title", "publisher_name" }, true, DisplayName = "Parameters without defaults are all required.")]
+ [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.")]
+ [DataRow("sp3", new string[] { "id", "title" }, new string[] { "integer", "string" }, new string[] { "id" }, true, DisplayName = "Parameter explicitly marked required: false is not required.")]
+ [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.")]
[DataTestMethod]
- public void ValidateRequestBodyContents(string entityName, string[] expectedParameters, string[] expectedParametersJsonTypes)
+ public void ValidateRequestBodyContents(string entityName, string[] expectedParameters, string[] expectedParametersJsonTypes, string[] expectedRequiredParameters, bool expectedRequestBodyRequired)
{
Dictionary configuredOperations = ResolveConfiguredOperations(_runtimeEntities[entityName]);
foreach (OperationType opType in configuredOperations.Keys)
@@ -100,7 +173,17 @@ public void ValidateRequestBodyContents(string entityName, string[] expectedPara
OpenApiReference schemaComponentReference = GetRequestBodyReference(requestBody);
string expectedSchemaReferenceId = $"{entityName}{OpenApiDocumentor.SP_REQUEST_SUFFIX}";
+ // Validate the body-level required flag.
+ Assert.AreEqual(expectedRequestBodyRequired, requestBody.Required, message: "Unexpected request body required value.");
+
ValidateOpenApiReferenceContents(schemaComponentReference, expectedSchemaReferenceId, expectedParameters, expectedParametersJsonTypes);
+
+ // Validate that the expected parameters are marked as required in the schema component.
+ ISet requiredParameters = _openApiDocument.Components.Schemas[expectedSchemaReferenceId].Required ?? new HashSet();
+ CollectionAssert.AreEquivalent(
+ expectedRequiredParameters,
+ requiredParameters.ToArray(),
+ message: "Unexpected required parameters in request body schema component.");
}
}
@@ -149,6 +232,37 @@ public void OpenApiDocumentor_TagsIncludeEntityDescription()
$"Expected tag for '{entityName}' with description '{expectedDescription}' not found.");
}
+ ///
+ /// Validates that the generated GET operation query parameters for stored procedure entities
+ /// have the correct required flag: a parameter without a config default and not explicitly
+ /// marked required: false is required; a parameter explicitly marked required: false is not.
+ ///
+ /// Entity name.
+ /// Expected query parameter names.
+ /// Whether each corresponding query parameter is expected to be required.
+ [DataRow("sp1", new string[] { "title", "publisher_name" }, new bool[] { true, true }, DisplayName = "GET parameters without defaults are required.")]
+ [DataRow("sp4", new string[] { "id" }, new bool[] { false }, DisplayName = "GET parameter explicitly marked required: false is not required.")]
+ [DataTestMethod]
+ public void ValidateGetQueryParameters(string entityName, string[] expectedParameters, bool[] expectedRequired)
+ {
+ OpenApiOperation getOperation = _openApiDocument.Paths["/" + entityName].Operations[OperationType.Get];
+ Assert.IsNotNull(getOperation, "GET operation not found.");
+
+ // Filter to query parameters only (excludes the Authorization and X-MS-API-ROLE header parameters).
+ List spParams = getOperation.Parameters
+ .Where(p => p.In == ParameterLocation.Query)
+ .ToList();
+
+ Assert.AreEqual(expectedParameters.Length, spParams.Count, "Unexpected number of GET query parameters.");
+
+ for (int i = 0; i < expectedParameters.Length; i++)
+ {
+ OpenApiParameter param = spParams.FirstOrDefault(p => p.Name == expectedParameters[i]);
+ Assert.IsNotNull(param, $"Parameter '{expectedParameters[i]}' not found in GET query parameters.");
+ Assert.AreEqual(expectedRequired[i], param.Required, $"Unexpected required value for GET query parameter '{expectedParameters[i]}'.");
+ }
+ }
+
///
/// Validates that the provided OpenApiReference object has the expected schema reference id
/// and that that id is present in the list of component schema in the OpenApi document.