diff --git a/aip/general/0163/aip.md.j2 b/aip/general/0163/aip.md.j2 new file mode 100644 index 00000000..68c1d33a --- /dev/null +++ b/aip/general/0163/aip.md.j2 @@ -0,0 +1,78 @@ +# Change validation + +Occasionally, a user wants to validate an intended change to see what the +result will be before actually making the change. For example, a request to +provision new servers in a fleet will have an impact on the overall fleet size +and cost, and could potentially have unexpected downstream effects. + +## Guidance + +Operations **may** provide an field to validate, but not actually execute, the +request. To provide this option, the operation **should** include a `dry_run` +boolean field: + +```http +PATCH /v1/publishers/{publisher}/books/{book}?dryRun=true HTTP/2 +Host: library.googleapis.com +Accept: application/json +``` + +- Standard operations **must** expose the `dry_run` field on the query string. +- Custom operations **may** expose the `dry_run` field in the request body, on + the query string, or accept either one. + +### Responses + +An operation **may** return a `204 No Content` response when asked to perform a +dry run, which **must** be an empty response. + +Alternatively, an operation **may** provide the same response (status code, +headers, and response body) that it would have provided if the request was +actually executed. + +Under this approach, it may be infeasible to provide the full output. For +example, if creating a resource would create an auto-generated ID, it does not +make sense to do this on validation. The operation **should** prefer to use +`204 No Content` in this scenario, but if a populated response is needed, the +operation **should** omit such fields on the response. + +**Note:** When representing fields that can not be populated, non-zero or +non-empty sentinel values **must not** be used. + +### Validation scope + +When performing a dry run, the service **should** check all of the following: + +- The validity of the request according to the schema. +- Referential integrity of values in the request, if applicable. +- Whether the user has permission to make the request. +- Whether the user has sufficient quota to make the request. + +A request using `dry_run` **must** fail if it can be determined that the actual +request would fail. + +**Note:** An operation **may** elect not to exhaustively check certain +implementation-specific details if validating them would be expensive or pose a +security concern. Therefore, a successful dry run indicates that an equivalent +live request is likely to succeed, but is not a full guarantee of success. + +## Interface Definitions + +{% tab proto %} + +{% sample 'standard_operation.proto', 'message CreateBookRequest' %} + +- The `dry_run` field **must** use the `bool` type. +- The `dry_run` field **must not** be annotated as `REQUIRED`. +- In protocol buffers, the same response type is always used for a single RPC. + The operation **may** return the response message with no fields populated. + +{% tab operations %} + +{% sample 'standard_operation.oas.yaml', 'paths' %} + +- The `dryRun` query parameter **must** be `type: boolean`. +- The `dryRun` query parameter **must not** be `required: true`. +- createBook could also return a 204 response if `dryRun` is `true`. + +{% endtabs %} diff --git a/aip/general/0163/aip.yaml b/aip/general/0163/aip.yaml new file mode 100644 index 00000000..5a627fc2 --- /dev/null +++ b/aip/general/0163/aip.yaml @@ -0,0 +1,7 @@ +--- +id: 163 +state: approved +created: 2019-12-16 +placement: + category: design-patterns + order: 90 diff --git a/aip/general/0163/standard_operation.oas.yaml b/aip/general/0163/standard_operation.oas.yaml new file mode 100644 index 00000000..710e3923 --- /dev/null +++ b/aip/general/0163/standard_operation.oas.yaml @@ -0,0 +1,68 @@ +openapi: 3.0.3 +info: + title: Library + version: 1.0.0 +paths: + /publishers/{publisherId}/books/{bookId}: + parameters: + - name: publisherId + in: path + description: The id of the book publisher. + required: true + schema: + type: string + - name: bookId + in: path + description: The id of the book. + required: true + schema: + type: string + patch: + operationId: createBook + description: Create a single book. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + parameters: + - name: dryRun + in: query + description: | + If set, validate the request and preview the book, + but do not actually post it. + schema: + type: boolean + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Book' +components: + schemas: + Book: + description: A representation of a single book. + type: object + 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 + description: The rating assigned to the book. diff --git a/aip/general/0163/standard_operation.proto b/aip/general/0163/standard_operation.proto new file mode 100644 index 00000000..dbfcd487 --- /dev/null +++ b/aip/general/0163/standard_operation.proto @@ -0,0 +1,74 @@ +// 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"; + +service Library { + // Create a single book. + rpc CreateBook(CreateBookRequest) returns (Book) { + option (google.api.http) = { + post: "/v1/{parent=publishers/*}/books" + body: "book" + }; + option (google.api.method_signature) = "parent,book"; + } +} + + +// The request message for creating a book. +message CreateBookRequest { + // The parent collection where this book will be created. + // Format: publishers/{publisher} + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "library.googleapis.com/Book" + }]; + + // The book to create. + Book book = 2 [(google.api.field_behavior) = REQUIRED]; + + // If set, validate the request and preview the review, but do not actually + // post it. + bool dry_run = 3; +} + +// 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; +}