diff --git a/aip/general/0164.md b/aip/general/0164.md index b19793b1e..9d4e21a31 100644 --- a/aip/general/0164.md +++ b/aip/general/0164.md @@ -98,6 +98,66 @@ rpc UndeleteBook(UndeleteBookRequest) returns (google.longrunning.Operation) { be if the RPC was not long-running). - Both the `response_type` and `metadata_type` fields **must** be specified. +### Expunge + +Resources that support soft delete **may** provide an `Expunge` custom method to +allow users to trigger immediate permanent deletion of a ready or soft-deleted +resource. This method can operate on resources that are currently in a ready or +soft-deleted state (e.g., `delete_time` is set). + +```proto +// Permanently deletes a soft-deleted Book. +rpc ExpungeBook(ExpungeBookRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/{name=publishers/*/books/*}:expunge" + body: "*" + }; + option (google.api.method_signature) = "name"; +} +``` + +- The URI must use a custom method with the :expunge suffix. +- The HTTP verb must be POST and the body clause must be "*". +- The response message must be `google.protobuf.Empty` or a `google.longrunning.Operation`. + +### Long-running expunge + +If the expunge process takes significant time, the method **may** be a +`google.longrunning.Operation` (AIP-151) instead: + +```proto +// Permanently deletes a soft-deleted Book. +rpc ExpungeBook(ExpungeBookRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + post: "/v1/{name=publishers/*/books/*}:expunge" + body: "*" + }; + option (google.longrunning.operation_info) = { + response_type: "google.protobuf.Empty" + metadata_type: "OperationMetadata" + }; + option (google.api.method_signature) = "name"; +} +``` + +### Expunge request message + +Expunge methods implement a common request message pattern: + +```proto +message ExpungeBookRequest { + // The name of the soft-deleted book to expunge. + // Format: publishers/{publisher}/books/{book} + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference).type = "library.googleapis.com/Book" + ]; +} +``` + +- The request message **must** refer to the resource to be expunged by name. +- There **should not** be any other request fields. + ### List and Get Soft-deleted resources **should not** be returned in `List` (AIP-132) responses @@ -139,6 +199,16 @@ If the user calling `Undelete` has proper permission, but the requested resource is not deleted, the service **must** respond with `ALREADY_EXISTS` (HTTP 409). +If the user calling `Expunge` requests a resource that does not exist (was never +created or already expunged), the method **must** return `NOT_FOUND` (HTTP 404). + +If the resource exists but is not in a ready or soft-deleted state, the method +**must** return `FAILED_PRECONDITION` (HTTP 400). + +Standard permission errors (`PERMISSION_DENIED`) apply. Services **must** require +an explicit expunge permission that is separate from standard delete permissions +(e.g., `..expunge`). + ## Further reading - For the `Delete` standard method, see AIP-135. @@ -148,6 +218,7 @@ resource is not deleted, the service **must** respond with `ALREADY_EXISTS` ## Changelog +- **2026-04-28**: Added guidance for the `Expunge` custom method. - **2024-09-24**: Included missing requirement for `delete_time`. - **2023-07-13**: Renamed overloaded `expire_time` to `purge_time`. - **2021-07-12**: Added error behavior when soft deleting a deleted resource.