Skip to content
25 changes: 15 additions & 10 deletions src/Core/Services/OpenAPI/OpenApiDocumentor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ private OpenApiDocument BuildOpenApiDocument(RuntimeConfig runtimeConfig, string
{
new() { Url = url }
},
Paths = BuildPaths(runtimeConfig.Entities, runtimeConfig.DefaultDataSourceName, globalTagsDict, role),
Paths = BuildPaths(runtimeConfig.Entities, runtimeConfig.DefaultDataSourceName, globalTagsDict, role, isRequestBodyStrict: runtimeConfig.IsRequestBodyStrict),
Components = components,
Tags = globalTagsDict.Values.ToList()
};
Expand Down Expand Up @@ -300,7 +300,7 @@ public void CreateDocument(bool doOverrideExistingDocument = false)
/// <param name="globalTags">Dictionary of global tags keyed by normalized REST path for reuse.</param>
/// <param name="role">Optional role to filter permissions. If null, returns superset of all roles.</param>
/// <returns>All possible paths in the DAB engine's REST API endpoint.</returns>
private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSourceName, Dictionary<string, OpenApiTag> globalTags, string? role = null)
private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSourceName, Dictionary<string, OpenApiTag> globalTags, string? role = null, bool isRequestBodyStrict = true)
{
OpenApiPaths pathsCollection = new();

Expand Down Expand Up @@ -377,7 +377,8 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
sourceDefinition: sourceDefinition,
includePrimaryKeyPathComponent: true,
configuredRestOperations: configuredRestOperations,
tags: tags);
tags: tags,
isRequestBodyStrict: isRequestBodyStrict);

if (pkOperations.Count > 0)
{
Expand All @@ -400,7 +401,8 @@ private OpenApiPaths BuildPaths(RuntimeEntities entities, string defaultDataSour
sourceDefinition: sourceDefinition,
includePrimaryKeyPathComponent: false,
configuredRestOperations: configuredRestOperations,
tags: tags);
tags: tags,
isRequestBodyStrict: isRequestBodyStrict);

if (operations.Count > 0)
{
Expand Down Expand Up @@ -435,7 +437,8 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
SourceDefinition sourceDefinition,
bool includePrimaryKeyPathComponent,
Dictionary<OperationType, bool> configuredRestOperations,
List<OpenApiTag> tags)
List<OpenApiTag> tags,
bool isRequestBodyStrict = true)
{
Dictionary<OperationType, OpenApiOperation> openApiPathItemOperations = new();

Expand All @@ -457,7 +460,8 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
if (configuredRestOperations[OperationType.Put])
{
OpenApiOperation putOperation = CreateBaseOperation(description: PUT_DESCRIPTION, tags: tags);
putOperation.RequestBody = CreateOpenApiRequestBodyPayload($"{entityName}_NoPK", requestBodyRequired);
string putPatchSchemaRef = isRequestBodyStrict ? $"{entityName}_NoPK" : entityName;
putOperation.RequestBody = CreateOpenApiRequestBodyPayload(putPatchSchemaRef, requestBodyRequired);
putOperation.Responses.Add(HttpStatusCode.OK.ToString("D"), CreateOpenApiResponse(description: nameof(HttpStatusCode.OK), responseObjectSchemaName: entityName));
putOperation.Responses.Add(HttpStatusCode.Created.ToString("D"), CreateOpenApiResponse(description: nameof(HttpStatusCode.Created), responseObjectSchemaName: entityName));
openApiPathItemOperations.Add(OperationType.Put, putOperation);
Expand All @@ -466,7 +470,8 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
if (configuredRestOperations[OperationType.Patch])
{
OpenApiOperation patchOperation = CreateBaseOperation(description: PATCH_DESCRIPTION, tags: tags);
patchOperation.RequestBody = CreateOpenApiRequestBodyPayload($"{entityName}_NoPK", requestBodyRequired);
string patchSchemaRef = isRequestBodyStrict ? $"{entityName}_NoPK" : entityName;
Comment thread
aaronburtle marked this conversation as resolved.
patchOperation.RequestBody = CreateOpenApiRequestBodyPayload(patchSchemaRef, requestBodyRequired);
patchOperation.Responses.Add(HttpStatusCode.OK.ToString("D"), CreateOpenApiResponse(description: nameof(HttpStatusCode.OK), responseObjectSchemaName: entityName));
patchOperation.Responses.Add(HttpStatusCode.Created.ToString("D"), CreateOpenApiResponse(description: nameof(HttpStatusCode.Created), responseObjectSchemaName: entityName));
openApiPathItemOperations.Add(OperationType.Patch, patchOperation);
Expand Down Expand Up @@ -496,7 +501,7 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(

if (configuredRestOperations[OperationType.Post])
{
string postBodySchemaReferenceId = DoesSourceContainAutogeneratedPrimaryKey(sourceDefinition) ? $"{entityName}_NoAutoPK" : $"{entityName}";
string postBodySchemaReferenceId = isRequestBodyStrict && DoesSourceContainAutogeneratedPrimaryKey(sourceDefinition) ? $"{entityName}_NoAutoPK" : $"{entityName}";
OpenApiOperation postOperation = CreateBaseOperation(description: POST_DESCRIPTION, tags: tags);
postOperation.RequestBody = CreateOpenApiRequestBodyPayload(postBodySchemaReferenceId, IsRequestBodyRequired(sourceDefinition, considerPrimaryKeys: true));
postOperation.Responses.Add(HttpStatusCode.Created.ToString("D"), CreateOpenApiResponse(description: nameof(HttpStatusCode.Created), responseObjectSchemaName: entityName));
Expand All @@ -509,7 +514,7 @@ private Dictionary<OperationType, OpenApiOperation> CreateOperations(
// which is useful for entities with identity/auto-generated keys.
if (DoesSourceContainAutogeneratedPrimaryKey(sourceDefinition))
{
string keylessBodySchemaReferenceId = $"{entityName}_NoAutoPK";
string keylessBodySchemaReferenceId = isRequestBodyStrict ? $"{entityName}_NoAutoPK" : entityName;
bool keylessRequestBodyRequired = IsRequestBodyRequired(sourceDefinition, considerPrimaryKeys: true);

if (configuredRestOperations[OperationType.Put])
Expand Down Expand Up @@ -1342,7 +1347,7 @@ private Dictionary<string, OpenApiSchema> CreateComponentSchemas(RuntimeEntities
schemas.Add(entityName, CreateComponentSchema(entityName, fields: exposedColumnNames, metadataProvider, entities, isRequestBodySchema: false));

// Only generate request body schemas if mutation operations are available
if (hasPostOperation || hasPutPatchOperation)
if (isRequestBodyStrict && (hasPostOperation || hasPutPatchOperation))
{
// Create an entity's request body component schema excluding autogenerated primary keys.
Comment thread
aaronburtle marked this conversation as resolved.
// A POST request requires any non-autogenerated primary key references to be in the request body.
Expand Down
16 changes: 8 additions & 8 deletions src/Service.Tests/OpenApiDocumentor/RequestBodyStrictTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,22 @@ public async Task RequestBodyStrict_True_DisallowsExtraFields()
}

/// <summary>
/// Validates that when request-body-strict is false, request body schemas
/// have additionalProperties set to true.
/// Validates that when request-body-strict is false, the redundant _NoAutoPK and _NoPK
/// schemas are not generated. Operations reference the base entity schema instead.
/// </summary>
[TestMethod]
public async Task RequestBodyStrict_False_AllowsExtraFields()
public async Task RequestBodyStrict_False_OmitsRedundantSchemas()
{
OpenApiDocument doc = await GenerateDocumentWithPermissions(
OpenApiTestBootstrap.CreateBasicPermissions(),
requestBodyStrict: false);

// Request body schemas should have additionalProperties = true
Assert.IsTrue(doc.Components.Schemas.ContainsKey("book_NoAutoPK"), "POST request body schema should exist");
Assert.IsTrue(doc.Components.Schemas["book_NoAutoPK"].AdditionalPropertiesAllowed, "POST request body should allow extra fields in non-strict mode");
// _NoAutoPK and _NoPK schemas should not be generated when strict mode is off
Assert.IsFalse(doc.Components.Schemas.ContainsKey("book_NoAutoPK"), "POST request body schema should not exist in non-strict mode");
Assert.IsFalse(doc.Components.Schemas.ContainsKey("book_NoPK"), "PUT/PATCH request body schema should not exist in non-strict mode");

Assert.IsTrue(doc.Components.Schemas.ContainsKey("book_NoPK"), "PUT/PATCH request body schema should exist");
Assert.IsTrue(doc.Components.Schemas["book_NoPK"].AdditionalPropertiesAllowed, "PUT/PATCH request body should allow extra fields in non-strict mode");
// Base entity schema should still exist
Assert.IsTrue(doc.Components.Schemas.ContainsKey("book"), "Base entity schema should exist");
Comment thread
aaronburtle marked this conversation as resolved.
}

private static async Task<OpenApiDocument> GenerateDocumentWithPermissions(EntityPermission[] permissions, bool? requestBodyStrict = null)
Expand Down
Loading