From f7ce6bfc233be3bcbc11cd670b1dfeba7e89f016 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Tue, 13 Jul 2021 14:57:51 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20AIP-135=20=E2=80=93=20Delete=20for?= =?UTF-8?q?=20individual=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aip/general/0131/aip.md.j2 | 8 +- aip/general/0135/aip.md.j2 | 214 +++++++++++++++++++ aip/general/0135/aip.yaml | 7 + aip/general/0135/cascading_delete.oas.yaml | 46 ++++ aip/general/0135/cascading_delete.proto | 46 ++++ aip/general/0135/delete.oas.yaml | 38 ++++ aip/general/0135/delete.proto | 65 ++++++ aip/general/0135/delete_if_existing.oas.yaml | 46 ++++ aip/general/0135/delete_if_existing.proto | 69 ++++++ aip/general/0135/lro_delete.oas.yaml | 71 ++++++ aip/general/0135/lro_delete.proto | 69 ++++++ aip/general/0135/protected_delete.oas.yaml | 46 ++++ aip/general/0135/protected_delete.proto | 69 ++++++ 13 files changed, 790 insertions(+), 4 deletions(-) create mode 100644 aip/general/0135/aip.md.j2 create mode 100644 aip/general/0135/aip.yaml create mode 100644 aip/general/0135/cascading_delete.oas.yaml create mode 100644 aip/general/0135/cascading_delete.proto create mode 100644 aip/general/0135/delete.oas.yaml create mode 100644 aip/general/0135/delete.proto create mode 100644 aip/general/0135/delete_if_existing.oas.yaml create mode 100644 aip/general/0135/delete_if_existing.proto create mode 100644 aip/general/0135/lro_delete.oas.yaml create mode 100644 aip/general/0135/lro_delete.proto create mode 100644 aip/general/0135/protected_delete.oas.yaml create mode 100644 aip/general/0135/protected_delete.proto diff --git a/aip/general/0131/aip.md.j2 b/aip/general/0131/aip.md.j2 index 3fb804eb..f192573f 100644 --- a/aip/general/0131/aip.md.j2 +++ b/aip/general/0131/aip.md.j2 @@ -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 @@ -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. diff --git a/aip/general/0135/aip.md.j2 b/aip/general/0135/aip.md.j2 new file mode 100644 index 00000000..3a295f0c --- /dev/null +++ b/aip/general/0135/aip.md.j2 @@ -0,0 +1,214 @@ +# 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. + - 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 HTTP method **must** be `DELETE`. +- 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. diff --git a/aip/general/0135/aip.yaml b/aip/general/0135/aip.yaml new file mode 100644 index 00000000..b93141c5 --- /dev/null +++ b/aip/general/0135/aip.yaml @@ -0,0 +1,7 @@ +--- +id: 135 +state: approved +created: 2019-01-24 +placement: + category: operations + order: 50 diff --git a/aip/general/0135/cascading_delete.oas.yaml b/aip/general/0135/cascading_delete.oas.yaml new file mode 100644 index 00000000..135be342 --- /dev/null +++ b/aip/general/0135/cascading_delete.oas.yaml @@ -0,0 +1,46 @@ +--- +openapi: 3.0.3 +info: + title: Library + version: 1.0.0 +paths: + /publishers/{publisherId}/books/{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: No Content +components: + schema: + 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: float + description: The rating assigned to the book. diff --git a/aip/general/0135/cascading_delete.proto b/aip/general/0135/cascading_delete.proto new file mode 100644 index 00000000..fa6e890d --- /dev/null +++ b/aip/general/0135/cascading_delete.proto @@ -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 { + // Get 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; +} diff --git a/aip/general/0135/delete.oas.yaml b/aip/general/0135/delete.oas.yaml new file mode 100644 index 00000000..32a8f997 --- /dev/null +++ b/aip/general/0135/delete.oas.yaml @@ -0,0 +1,38 @@ +--- +openapi: 3.0.3 +info: + title: Library + version: 1.0.0 +paths: + /publishers/{publisherId}/books/{bookId}: + delete: + operationId: deleteBook + description: Delete a single book. + responses: + 204: + description: No Content +components: + schema: + 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: float + description: The rating assigned to the book. diff --git a/aip/general/0135/delete.proto b/aip/general/0135/delete.proto new file mode 100644 index 00000000..1215df99 --- /dev/null +++ b/aip/general/0135/delete.proto @@ -0,0 +1,65 @@ +// 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 { + // Get a single book. + rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/{name=publishers/*/books/*}" + }; + option (google.api.method_signature) = "name"; + } +} + +// Request message to delete a single book. +message DeleteBookRequest { + // The name of the book to delete. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "library.googleapis.com/Book" + }]; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The ISBN (International Standard Book Number) for this book. + string isbn = 2; + + // The title of the book. + string title = 3; + + // The author or authors of the book. + repeated string authors = 4; + + // The rating assigned to the book. + float rating = 5; +} diff --git a/aip/general/0135/delete_if_existing.oas.yaml b/aip/general/0135/delete_if_existing.oas.yaml new file mode 100644 index 00000000..c502d8cb --- /dev/null +++ b/aip/general/0135/delete_if_existing.oas.yaml @@ -0,0 +1,46 @@ +--- +openapi: 3.0.3 +info: + title: Library + version: 1.0.0 +paths: + /publishers/{publisherId}/books/{bookId}: + delete: + operationId: deleteBook + description: Delete a single book. + parameters: + - in: query + name: allow_missing + schema: + type: boolean + description: | + If set to true, and the book is not found, the request will succeed + but no action will be taken on the server + responses: + 204: + description: No Content +components: + schema: + 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: float + description: The rating assigned to the book. diff --git a/aip/general/0135/delete_if_existing.proto b/aip/general/0135/delete_if_existing.proto new file mode 100644 index 00000000..c8a7bdc2 --- /dev/null +++ b/aip/general/0135/delete_if_existing.proto @@ -0,0 +1,69 @@ +// 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 { + // Get a single book. + rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/{name=publishers/*/books/*}" + }; + option (google.api.method_signature) = "name"; + } +} + +// Request message to delete a single book. +message DeleteBookRequest { + // The name of the book to delete. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "library.googleapis.com/Book" + }]; + + // If set to true, and the book is not found, the request will succeed + // but no action will be taken on the server + bool allow_missing = 2; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The ISBN (International Standard Book Number) for this book. + string isbn = 2; + + // The title of the book. + string title = 3; + + // The author or authors of the book. + repeated string authors = 4; + + // The rating assigned to the book. + float rating = 5; +} diff --git a/aip/general/0135/lro_delete.oas.yaml b/aip/general/0135/lro_delete.oas.yaml new file mode 100644 index 00000000..65d36071 --- /dev/null +++ b/aip/general/0135/lro_delete.oas.yaml @@ -0,0 +1,71 @@ +--- +openapi: 3.0.3 +info: + title: Library + version: 1.0.0 +paths: + /publishers/{publisherId}/books/{bookId}: + delete: + operationId: deleteBook + description: Delete a single book. + responses: + 202: + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteBookStatus' +components: + schema: + 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: float + description: The rating assigned to the book. + DeleteBookStatus: + description: The status of the deleteBook operation. + allOf: + - $ref: '#/components/schemas/StatusMonitor' + - type: object + properties: + response: + type: object + description: Empty object. + metadata: + type: object + properties: + start_time: + type: string + format: date-time + description: The time the operation started. + progress_percent: + type: integer + format: int32 + description: The current progress, expressed as an integer. + state: + type: string + description: The current state of the operation. + enum: + - STATE_UNSPECIFIED + - RUNNING + - CANCELLING + - CANCELLED + - FAILED diff --git a/aip/general/0135/lro_delete.proto b/aip/general/0135/lro_delete.proto new file mode 100644 index 00000000..d16b5aed --- /dev/null +++ b/aip/general/0135/lro_delete.proto @@ -0,0 +1,69 @@ +// 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/longrunning/operations.proto"; + +service Library { + // Get a single book. + rpc DeleteBook(DeleteBookRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + delete: "/v1/{name=publishers/*/books/*}" + }; + option (google.longrunning.operation_info) = { + response_type: "google.protobuf.Empty" + metadata_type: "OperationMetadata" + }; + option (google.api.method_signature) = "name"; + } +} + +// Request message to delete a single book. +message DeleteBookRequest { + // The name of the book to delete. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "library.googleapis.com/Book" + }]; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The ISBN (International Standard Book Number) for this book. + string isbn = 2; + + // The title of the book. + string title = 3; + + // The author or authors of the book. + repeated string authors = 4; + + // The rating assigned to the book. + float rating = 5; +} diff --git a/aip/general/0135/protected_delete.oas.yaml b/aip/general/0135/protected_delete.oas.yaml new file mode 100644 index 00000000..e5f75944 --- /dev/null +++ b/aip/general/0135/protected_delete.oas.yaml @@ -0,0 +1,46 @@ +--- +openapi: 3.0.3 +info: + title: Library + version: 1.0.0 +paths: + /publishers/{publisherId}/books/{bookId}: + delete: + operationId: deleteBook + description: Delete a single book. + parameters: + - in: header + name: If-Match + schema: + type: string + description: | + The etag of the book. + If this is provided, it must match the server's etag. + responses: + 204: + description: No Content +components: + schema: + 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: float + description: The rating assigned to the book. diff --git a/aip/general/0135/protected_delete.proto b/aip/general/0135/protected_delete.proto new file mode 100644 index 00000000..44e2117b --- /dev/null +++ b/aip/general/0135/protected_delete.proto @@ -0,0 +1,69 @@ +// 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 { + // Get a single book. + rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/{name=publishers/*/books/*}" + }; + option (google.api.method_signature) = "name"; + } +} + +// Request message to delete a single book. +message DeleteBookRequest { + // The name of the book to delete. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "library.googleapis.com/Book" + }]; + + // Optional. The etag of the book. + // If this is provided, it must match the server's etag. + string etag = 2; +} + +// A representation of a single book. +message Book { + option (google.api.resource) = { + type: "library.googleapis.com/Book" + pattern: "publishers/{publisher}/books/{book}" + }; + + // The name of the book. + // Format: publishers/{publisher}/books/{book} + string name = 1; + + // The ISBN (International Standard Book Number) for this book. + string isbn = 2; + + // The title of the book. + string title = 3; + + // The author or authors of the book. + repeated string authors = 4; + + // The rating assigned to the book. + float rating = 5; +} From 8045cffa30a558d6ed1454c56c8ee7b445125dcb Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 30 Oct 2022 16:26:57 -0500 Subject: [PATCH 2/2] Improve/fix oas examples --- aip/general/0135/aip.md.j2 | 3 +- aip/general/0135/cascading_delete.oas.yaml | 27 ++++++++++-- aip/general/0135/cascading_delete.proto | 2 +- aip/general/0135/delete.oas.yaml | 27 ++++++++++-- aip/general/0135/delete.proto | 2 +- aip/general/0135/delete_if_existing.oas.yaml | 27 ++++++++++-- aip/general/0135/delete_if_existing.proto | 2 +- aip/general/0135/lro_delete.oas.yaml | 46 +++++++++++++++++--- aip/general/0135/lro_delete.proto | 2 +- aip/general/0135/protected_delete.oas.yaml | 27 ++++++++++-- aip/general/0135/protected_delete.proto | 2 +- 11 files changed, 139 insertions(+), 28 deletions(-) diff --git a/aip/general/0135/aip.md.j2 b/aip/general/0135/aip.md.j2 index 3a295f0c..ce436f21 100644 --- a/aip/general/0135/aip.md.j2 +++ b/aip/general/0135/aip.md.j2 @@ -33,7 +33,7 @@ Accept: application/json **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. + 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. @@ -51,7 +51,6 @@ Accept: application/json - 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 HTTP method **must** be `DELETE`. - The request message field receiving the resource name **should** map to the URI path. - This field **should** be called `name`. diff --git a/aip/general/0135/cascading_delete.oas.yaml b/aip/general/0135/cascading_delete.oas.yaml index 135be342..b1348f58 100644 --- a/aip/general/0135/cascading_delete.oas.yaml +++ b/aip/general/0135/cascading_delete.oas.yaml @@ -5,6 +5,9 @@ info: 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. @@ -17,10 +20,25 @@ paths: 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: No Content + '204': + description: Book was deleted components: - schema: + 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: @@ -42,5 +60,6 @@ components: type: string description: The author or authors of the book. rating: - type: float + type: number + format: float32 description: The rating assigned to the book. diff --git a/aip/general/0135/cascading_delete.proto b/aip/general/0135/cascading_delete.proto index fa6e890d..6c5cf079 100644 --- a/aip/general/0135/cascading_delete.proto +++ b/aip/general/0135/cascading_delete.proto @@ -21,7 +21,7 @@ import "google/api/resource.proto"; import "google/protobuf/empty.proto"; service Library { - // Get a single book. + // Delete a single book. rpc DeletePublisher(DeletePublisherRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/v1/{name=publishers/*}" diff --git a/aip/general/0135/delete.oas.yaml b/aip/general/0135/delete.oas.yaml index 32a8f997..26b809fe 100644 --- a/aip/general/0135/delete.oas.yaml +++ b/aip/general/0135/delete.oas.yaml @@ -5,14 +5,32 @@ info: 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. responses: - 204: - description: No Content + '204': + description: Book was deleted components: - schema: + 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: @@ -34,5 +52,6 @@ components: type: string description: The author or authors of the book. rating: - type: float + type: number + format: float32 description: The rating assigned to the book. diff --git a/aip/general/0135/delete.proto b/aip/general/0135/delete.proto index 1215df99..529215a9 100644 --- a/aip/general/0135/delete.proto +++ b/aip/general/0135/delete.proto @@ -21,7 +21,7 @@ import "google/api/resource.proto"; import "google/protobuf/empty.proto"; service Library { - // Get a single book. + // Delete a single book. rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/v1/{name=publishers/*/books/*}" diff --git a/aip/general/0135/delete_if_existing.oas.yaml b/aip/general/0135/delete_if_existing.oas.yaml index c502d8cb..43765103 100644 --- a/aip/general/0135/delete_if_existing.oas.yaml +++ b/aip/general/0135/delete_if_existing.oas.yaml @@ -5,6 +5,9 @@ info: 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. @@ -17,10 +20,25 @@ paths: If set to true, and the book is not found, the request will succeed but no action will be taken on the server responses: - 204: - description: No Content + '204': + description: Book was deleted components: - schema: + 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: @@ -42,5 +60,6 @@ components: type: string description: The author or authors of the book. rating: - type: float + type: number + format: float32 description: The rating assigned to the book. diff --git a/aip/general/0135/delete_if_existing.proto b/aip/general/0135/delete_if_existing.proto index c8a7bdc2..2900d09f 100644 --- a/aip/general/0135/delete_if_existing.proto +++ b/aip/general/0135/delete_if_existing.proto @@ -21,7 +21,7 @@ import "google/api/resource.proto"; import "google/protobuf/empty.proto"; service Library { - // Get a single book. + // Delete a single book. rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/v1/{name=publishers/*/books/*}" diff --git a/aip/general/0135/lro_delete.oas.yaml b/aip/general/0135/lro_delete.oas.yaml index 65d36071..15c89412 100644 --- a/aip/general/0135/lro_delete.oas.yaml +++ b/aip/general/0135/lro_delete.oas.yaml @@ -5,18 +5,36 @@ info: 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. responses: - 202: + '202': description: Accepted content: application/json: schema: $ref: '#/components/schemas/DeleteBookStatus' components: - schema: + 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: @@ -38,7 +56,8 @@ components: type: string description: The author or authors of the book. rating: - type: float + type: number + format: float32 description: The rating assigned to the book. DeleteBookStatus: description: The status of the deleteBook operation. @@ -52,11 +71,11 @@ components: metadata: type: object properties: - start_time: + startTime: type: string format: date-time description: The time the operation started. - progress_percent: + progressPercent: type: integer format: int32 description: The current progress, expressed as an integer. @@ -69,3 +88,20 @@ components: - CANCELLING - CANCELLED - FAILED + StatusMonitor: + description: The status of a long running operation. + type: object + properties: + name: + type: string + description: The server-assigned name, which is only unique within the same service that originally returns it. + done: + type: boolean + description: >- + If the value is false, it means the operation is still in progress. If true, the operation is completed, + and either response or error is available. + error: + $ref: '#/components/schemas/Error' + required: + - name + - done \ No newline at end of file diff --git a/aip/general/0135/lro_delete.proto b/aip/general/0135/lro_delete.proto index d16b5aed..a1c3924d 100644 --- a/aip/general/0135/lro_delete.proto +++ b/aip/general/0135/lro_delete.proto @@ -21,7 +21,7 @@ import "google/api/resource.proto"; import "google/longrunning/operations.proto"; service Library { - // Get a single book. + // Delete a single book. rpc DeleteBook(DeleteBookRequest) returns (google.longrunning.Operation) { option (google.api.http) = { delete: "/v1/{name=publishers/*/books/*}" diff --git a/aip/general/0135/protected_delete.oas.yaml b/aip/general/0135/protected_delete.oas.yaml index e5f75944..c486d4ed 100644 --- a/aip/general/0135/protected_delete.oas.yaml +++ b/aip/general/0135/protected_delete.oas.yaml @@ -5,6 +5,9 @@ info: 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. @@ -17,10 +20,25 @@ paths: The etag of the book. If this is provided, it must match the server's etag. responses: - 204: - description: No Content + '204': + description: Book was deleted components: - schema: + 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: @@ -42,5 +60,6 @@ components: type: string description: The author or authors of the book. rating: - type: float + type: number + format: float32 description: The rating assigned to the book. diff --git a/aip/general/0135/protected_delete.proto b/aip/general/0135/protected_delete.proto index 44e2117b..397901aa 100644 --- a/aip/general/0135/protected_delete.proto +++ b/aip/general/0135/protected_delete.proto @@ -21,7 +21,7 @@ import "google/api/resource.proto"; import "google/protobuf/empty.proto"; service Library { - // Get a single book. + // Delete a single book. rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/v1/{name=publishers/*/books/*}"