Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 4 additions & 4 deletions aip/general/0131/aip.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ In REST APIs, it is customary to make a `GET` request to a resource's URI (for
example, `/v1/publishers/{publisher}/books/{book}`) in order to retrieve that
resource.

Our APIs honor this pattern by allowing `GET` requests to be sent to the
Services implement this pattern by allowing `GET` requests to be sent to the
resource URI, which returns the resource itself.

## Guidance
Expand Down Expand Up @@ -53,9 +53,9 @@ any additional wrapping:
### Errors

If the user does not have sufficient permission to know that the resource
exists, the service **should** reply with an HTTP 404 error, regardless of
whether or not the resource exists. Permission **must** be checked prior to
checking if the resource exists.
exists, the service **should** reply with an HTTP 403 error, regardless of
whether or not the resource exists, as described in AIP-211. Permission
**must** be checked prior to checking if the resource exists.

If the user has sufficient permission to know that the resource exists, but is
unable to access it, the service **should** reply with an HTTP 403 error.
Expand Down
213 changes: 213 additions & 0 deletions aip/general/0135/aip.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# DELETE for individual resources

In REST APIs, it is customary to make a `DELETE` request to a resource's URI
(for example, `/v1/publishers/{publisher}/books/{book}`) in order to delete
that resource.

Services implement this pattern by allowing `DELETE` requests to be sent to the
resource URI, which deletes the resource.

## Guidance

APIs **should** generally provide a `DELETE` method for resources unless it is
not valuable for users to do so. When the `DELETE` method is used on a URI
ending in a resource ID or resource ID alias, the resource should be deleted,
and the result should be a `204 No Content` empty response.

### Requests

Single-resource `DELETE` operations **must** be made by sending a `DELETE`
request to the resource's URI:

```http
DELETE /v1/publishers/{publisher}/books/{book} HTTP/2
Host: library.googleapis.com
Accept: application/json
```

- The HTTP method **must** be `DELETE`.
- There **must not** be a request body.
- If a `DELETE` request contains a body, the body **must** be ignored, and
**must not** cause an error.
- The request **must not** require any fields in the query string. The request
**should not** include optional fields in the query string unless described
in another AIP.
- Single-resource `DELETE` operations **must** return `204 No Content` with no
response body.
- Exception: If the resource is soft deleted (AIP-164), in which case the
operation **must** return `200 OK` and the resource itself, without any
additional wrapping.

{% tab proto %}

{% sample 'delete.proto', 'rpc DeleteBook' %}

- The RPC's name **must** begin with the word `Delete`. The remainder of the
RPC name **should** be the singular form of the resource's message name.
- The request message **must** match the RPC name, with a `-Request` suffix.
- The response message **should** be `google.protobuf.Empty`.
- If the resource is [soft deleted](#soft-delete), the response message
**should** be the resource itself.
- If the delete RPC is [long-running](#long-running-delete), the response
message **must** be a `google.longrunning.Operation` which resolves to the
correct response.
- The request message field receiving the resource name **should** map to the
URI path.
- This field **should** be called `name`.
- The `name` field **should** be the only variable in the URI path. All
remaining parameters **should** map to URI query parameters.
- There **must not** be a `body` key in the `google.api.http` annotation.
- There **should** be exactly one `google.api.method_signature` annotation,
with a value of `"name"`. If an etag or force field are used, they **may** be
included in the signature.

`Delete` methods have consistent request messages:

{% sample 'delete.proto', 'message DeleteBookRequest' %}

- A `name` field **must** be included. It **should** be called `name`.
- The field **should** be [annotated as required][aip-203].
- The field **should** identify the [resource type][aip-123] that it
references.
- The comment for the field **should** document the resource pattern.
- The request message **must not** contain any other required fields, and
**should not** contain other optional fields except those described in this
or another AIP.

{% tab oas %}

{% sample 'delete.oas.yaml', 'paths' %}

- The `operationId` **must** begin with the word `delete`. The remainder of the
`operationId` **should** be the singular form of the resource type's name.
- The URI **should** contain a variable for each individual ID in the resource
hierarchy.
- The path parameter for all resource IDs **must** be in the form
`{resourceName}Id` (such as `bookId`), and path parameters representing the
ID of the parent resources **must** end with `Id`.

{% endtabs %}

### Errors

If the user does not have sufficient permission to know that the resource
exists, the service **should** reply with an HTTP 403 error, regardless of
whether or not the resource exists, as described in AIP-211. Permission
**must** be checked prior to checking if the resource exists.

If the user has sufficient permission to know that the resource exists, but is
unable to access it, the service **should** error with `403 Forbidden`.

If the user does have proper permission, but the requested resource does not
exist, the service **must** error with `404 Not Found` unless `allow_missing`
is set to `true`.

### Soft delete

**Note:** This material was moved into its own document to provide a more
comprehensive treatment: AIP-164.

### Long-running delete

Some resources take longer to delete a resource than is reasonable for a
regular API request. In this situation, the operation **should** be defined as
a [long-running request][aip-151] instead:

{% tab proto %}

{% sample 'lro_delete.proto', 'rpc DeleteBook' %}

- The response type **must** be set to the appropriate return type if the RPC
was not long-running: `google.protobuf.Empty` for most Delete RPCs, or the
resource itself for soft delete (AIP-164).
- Both the `response_type` and `metadata_type` fields **must** be specified
(even if they are `google.protobuf.Empty`).

{% tab oas %}

{% sample 'lro_delete.oas.yaml', 'paths' %}

{% endtabs %}

**Note:** Declarative-friendly resources (AIP-128) **should** use long-running
delete.

### Cascading delete

Sometimes, it may be necessary for users to be able to delete a resource as
well as all applicable child resources. However, since deletion is usually
permanent, it is also important that users not do so accidentally, as
reconstructing wiped-out child resources may be quite difficult.

If an API allows deletion of a resource that may have child resources, the API
**should** provide a `bool force` field on the request, which the user sets to
explicitly opt in to a cascading delete.

{% tab proto %}

{% sample 'cascading_delete.proto', 'message DeletePublisherRequest' %}

{% tab oas %}

{% sample 'cascading_delete.oas.yaml', 'paths' %}

{% endtabs %}

The API **must** error with `412 Precondition Failed` if the `force` field is
`false` (or unset) and child resources are present.

### Protected delete

Sometimes, it may be necessary for users to ensure that no changes have been
made to a resource that is being deleted. If a resource provides an
[etag][aip-154], the delete request **may** accept the etag (as either required
or optional):

{% tab proto %}

{% sample 'protected_delete.proto', 'message DeleteBookRequest' %}

{% tab oas %}

{% sample 'protected_delete.oas.yaml', 'paths' %}

{% endtabs %}

If the etag is provided and does not match the server-computed etag, the
request **must** error with `412 Precondition Failed`.

**Note:** Declarative-friendly resources (AIP-128) **must** provide
[etags][aip-154] for Delete requests.

### Delete if existing

If the service uses client-assigned resource names, `Delete` methods **may**
expose a `bool allow_missing` field, which will cause the method to succeed in
the event that the user attempts to delete a resource that is not present (in
which case the request is a no-op):

{% tab proto %}

{% sample 'delete_if_existing.proto', 'message DeleteBookRequest' %}

{% tab oas %}

{% sample 'delete_if_existing.oas.yaml', 'paths' %}

{% endtabs %}

More specifically, the `allow_missing` flag triggers the following behavior:

- If the method call is on a resource that does not exist, the request is a
no-op.
- The `etag` field is ignored.
- If the method call is on a resource that already exists, the resource is
deleted (subject to other checks).

**Note:** Declarative-friendly resources (AIP-128) **should** expose the
`bool allow_missing` field.

## Further reading

- For soft delete and undelete, see AIP-164.
- For bulk deleting large numbers of resources based on a filter, see AIP-165.
7 changes: 7 additions & 0 deletions aip/general/0135/aip.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
id: 135
state: approved
created: 2019-01-24
placement:
category: operations
order: 50
65 changes: 65 additions & 0 deletions aip/general/0135/cascading_delete.oas.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
openapi: 3.0.3
info:
title: Library
version: 1.0.0
paths:
/publishers/{publisherId}/books/{bookId}:
parameters:
- $ref: "#/components/parameters/PublisherId"
- $ref: "#/components/parameters/BookId"
delete:
operationId: deleteBook
description: Delete a single book.
parameters:
- in: query
name: force
schema:
type: boolean
description: |
If set to true, any books from this publisher will also be deleted.
(Otherwise, the request fails unless the publisher has no books.)
responses:
'204':
description: Book was deleted
components:
parameters:
PublisherId:
name: publisherId
in: path
description: The id of the book publisher.
required: true
schema:
type: string
BookId:
name: bookId
in: path
description: The id of the book.
required: true
schema:
type: string
schemas:
Book:
description: A representation of a single book.
properties:
name:
type: string
description: |
The name of the book.
Format: publishers/{publisher}/books/{book}
isbn:
type: string
description: |
The ISBN (International Standard Book Number) for this book.
title:
type: string
description: The title of the book.
authors:
type: array
items:
type: string
description: The author or authors of the book.
rating:
type: number
format: float32
description: The rating assigned to the book.
46 changes: 46 additions & 0 deletions aip/general/0135/cascading_delete.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/protobuf/empty.proto";

service Library {
// Delete a single book.
rpc DeletePublisher(DeletePublisherRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/v1/{name=publishers/*}"
};
option (google.api.method_signature) = "name";
}
}

// Request message to delete a publisher.
message DeletePublisherRequest {
// The name of the publisher to delete.
// Format: publishers/{publisher}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
type: "library.googleapis.com/Publisher"
}];

// If set to true, any books from this publisher will also be deleted.
// (Otherwise, the request will only work if the publisher has no books.)
bool force = 2;
}
Loading