Visitar URL original
Add Validation Rule for path operations to not have a request body by martincostello · Pull Request #2472 · microsoft/OpenAPI.NET · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add recommended rules
Add new `OpenApiRecommendedRules` class and adds a new `GetOperationShouldNotHaveRequestBody` rule.

Resolves #2454.
  • Loading branch information
martincostello committed Aug 22, 2025
commit 7b7cea4f966ec7e89e4b275d99b0b70bef0ea782
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Net.Http;

namespace Microsoft.OpenApi
{
/// <summary>
/// Additional recommended validation rules for OpenAPI.
/// </summary>
public static class OpenApiRecommendedRules
{
/// <summary>
/// A relative path to an individual endpoint. The field name MUST begin with a slash.
/// </summary>
public static ValidationRule<OpenApiPaths> GetOperationShouldNotHaveRequestBody =>
new(nameof(GetOperationShouldNotHaveRequestBody),
(context, item) =>
{
foreach (var path in item)
{
if (path.Value.Operations is not { Count: > 0 } operations)
{
continue;
}

context.Enter(path.Key);

foreach (var operation in operations)
{
if (!operation.Key.Equals(HttpMethod.Get))
{
continue;
}

if (operation.Value.RequestBody != null)
{
context.Enter(operation.Key.Method.ToLowerInvariant());
context.Enter("requestBody");

context.CreateWarning(
Comment thread
martincostello marked this conversation as resolved.
Resolved
nameof(GetOperationShouldNotHaveRequestBody),
"GET operations should not have a request body.");

context.Exit();
context.Exit();
}
}

context.Exit();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,10 @@ namespace Microsoft.OpenApi
public OpenApiReaderException(string message, Microsoft.OpenApi.Reader.ParsingContext context) { }
public OpenApiReaderException(string message, System.Exception innerException) { }
}
public static class OpenApiRecommendedRules
{
public static Microsoft.OpenApi.ValidationRule<Microsoft.OpenApi.OpenApiPaths> GetOperationShouldNotHaveRequestBody { get; }
}
public class OpenApiReferenceError : Microsoft.OpenApi.OpenApiError
{
public readonly Microsoft.OpenApi.BaseOpenApiReference? Reference;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Xunit;

namespace Microsoft.OpenApi.Validations.Tests;

public static class OpenApiRecommendedRulesTests
{
[Fact]
public static void GetOperationWithoutRequestBodyIsValid()
{
// Arrange
var document = new OpenApiDocument
{
Components = new OpenApiComponents(),
Info = new OpenApiInfo
{
Title = "People Document",
Version = "1.0.0"
},
Paths = [],
Workspace = new()
};

document.AddComponent("Person", new OpenApiSchema
{
Type = JsonSchemaType.Object,
Properties = new Dictionary<string, IOpenApiSchema>()
{
["name"] = new OpenApiSchema { Type = JsonSchemaType.String },
["email"] = new OpenApiSchema { Type = JsonSchemaType.String, Format = "email" }
}
});

document.Paths.Add("people", new OpenApiPathItem
{
Operations = new Dictionary<HttpMethod, OpenApiOperation>()
{
[HttpMethod.Get] = new OpenApiOperation
{
RequestBody = null,
Responses = new()
{
["200"] = new OpenApiResponse
{
Description = "OK",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
}
}
},
[HttpMethod.Post] = new OpenApiOperation
{
RequestBody = new OpenApiRequestBody
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
},
Responses = new()
{
["200"] = new OpenApiResponse
{
Description = "OK",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
}
}
}
}
});

var ruleSet = new ValidationRuleSet();
ruleSet.Add(typeof(OpenApiPaths), OpenApiRecommendedRules.GetOperationShouldNotHaveRequestBody);

// Act

var errors = document.Validate(ruleSet);
var result = !errors.Any();

// Assert
Assert.True(result);
Assert.NotNull(errors);
Assert.Empty(errors);
}

[Fact]
public static void GetOperationWithRequestBodyIsInvalid()
{
// Arrange
var document = new OpenApiDocument
{
Components = new OpenApiComponents(),
Info = new OpenApiInfo
{
Title = "People Document",
Version = "1.0.0"
},
Paths = [],
Workspace = new()
};

document.AddComponent("Person", new OpenApiSchema
{
Type = JsonSchemaType.Object,
Properties = new Dictionary<string, IOpenApiSchema>()
{
["name"] = new OpenApiSchema { Type = JsonSchemaType.String },
["email"] = new OpenApiSchema { Type = JsonSchemaType.String, Format = "email" }
}
});

document.Paths.Add("people", new OpenApiPathItem
{
Operations = new Dictionary<HttpMethod, OpenApiOperation>()
{
[HttpMethod.Get] = new OpenApiOperation
{
RequestBody = new OpenApiRequestBody
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
},
Responses = new()
{
["200"] = new OpenApiResponse
{
Description = "OK",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
}
}
},
[HttpMethod.Post] = new OpenApiOperation
{
RequestBody = new OpenApiRequestBody
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
},
Responses = new()
{
["200"] = new OpenApiResponse
{
Description = "OK",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchemaReference("Person", document),
}
}
}
}
}
}
});

var ruleSet = new ValidationRuleSet();
ruleSet.Add(typeof(OpenApiPaths), OpenApiRecommendedRules.GetOperationShouldNotHaveRequestBody);

// Act

var errors = document.Validate(ruleSet);
var result = !errors.Any();

// Assert
Assert.False(result);
Assert.NotNull(errors);
var error = Assert.Single(errors);
Assert.Equal("GET operations should not have a request body.", error.Message);
Assert.Equal("#/paths/people/get/requestbody", error.Pointer);
}
}