Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(webapi): Repeat delete requests should return 400 BAD REQUEST #1542

Merged
merged 4 commits into from
Nov 29, 2024

Conversation

oskogstad
Copy link
Collaborator

@oskogstad oskogstad commented Nov 29, 2024

Description

Related Issue(s)

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)

Documentation

  • Documentation is updated (either in docs-directory, Altinnpedia or a separate linked PR in altinn-studio-docs., if applicable)

Summary by CodeRabbit

  • New Features

    • Enhanced delete functionality now includes a response for already deleted dialogs, providing clearer feedback to users.
    • The system can now handle soft-deleted entities in delete operations.
    • New response for validation errors (400 Bad Request) added to the delete endpoint, improving error communication.
  • Bug Fixes

    • Improved error handling to better communicate various outcomes of delete operations, including success, not found, forbidden, and concurrency errors.

@oskogstad oskogstad requested a review from a team as a code owner November 29, 2024 12:00
Copy link
Contributor

coderabbitai bot commented Nov 29, 2024

📝 Walkthrough

Walkthrough

The pull request introduces modifications to the DeleteDialogResult class by adding a BadRequest type to represent a new outcome of the delete operation. The DeleteDialogCommandHandler now checks if a dialog is already marked as deleted, returning a BadRequest result with a specific error message instead of executing deletion. The DeleteDialogEndpoint class has been updated to handle this new BadRequest case in its response logic. Additionally, the query for retrieving dialogs has been adjusted to include soft-deleted entities, enhancing error handling for various outcomes of the delete operation.

Changes

File Path Change Summary
src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs Updated DeleteDialogResult to include BadRequest. Modified Handle method to check for already deleted dialogs. Adjusted error handling logic.
src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs Added handling for BadRequest result in HandleAsync method, responding with BadRequestAsync. Expanded error handling logic.
src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs Updated error handling in UpdateDialogCommandHandler to reference a specific GitHub issue for deleted dialogs.
docs/schema/V1/swagger.verified.json Added response for 400 status code for DELETE /api/v1/serviceowner/dialogs/{dialogId} indicating a validation error.
src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpointSummary.cs Added response status for StatusCodes.Status400BadRequest to enhance error handling for malformed delete requests.

Possibly related PRs

Suggested reviewers

  • MagnusSandgren
  • arealmaas

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c30a08e and 24f3db9.

📒 Files selected for processing (1)
  • src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs (1)

Line range hint 21-31: Add 410 GONE to API documentation

The endpoint configuration needs to be updated to document the new 410 GONE response for already deleted dialogs. This is important for maintaining accurate API documentation.

Apply this diff:

         Description(b => b.ProducesOneOf(
             StatusCodes.Status204NoContent,
             StatusCodes.Status404NotFound,
+            StatusCodes.Status410Gone,
             StatusCodes.Status412PreconditionFailed));
src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (3)

47-47: Consider index implications of ignoring query filters.

While IgnoreQueryFilters() is necessary here to check for deleted dialogs, it might bypass database indexes that include the deletion status. This could impact query performance if there are many soft-deleted records.

Consider adding a covering index that includes both the Id and Deleted columns to optimize this query pattern.


55-60: Enhance error messaging for API consumers.

While the logic is correct, consider adding more context to help API consumers understand why they're receiving a 410 GONE response and what actions they can take.

-            return new EntityDeleted<DialogEntity>(request.Id);
+            return new EntityDeleted<DialogEntity>(request.Id, 
+                "The requested dialog has been deleted. No further action is required.");

57-58: Make the TODO more specific.

The TODO comment could be more specific about what needs to be updated when restoration is implemented.

-            // TODO: When restoration is implemented, add a hint to the error message.
-            // https://github.com/digdir/dialogporten/pull/406
+            // TODO: When dialog restoration is implemented (PR #406), 
+            // update the error message to include instructions for restoration if applicable.
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 12e4b0b and 07e949e.

📒 Files selected for processing (2)
  • src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2 hunks)
  • src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs (1 hunks)
🔇 Additional comments (4)
src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs (2)

38-42: LGTM! Implementation correctly handles deleted dialog case

The addition of the deleted case in the result matching follows the existing pattern and correctly implements the 410 GONE response for deleted dialogs, fulfilling the PR objective.


38-42: Verify DeleteDialogCommand implementation

Let's verify that the command handler correctly identifies already deleted dialogs.

✅ Verification successful

Based on the search results, I can now generate the final response as we have sufficient information about the DeleteDialogCommand implementation and soft delete handling:

DeleteDialogCommand correctly handles already deleted dialogs

The implementation properly handles repeated delete requests through:

  • Soft delete implementation via ISoftDeletableEntity interface on DialogEntity
  • Proper status checks in DeleteDialogCommandHandler that returns EntityDeleted result when attempting to delete an already deleted dialog
  • Consistent handling in the endpoint that maps EntityDeleted result to HTTP 410 Gone response
  • Domain events (DialogDeletedDomainEvent) are properly published when a dialog is deleted
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for DeleteDialogCommand handler implementation
ast-grep --pattern 'class DeleteDialogCommandHandler {
  $$$
  Handle($_) {
    $$$
  }
}'

# Search for related dialog entity definition to verify soft delete implementation
rg -A 5 "class Dialog.*{" 

Length of output: 214


Script:

#!/bin/bash
# Search for DeleteDialogCommand handler implementation with a more precise pattern
ast-grep --pattern 'class DeleteDialogCommandHandler'

# Then search for Handle method implementation
rg -A 20 "Handle.*DeleteDialogCommand"

# Search for Dialog class definition with fixed regex
rg -A 5 "class Dialog\s*{"

# Search for any soft delete related properties or methods in Dialog
rg -A 3 "IsDeleted|DeletedAt|Delete"

Length of output: 100702


Script:

#!/bin/bash
# Search for DeleteDialogCommand handler with fixed pattern
rg -A 20 "class DeleteDialogCommandHandler"

# Search for Dialog class and related soft delete properties
rg -A 10 "class DialogEntity"

# Search for soft delete related methods in DialogEntity
rg -A 5 "Delete.*Dialog"

Length of output: 78824

src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2)

22-22: LGTM! The OneOf type extension is well-structured.

The addition of EntityDeleted to the result types aligns with the PR objective and follows the existing pattern.


Line range hint 22-77: Verify consistent handling of deleted dialogs across endpoints.

The implementation looks good overall, but let's verify that other endpoints consistently handle deleted dialogs.

✅ Verification successful

Consistent handling of deleted dialogs confirmed across endpoints

The verification shows that all dialog-related endpoints consistently:

  • Use IgnoreQueryFilters() to include soft-deleted records in queries
  • Check for null dialog entities first
  • Have proper access control via WhereIf conditions for service owner endpoints

The implementation in DeleteDialogCommand aligns with the codebase's overall approach to soft deletion and follows the established pattern for handling deleted entities.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent handling of deleted dialogs across endpoints
# and handlers to ensure uniform behavior.

# Look for other endpoints that might need similar handling
ast-grep --pattern 'class $_Endpoint {
  $$$
  HandleAsync($$$) {
    $$$
  }
}'

# Check for other uses of IgnoreQueryFilters
rg "IgnoreQueryFilters\(\)" -A 5

# Look for other dialog deletion related code
rg "Deleted\s*=\s*true" -A 5

Length of output: 19605

arealmaas
arealmaas previously approved these changes Nov 29, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2)

47-47: Consider performance impact of ignoring query filters.

While IgnoreQueryFilters() is necessary to detect soft-deleted dialogs, it might impact query performance by scanning all records, including deleted ones. Consider adding an index on the Deleted column if you expect a significant number of soft-deleted records.


55-60: Enhance error message for API consumers.

While the logic is correct, consider enhancing the error message to provide more context to API consumers. This could include when the entity was deleted and potentially how to handle this situation.

-            return new EntityDeleted<DialogEntity>(request.Id);
+            return new EntityDeleted<DialogEntity>(request.Id)
+            {
+                Message = $"Dialog {request.Id} was previously deleted. Please create a new dialog or contact support if you need to restore this dialog."
+            };
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 07e949e and cd3b069.

📒 Files selected for processing (2)
  • src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2 hunks)
  • src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs (1 hunks)
🔇 Additional comments (2)
src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2)

22-22: LGTM! Good use of OneOf for type-safe result handling.

The addition of EntityDeleted to the result types properly models the "already deleted" state, enabling correct HTTP status code mapping (410 GONE) at the endpoint level.


Line range hint 22-60: Verify consistent implementation across endpoints.

Let's ensure this pattern is consistently implemented across all delete operations and properly mapped to HTTP status codes.

✅ Verification successful

Implementation verified and consistent with best practices

The delete operation implementation is properly structured and follows consistent patterns:

  • The DeleteDialogEndpoint correctly maps EntityDeleted to HTTP 410 Gone status using the GoneAsync extension method
  • The pattern is consistently used across the codebase, with EntityDeleted<T> being used in multiple related operations
  • The endpoint properly documents its response codes in the OpenAPI description
  • The implementation includes proper error handling for various scenarios (not found, forbidden, concurrency)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify consistent implementation of delete operations and status code mapping

# Check for other delete operations that should follow this pattern
ast-grep --pattern 'class $_DeleteCommand : IRequest<$_Result>'

# Check for proper HTTP status code mapping
rg -A 5 "EntityDeleted.*GoneAsync"

Length of output: 102


Script:

#!/bin/bash
# Let's try a different approach to find delete operations and their mappings

# Find all delete command handlers
ast-grep --pattern 'class $_DeleteCommandHandler : IRequestHandler<$_DeleteCommand, $_>'

# Find all delete-related result types
ast-grep --pattern 'class $_DeleteResult : OneOfBase<$_>'

# Look for HTTP response mappings in controller endpoints
rg -l "Delete.*Controller"

Length of output: 179


Script:

#!/bin/bash
# Let's search for delete-related patterns more broadly

# Find files containing delete operations
rg -l "Delete.*Command"

# Search for EntityDeleted type usage
rg "EntityDeleted[<]"

# Look for HTTP response mapping patterns
rg "IResult.*Gone"

# Search for delete endpoints in controllers
rg "Delete.*async"

Length of output: 3063


Script:

#!/bin/bash
# Let's examine the delete endpoint implementation and response mapping

# Check the delete endpoint implementation
cat src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs

# Look for HTTP response mapping patterns in the Results folder
rg -l "Results"

Length of output: 3462

@oskogstad oskogstad changed the title fix(webapi): Repeat delete requests should return 410 GONE fix(webapi): Repeat delete requests should return 400 BAD REQUEST Nov 29, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpointSummary.cs (1)

Line range hint 12-21: Update documentation to include repeated delete behavior

The description thoroughly explains soft delete behavior and concurrency control but should be updated to mention that attempting to delete an already deleted dialog will result in a 400 Bad Request response.

Add this information to the description:

 Note that the dialog will still be available on the single details endpoint, but will have a deleted status. It will not appear on the list endpoint for either service owners nor end users.
 If end users attempt to access the dialog via the details endpoint, they will get a 410 Gone response.
+
+Attempting to delete an already deleted dialog will result in a 400 Bad Request response.

 Optimistic concurrency control is implemented using the If-Match header.
src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (1)

55-60: Consider making the error message more user-friendly.

While the logic is correct, the error message could be more user-friendly by focusing on the business scenario rather than technical details.

-            return new BadRequest($"Entity '{nameof(DialogEntity)}' with key '{request.Id}' is already removed, and cannot be deleted again.");
+            return new BadRequest($"The dialog has already been deleted and cannot be deleted again.");
docs/schema/V1/swagger.verified.json (1)

5922-5931: Consider enhancing the error documentation for repeated delete scenarios.

While the 400 response definition is technically correct, consider adding more specific documentation about the error codes and messages that will be returned when attempting to delete an already deleted dialog. This would help API consumers better handle this specific case.

Add error example for repeated delete case:

 "400": {
   "content": {
     "application/problem+json": {
       "schema": {
         "$ref": "#/components/schemas/ProblemDetails"
-      }
+      },
+      "example": {
+        "type": "https://www.rfc-editor.org/rfc/rfc7231#section-6.5.1",
+        "title": "Validation error occurred",
+        "status": 400,
+        "detail": "Dialog is already deleted",
+        "errors": [
+          {
+            "code": "DialogAlreadyDeleted",
+            "name": "dialogId",
+            "reason": "The dialog has already been deleted"
+          }
+        ]
+      }
     }
   },
   "description": "Validation error occurred. See problem details for a list of errors."
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cd3b069 and c30a08e.

📒 Files selected for processing (4)
  • docs/schema/V1/swagger.verified.json (1 hunks)
  • src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2 hunks)
  • src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs (2 hunks)
  • src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpointSummary.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpoint.cs
🔇 Additional comments (4)
src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/Dialogs/Delete/DeleteDialogEndpointSummary.cs (1)

24-24: Consider using a more specific error message constant

The current implementation uses a generic ValidationError constant for the 400 Bad Request response. Consider creating a more specific constant that better describes the "already deleted" scenario.

Let's check the available constants:

src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Delete/DeleteDialogCommand.cs (2)

22-22: LGTM! Result type extension aligns with PR objective.

The addition of BadRequest to the DeleteDialogResult type properly supports the new requirement to handle repeated delete requests.


47-47: Verify consistent usage of IgnoreQueryFilters for soft-deleted dialogs.

The addition of IgnoreQueryFilters() is correct and necessary to check for already deleted dialogs.

✅ Verification successful

IgnoreQueryFilters is consistently used across dialog-related queries

The verification shows that IgnoreQueryFilters() is consistently used across all dialog-related queries where soft-deleted records need to be accessed. The codebase follows a global query filter pattern for soft deletion (via HasQueryFilter(x => !x.Deleted)), and IgnoreQueryFilters() is appropriately used to bypass this filter when needed. This is evident in:

  • All dialog retrieval operations (Get/Search)
  • Dialog management operations (Delete/Update/Purge)
  • Related entity queries (Activities/Transmissions/SeenLogs)
  • Both ServiceOwner and EndUser features
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if IgnoreQueryFilters is consistently used when checking deleted status
# Look for other places where we might need to check deleted status but might be missing IgnoreQueryFilters

# Search for dialog deletion related queries
rg -l "Deleted\s*[=|!]" | xargs rg "FirstOrDefaultAsync|SingleOrDefaultAsync|AnyAsync" -B 2

Length of output: 93


Script:

#!/bin/bash
# Let's try a different approach to find soft deletion patterns
# First, let's look for IgnoreQueryFilters usage
rg "IgnoreQueryFilters" -B 2 -A 2

# Then, let's search for Deleted property in entity configurations
ast-grep --pattern 'public bool Deleted { get; set; }'

# Also search for global query filters that might be applying soft deletion
rg "HasQueryFilter" -B 2 -A 2

Length of output: 17164

docs/schema/V1/swagger.verified.json (1)

5922-5931: LGTM! The 400 response definition is well structured.

The added 400 response for the delete endpoint follows OpenAPI best practices:

  • Uses proper content type (application/problem+json)
  • References the standard ProblemDetails schema
  • Includes clear description of the error scenario

Copy link

@oskogstad oskogstad merged commit f14861d into main Nov 29, 2024
24 checks passed
@oskogstad oskogstad deleted the fix/return-410-on-repeat-delete-requests branch November 29, 2024 13:34
oskogstad pushed a commit that referenced this pull request Nov 29, 2024
🤖 I have created a release *beep* *boop*
---


##
[1.40.1](v1.40.0...v1.40.1)
(2024-11-29)


### Bug Fixes

* **webapi:** Repeat delete requests should return 400 BAD REQUEST
([#1542](#1542))
([f14861d](f14861d))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants