Compare commits
6 Commits
master
...
rule/add-R
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4bbe613ea6 | ||
![]() |
a3a542a32f | ||
![]() |
f554b6cbb5 | ||
![]() |
b3111107ed | ||
![]() |
01baaa9f00 | ||
![]() |
1fdb02e153 |
434
rules/S6963/csharp/Testcases/AllStatusCodes.cs
Normal file
434
rules/S6963/csharp/Testcases/AllStatusCodes.cs
Normal file
@ -0,0 +1,434 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
WebApplication app = null;
|
||||
app.UseSwagger(); // Necessary for the rule to raise
|
||||
|
||||
[ApiController]
|
||||
public class AllTargetCodes : Controller
|
||||
{
|
||||
#region COMPLIANT
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsOk() => Ok(); // Compliant - 200
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsOk2() => Ok(null); // Compliant - 200
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsContent() => Content(""); // Compliant - 200
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsContent2() => Content("", ""); // Compliant - 200
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsContent3() => Content("", default(MediaTypeHeaderValue)); // Compliant - 200
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsContent4() => Content("", "", null); // Compliant - 200
|
||||
|
||||
public IActionResult ReturnFile() => File("", ""); // Compliant - 200
|
||||
|
||||
public IActionResult ReturnPhysicalFile() => PhysicalFile("", ""); // Compliant - 200
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2XX
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreated() => // Noncompliant - 201
|
||||
Created(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreated2() => // Noncompliant - 201
|
||||
Created("uri", null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreated3() => // Noncompliant - 201
|
||||
Created(default(Uri), null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreatedAtAction() => // Noncompliant - 201
|
||||
CreatedAtAction("actionName", null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreatedAtAction2() => // Noncompliant - 201
|
||||
CreatedAtAction("actionName", null, null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreatedAtAction3() => // Noncompliant - 201
|
||||
CreatedAtAction("actionName", "controllerName", null, null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreatedAtRoute() => // Noncompliant - 201
|
||||
CreatedAtRoute("routeName", null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreatedAtRoute2() => // Noncompliant - 201
|
||||
CreatedAtRoute(null, null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsCreatedAtRoute3() => // Noncompliant - 201
|
||||
CreatedAtRoute("routeName", null, null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAccepted() => // Noncompliant - 202
|
||||
Accepted(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAccepted2() => // Noncompliant - 202
|
||||
Accepted(default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAccepted3() => // Noncompliant - 202
|
||||
Accepted(default(Uri)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAccepted4() => // Noncompliant - 202
|
||||
Accepted("uri"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAccepted5() => // Noncompliant - 202
|
||||
Accepted("uri", null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAccepted6() => // Noncompliant - 202
|
||||
Accepted(default(Uri), null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtAction() => // Noncompliant - 202
|
||||
AcceptedAtAction("actionName"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtAction2() => // Noncompliant - 202
|
||||
AcceptedAtAction("actionName", "controllerName"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtAction3() => // Noncompliant - 202
|
||||
AcceptedAtAction("actionName", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtAction4() => // Noncompliant - 202
|
||||
AcceptedAtAction("actionName", "controllerName", null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtAction5() => // Noncompliant - 202
|
||||
AcceptedAtAction("actionName", "controllerName", null, null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtRoute() => // Noncompliant - 202
|
||||
AcceptedAtRoute(default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtRoute2() => // Noncompliant - 202
|
||||
AcceptedAtRoute("routeName"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtRoute3() => // Noncompliant - 202
|
||||
AcceptedAtRoute("routeName", null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtRoute4() => // Noncompliant - 202
|
||||
AcceptedAtRoute(default(object), null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsAcceptedAtRoute5() => // Noncompliant - 202
|
||||
AcceptedAtRoute("routeName", null, null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsNoContent() => // Noncompliant - 204
|
||||
NoContent(); // Secondary
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3XX
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectPermanent() => // Noncompliant - 301
|
||||
RedirectPermanent("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsLocalRedirectPermanent() => // Noncompliant - 301
|
||||
LocalRedirectPermanent("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnRedirectToActionPermanent() => // Noncompliant - 301
|
||||
RedirectToActionPermanent(""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnRedirectToActionPermanent2() => // Noncompliant - 301
|
||||
RedirectToActionPermanent("", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnRedirectToActionPermanent3() => // Noncompliant - 301
|
||||
RedirectToActionPermanent("", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnRedirectToActionPermanent4() => // Noncompliant - 301
|
||||
RedirectToActionPermanent("", "", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnRedirectToActionPermanent5() => // Noncompliant - 301
|
||||
RedirectToActionPermanent("", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnRedirectToActionPermanent6() => // Noncompliant - 301
|
||||
RedirectToActionPermanent("", "", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePermanent() => // Noncompliant - 302
|
||||
RedirectToRoutePermanent("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePermanent2() => // Noncompliant - 302
|
||||
RedirectToRoutePermanent(default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePermanent3() => // Noncompliant - 302
|
||||
RedirectToRoutePermanent("", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePermanent4() => // Noncompliant - 302
|
||||
RedirectToRoutePermanent("", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePermanent5() => // Noncompliant - 302
|
||||
RedirectToRoutePermanent("", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePermanent() => // Noncompliant - 301
|
||||
RedirectToPagePermanent("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePermanent2() => // Noncompliant - 301
|
||||
RedirectToPagePermanent("", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePermanent3() => // Noncompliant - 301
|
||||
RedirectToPagePermanent("", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePermanent4() => // Noncompliant - 301
|
||||
RedirectToPagePermanent("", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePermanent5() => // Noncompliant - 301
|
||||
RedirectToPagePermanent("", "", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPage() => // Noncompliant - 302
|
||||
RedirectToPage("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPage2() => // Noncompliant - 302
|
||||
RedirectToPage("", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPage3() => // Noncompliant - 302
|
||||
RedirectToPage("", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPage4() => // Noncompliant - 302
|
||||
RedirectToPage("", "", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPage5() => // Noncompliant - 302
|
||||
RedirectToPage("", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPage6() => // Noncompliant - 302
|
||||
RedirectToPage("", "", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirect() => // Noncompliant - 302
|
||||
Redirect("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsLocalRedirect() => // Noncompliant - 302
|
||||
LocalRedirect("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction() => // Noncompliant - 302
|
||||
RedirectToAction(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction2() => // Noncompliant - 302
|
||||
RedirectToAction("actionName"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction3() => // Noncompliant - 302
|
||||
RedirectToAction("actionName", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction4() => // Noncompliant - 302
|
||||
RedirectToAction("actionName", "controllerName"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction5() => // Noncompliant - 302
|
||||
RedirectToAction("actionName", "controllerName", default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction6() => // Noncompliant - 302
|
||||
RedirectToAction("actionName", "controlloerName", "fragment"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToAction7() => // Noncompliant - 302
|
||||
RedirectToAction("", "", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoute() => // Noncompliant - 302
|
||||
RedirectToRoute("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoute2() => // Noncompliant - 302
|
||||
RedirectToRoute(default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoute3() => // Noncompliant - 302
|
||||
RedirectToRoute("", default(object)); // Secondary
|
||||
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoute4() => // Noncompliant - 302
|
||||
RedirectToRoute("", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoute5() => // Noncompliant - 302
|
||||
RedirectToRoute("", "", ""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePreserveMethod() => // Noncompliant - 307
|
||||
RedirectToRoutePreserveMethod(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectPreserveMethod() => // Noncompliant - 307
|
||||
RedirectPreserveMethod("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsLocalRedirectPreserveMethod() => // Noncompliant - 307
|
||||
LocalRedirectPreserveMethod(""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToActionPreserveMethod() => // Noncompliant - 307
|
||||
RedirectToActionPreserveMethod(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePreserveMethod() => // Noncompliant - 307
|
||||
RedirectToPagePreserveMethod(""); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectPermanentPreserveMethod() => // Noncompliant - 308
|
||||
RedirectPermanentPreserveMethod("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsLocalRedirectPermanentPreserveMethod() => // Noncompliant - 308
|
||||
LocalRedirectPermanentPreserveMethod("url"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToActionPermanentPreserveMethod() => // Noncompliant - 308
|
||||
RedirectToActionPermanentPreserveMethod(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToRoutePermanentPreserveMethod() => // Noncompliant - 308
|
||||
RedirectToRoutePermanentPreserveMethod(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsRedirectToPagePermanentPreserveMethod() => // Noncompliant - 308
|
||||
RedirectToPagePermanentPreserveMethod(""); // Secondary
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4XX
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsBadRequest() => // Noncompliant - 400
|
||||
BadRequest(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsValidationProblem() => // Noncompliant - 400
|
||||
ValidationProblem(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsValidationProblem2() => // Noncompliant - 400
|
||||
ValidationProblem(default(ValidationProblemDetails)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsValidationProblem3() => // Noncompliant - 400
|
||||
ValidationProblem(default(ModelStateDictionary)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsUnauthorized() => // Noncompliant - 401
|
||||
Unauthorized(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsChallenge() => // Noncompliant - [depends on the configured IAuthenticationService, usually 401 or 403]
|
||||
Challenge(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsChallenge2() => // Noncompliant - [depends on the configured IAuthenticationService, usually 401 or 403]
|
||||
Challenge("scheme1", "scheme2"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsChallenge3() => // Noncompliant - [depends on the configured IAuthenticationService, usually 401 or 403]
|
||||
Challenge(default(AuthenticationProperties)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsChallenge4() => // Noncompliant - [depends on the configured IAuthenticationService, usually 401 or 403]
|
||||
Challenge(default(AuthenticationProperties), "scheme1", "scheme2"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsForbid() => // Noncompliant - usually 403
|
||||
Forbid(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsForbid2() => // Noncompliant - usually 403
|
||||
Forbid("scheme1", "scheme2"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsForbid3() => // Noncompliant - usually 403
|
||||
Forbid(default(AuthenticationProperties)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsForbid4() => // Noncompliant - usually 403
|
||||
Forbid(default(AuthenticationProperties), "scheme1", "scheme2"); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsNotFound() => // Noncompliant - 404
|
||||
NotFound(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsNotFound2() => // Noncompliant - 404
|
||||
NotFound(null); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsValidationProblem4() => // Noncompliant - 404
|
||||
ValidationProblem(statusCode: 404); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsConflict() => // Noncompliant - 409
|
||||
Conflict(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsConflict2() => // Noncompliant - 409
|
||||
Conflict(default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsConflict3() => // Noncompliant - 409
|
||||
Conflict(default(ModelStateDictionary)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsUnprocessableEntity() => // Noncompliant - 422
|
||||
UnprocessableEntity(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsUnprocessableEntity2() => // Noncompliant - 422
|
||||
UnprocessableEntity(default(object)); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ReturnsUnprocessableEntity3() => // Noncompliant - 422
|
||||
UnprocessableEntity(default(ModelStateDictionary)); // Secondary
|
||||
|
||||
#endregion
|
||||
}
|
14
rules/S6963/csharp/Testcases/NoSwaggerFound.cs
Normal file
14
rules/S6963/csharp/Testcases/NoSwaggerFound.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
WebApplication app = null;
|
||||
// UseSwagger is not called
|
||||
|
||||
namespace Things
|
||||
{
|
||||
[ApiController]
|
||||
public class Baseline : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NoAttribute() => BadRequest(); // Compliant, not in Swagger context
|
||||
}
|
||||
}
|
259
rules/S6963/csharp/Testcases/S6963.cs
Normal file
259
rules/S6963/csharp/Testcases/S6963.cs
Normal file
@ -0,0 +1,259 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
WebApplication app = null;
|
||||
app.UseSwagger(); // Necessary for the rule to raise
|
||||
|
||||
namespace Things
|
||||
{
|
||||
[ApiController]
|
||||
public class Baseline : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public IActionResult HasCorrectAttribute() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(400)]
|
||||
public IActionResult HasCorrectAttributeWithNumber() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||||
public IActionResult HasCorrectAttributeWithTpe() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest, "application/xml")]
|
||||
public IActionResult HasCorrectAttributeWithContentType() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType<int>(StatusCodes.Status400BadRequest)]
|
||||
public IActionResult HasCorrectAttributeGeneric() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType<int>(StatusCodes.Status400BadRequest, "application/json")]
|
||||
public IActionResult HasCorrectAttributeWithContentTypeGeneric() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NoAttribute() => // Noncompliant {{Annotate this method with ProducesResponseType for "BadRequest".}}
|
||||
// ^^^^^^^^^^^
|
||||
BadRequest(); // Secondary
|
||||
// ^^^^^^^^^^
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(StatusCodes.Status418ImATeapot)]
|
||||
public IActionResult HasWrongAttribute() => // Noncompliant {{Annotate this method with ProducesResponseType for "BadRequest".}}
|
||||
// ^^^^^^^^^^^^^^^^^
|
||||
BadRequest(); // Secondary
|
||||
// ^^^^^^^^^^
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(418)]
|
||||
public IActionResult HasAttributeWithWrongNumber() => // Noncompliant {{Annotate this method with ProducesResponseType for "BadRequest".}}
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
BadRequest(); // Secondary
|
||||
// ^^^^^^^^^^
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status418ImATeapot)]
|
||||
public IActionResult HasWrongAttributeWithType() => // Noncompliant
|
||||
BadRequest(); // Secondary
|
||||
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status418ImATeapot), "application/xml"]
|
||||
public IActionResult HasWrongAttributeWithContentType() => // Noncompliant
|
||||
BadRequest(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType<int>(StatusCodes.Status418ImATeapot)]
|
||||
public IActionResult HasWrongAttributeGeneric() => // Noncompliant
|
||||
BadRequest(); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType<int>(StatusCodes.Status418ImATeapot, "application/json")]
|
||||
public IActionResult HasWrongAttributeWithContentTypeGeneric() => // Noncompliant
|
||||
BadRequest(); // Secondary
|
||||
|
||||
[Route("foo")]
|
||||
public IActionResult NoAttributeWithRoute() => // Noncompliant {{Annotate this method with ProducesResponseType for "BadRequest".}}
|
||||
BadRequest(); // Secondary
|
||||
|
||||
/// <response code="400">Some text</response>
|
||||
[HttpGet("foo")]
|
||||
public IActionResult AnnotatedWithXml() => BadRequest();
|
||||
|
||||
[HttpGet("foo")]
|
||||
/// <response code="400">this does not work</response>
|
||||
public IActionResult AnnotatedErroneouslyWithXml() => // Noncompliant, response has to be above the attribute.
|
||||
BadRequest();
|
||||
|
||||
[HttpGet("foo")]
|
||||
protected IActionResult IsNotPublic() => BadRequest(); // Compliant, only public methods are considered
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
internal class NotPublic : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NoAttribute() => BadRequest(); // Compliant, only public classes are considered
|
||||
}
|
||||
|
||||
[NonController]
|
||||
[ApiController]
|
||||
public class NotAController : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NoAttribute() => BadRequest(); // Compliant, excluded by the attribute
|
||||
}
|
||||
|
||||
public class NotApiController : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NoAttribute() => BadRequest(); // Compliant, only classes annotated with "ApiController" are considered
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
public class NestedMethodCall: ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NoAttribute() => Passthrough(); // Compliant FN, we only consider the current method
|
||||
|
||||
private IActionResult Passthrough() => BadRequest();
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
public class MultipleErrorCodes : Controller
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public IActionResult MissOne(bool condition) => // Noncompliant {{Annotate this method with ProducesResponseType for "NotFound".}}
|
||||
// ^^^^^^^
|
||||
condition ? NotFound() : BadRequest(); // Secondary
|
||||
// ^^^^^^^^
|
||||
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(StatusCodes.Status302Found)]
|
||||
public IActionResult MissesMultiple(bool condition) // Noncompliant [badRequest] {{Annotate this method with ProducesResponseType for "BadRequest".}}
|
||||
// ^^^^^^^^^^^^^^
|
||||
// ^^^^^^^^^^^^^^ @-2 [notFound] {{Annotate this method with ProducesResponseType for "NotFound".}}
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
return NotFound(); // Secondary [notFound]
|
||||
// ^^^^^^^^
|
||||
}
|
||||
else if (condition)
|
||||
{
|
||||
return BadRequest(); // Secondary [badRequest]
|
||||
// ^^^^^^^^^^
|
||||
}
|
||||
|
||||
return Redirect("url");
|
||||
}
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public class AnnotatedAtControllerLevel : ControllerBase
|
||||
{
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult Valid() => BadRequest(); // Compliant
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult Invalid() => // Noncompliant {{Annotate this method with ProducesResponseType for "NotFound".}}
|
||||
// ^^^^^^^
|
||||
NotFound(); // Secondary
|
||||
// ^^^^^^^^
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
public class MixedAnnotations : Controller
|
||||
{
|
||||
/// <response code="404">Some text</response>
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public IActionResult MarkedWithBothXmlAndAttribute(bool condition) => // Compliant
|
||||
condition ? BadRequest() : NotFound();
|
||||
|
||||
/// <response code="307">Not found</response>
|
||||
/// <response code="409">Conflict</response>
|
||||
[HttpGet("foo")]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public IActionResult MarkedWithBothXmlAndAttribute(int condition) => // Noncompliant {{Annotate this method with ProducesResponseType for "NotFound".}}
|
||||
condition switch
|
||||
{
|
||||
1 => RedirectPreserveMethod("url"),
|
||||
2 => NotFound(), // Secondary
|
||||
// ^^^^^^^^
|
||||
3 => BadRequest(),
|
||||
4 => Conflict(),
|
||||
};
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
public class NotReturns : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult SimpleOk(bool condition) // Compliant, only return statements are checked
|
||||
{
|
||||
var x1 = BadRequest();
|
||||
IActionResult x2 = condition ? NotFound() : NoContent();
|
||||
var lambda = () => Redirect("url");
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NestedErrorCodes() // Noncompliant {{Annotate this method with ProducesResponseType for "NotFound".}}
|
||||
{
|
||||
return Ok(BadRequest()); // Conmpliant, we only consider the top node of the return expression
|
||||
return Ok(Redirect("url")); // Compliant, same as above
|
||||
|
||||
return NotFound(); // Secondary
|
||||
// ^^^^^^^^
|
||||
}
|
||||
}
|
||||
|
||||
// For the implementation: If this seems too cumbersome, consider dropping it and documenting it as FN
|
||||
[ApiController]
|
||||
public class ExplicitErrorCodes_NewObjects : Controller
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public ObjectResult NewObjectResult() => // Noncompliant
|
||||
new ObjectResult(42) { StatusCode = StatusCodes.Status418ImATeapot }; // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public StatusCodeResult NewStatusCodeResult() => // Noncompliant
|
||||
new StatusCodeResult(statusCode: 42); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NewContentResult() => // Noncompliant
|
||||
new ContentResult { StatusCode = StatusCodes.Status418ImATeapot }; // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult NewRedirectResult() => // Noncompliant
|
||||
new RedirectResult("url", permanent: true, preserveMethod: false); // Secondary
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
public class ExplicitErrorCodes_Methods : ControllerBase
|
||||
{
|
||||
[HttpGet("foo")]
|
||||
public IActionResult StatusCodeMethod() => // Noncompliant
|
||||
StatusCode(StatusCodes.Status404NotFound); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult StatusCodeMethodWithValue() => // Noncompliant
|
||||
StatusCode(statusCode: 42, null);
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ProblemMethodWithStatusCode() => // Noncompliant
|
||||
Problem(statusCode: StatusCodes.Status418ImATeapot); // Secondary
|
||||
|
||||
[HttpGet("foo")]
|
||||
public IActionResult ProblemMethodWithoutStatusCode() => // Compliant
|
||||
Problem();
|
||||
}
|
||||
}
|
24
rules/S6963/csharp/metadata.json
Normal file
24
rules/S6963/csharp/metadata.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"title": "Actions that return non-200 status codes should be annotated with ProducesResponseTypeAttribute",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "5min"
|
||||
},
|
||||
"tags": [
|
||||
"asp.net"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-6963",
|
||||
"sqKey": "S6963",
|
||||
"scope": "Main",
|
||||
"defaultQualityProfiles": ["Sonar way"],
|
||||
"quickfix": "targeted",
|
||||
"code": {
|
||||
"impacts": {
|
||||
"MAINTAINABILITY": "HIGH"
|
||||
},
|
||||
"attribute": "CLEAR"
|
||||
}
|
||||
}
|
118
rules/S6963/csharp/rule.adoc
Normal file
118
rules/S6963/csharp/rule.adoc
Normal file
@ -0,0 +1,118 @@
|
||||
In an https://learn.microsoft.com/en-us/aspnet/core[ASP.NET Core] https://en.wikipedia.org/wiki/Web_API[Web API], controller actions can return any https://en.wikipedia.org/wiki/List_of_HTTP_status_codes[HTTP status code]. If a controller action returns an unexpected status code, annotating the action with the https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.producesresponsetypeattribute[`++[ProducesResponseType]++`] attribute is recommended.
|
||||
|
||||
== Why is this an issue?
|
||||
|
||||
HTTP response status codes indicate the result of an HTTP request to the API's clients. The HTTP standard groups the status codes in the following way:
|
||||
|
||||
* `1xx` codes represent informational responses.
|
||||
* `2xx` codes represent successful responses.
|
||||
* `3xx` codes represent redirection.
|
||||
* `4xx` codes represent client errors.
|
||||
* `5xx` codes represent server errors.
|
||||
|
||||
For example, it is usually typical to return:
|
||||
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200[200 OK] for successful requests
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301[301 Moved Permanently] for requests that need to be redirected to a new URL, given by the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location[Location] header
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403[403 Forbidden] for unsuccessful requests that are not authorized to access the resource
|
||||
|
||||
When building a Web API, any combination of https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods[HTTP request method] and https://developer.mozilla.org/en-US/docs/Web/HTTP/Status[HTTP response status code] can be used. However, when deviating from the conventional HTTP status codes, or when something goes wrong and it is necessary to return a 4xx status code, it is essential to communicate the API's behavior to the clients.
|
||||
|
||||
If the application uses https://swagger.io/[Swagger], the API documentation will be generated based on the status codes returned by the controller actions. By default, Swashbuckle generates a `200 OK` response status code for controller actions not annotated with the `ProducesResponseType` attribute. Therefore, if the status codes are not properly annotated, the API documentation will not either, which can lead to confusion. This can be particularly problematic when the API is consumed by third-party clients, where the undocumented behavior can lead to unexpected results and bugs in the long run, without the API provider being aware of it.
|
||||
|
||||
This rule raises an issue on a controller action when:
|
||||
|
||||
* It can produce a response with a non-`200 OK` status code.
|
||||
* It is not annotated with a `++[ProducesResponseType]++` attribute for this status code, either at action or controller level.
|
||||
* It is not annotated with https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle#xml-comments[XML comments] for this status code.
|
||||
* The application has enabled the https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle#add-and-configure-swagger-middleware[Swagger middleware].
|
||||
* The controller is marked with the https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.apicontrollerattribute[`++[ApiController]++`] attribute.
|
||||
|
||||
== How to fix it
|
||||
|
||||
There are two ways to fix this issue:
|
||||
|
||||
* Annotate the controller action with the `++[ProducesResponseType]++` attribute for the non-`200 OK` status codes.
|
||||
* Annotate the controller action with XML comments for the non-`200 OK` status codes.
|
||||
|
||||
=== Code examples
|
||||
|
||||
==== Noncompliant code example
|
||||
|
||||
[source,csharp,diff-id=1,diff-type=noncompliant]
|
||||
----
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FoodController : ControllerBase
|
||||
{
|
||||
IFoodService _service;
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetById(int id) // Noncompliant: Annotate this method with ProducesResponseType for "NotFound".
|
||||
{
|
||||
var result = _service.GetById(id);
|
||||
return result is not null
|
||||
? Ok(result)
|
||||
: NotFound(); // 404 NotFound is not explicitly annotated
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
==== Compliant solution
|
||||
|
||||
[source,csharp,diff-id=1,diff-type=compliant]
|
||||
----
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FoodController : ControllerBase
|
||||
{
|
||||
IFoodService _service;
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult GetById(int id)
|
||||
{
|
||||
var result = _service.GetById(id);
|
||||
return result is not null
|
||||
? Ok(result)
|
||||
: NotFound();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Resources
|
||||
|
||||
=== Documentation
|
||||
|
||||
* Wikipedia - https://en.wikipedia.org/wiki/Web_API[Web API]
|
||||
* Wikipedia - https://en.wikipedia.org/wiki/List_of_HTTP_status_codes[List of HTTP status codes]
|
||||
* Mozilla - https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods[HTTP request methods]
|
||||
* Mozilla - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status[HTTP response status codes]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/aspnet/core[ASP.NET Core]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle[Get started with Swashbuckle and ASP.NET Core]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.apicontrollerattribute[ApiControllerAttribute Class]
|
||||
* Microsoft Learn - https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.producesresponsetypeattribute[ProducesResponseTypeAttribute Class]
|
||||
* SmartBear - https://swagger.io/[Swagger]
|
||||
|
||||
ifdef::env-github,rspecator-view[]
|
||||
|
||||
'''
|
||||
== Implementation Specification
|
||||
(visible only on this page)
|
||||
|
||||
=== Message
|
||||
|
||||
* Annotate this method with ProducesResponseType for "XXX".
|
||||
|
||||
(`XXX` is the method identifier causing this issue, e.g. `BadRequest` in `return BadRequest();`)
|
||||
|
||||
=== Highlighting
|
||||
|
||||
* Primary: The method name of the action. One issue per status code.
|
||||
* Secondary: the return statement's method identifier (e.g. `BadRequest` in `return BadRequest();`).
|
||||
|
||||
'''
|
||||
== Comments And Links
|
||||
(visible only on this page)
|
||||
|
||||
endif::env-github,rspecator-view[]
|
2
rules/S6963/metadata.json
Normal file
2
rules/S6963/metadata.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user