-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: AIP-132 – GET for collections (#20)
- Loading branch information
1 parent
aa26319
commit 5748816
Showing
4 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
# GET for collections | ||
|
||
In REST APIs, it is customary to make a `GET` request to a resource | ||
collection's URI (for example, `/v1/publishers/{publisher}/books`) in order to | ||
retrieve a list of the resources within that collection. These are sometimes | ||
colloquially called "list" operations. | ||
|
||
## Guidance | ||
|
||
APIs **should** generally provide a `GET` method for resource collections | ||
unless it is not valuable for users to do so. When the `GET` method is used on | ||
a URI ending in a resource collection, the result should be a list of | ||
resources. For more information about using the `GET` method on a URI ending | ||
with a discrete resource ID, see AIP-131. | ||
|
||
### Requests | ||
|
||
Collection `GET` operations **must** be made by sending a `GET` request to the | ||
resource collection's URI: | ||
|
||
```http | ||
GET /v1/publishers/{publisher}/books HTTP/2 | ||
Host: library.googleapis.com | ||
Accept: application/json | ||
``` | ||
|
||
- The HTTP method **must** be `GET`. | ||
- The request **must** be safe and **must not** have side effects. | ||
- There **must not** be a request body. | ||
- If a `GET` 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 query string **may** include fields for common design patterns | ||
relevant to list methods, such as `string filter` and `string orderBy`. | ||
- The query string **may** include custom fields if necessary for a specific | ||
resource, but not in place of any of the common fields for list methods. | ||
|
||
### Responses | ||
|
||
Collection `GET` operations **must** return a page of results, with each | ||
individual result being a resource: | ||
|
||
```json | ||
{ | ||
"results": [ | ||
{ | ||
"name": "publishers/lacroix/books/les-mis", | ||
"isbn": "978-037-540317-0", | ||
"title": "Les Misérables", | ||
"authors": ["Victor Hugo"], | ||
"rating": 9.6 | ||
}, | ||
{ | ||
"name": "publishers/lacroix/books/hunchback-of-notre-dame", | ||
"isbn": "978-140-274575-1", | ||
"title": "The Hunchback of Notre Dame", | ||
"authors": ["Victor Hugo"], | ||
"rating": 9.3 | ||
} | ||
], | ||
"nextPageToken": "xyz" | ||
} | ||
``` | ||
|
||
- The array of resources **must** be named `results` and contain resources with | ||
no additional wrapping. | ||
- The `string nextPageToken` field **must** be included in the list response | ||
schema. It **must** be set if there are subsequent pages, and **must not** be | ||
set if the response represents the final page. For more information, see | ||
AIP-158. | ||
- The response struct **may** include a `int32 totalSize` (or | ||
`int64 totalSize`) field with the number of items in the collection. | ||
- The value **may** be an estimate (the field **should** clearly document | ||
this if so). | ||
- If filtering is used, the `totalSize` field **should** reflect the size of | ||
the collection _after_ the filter is applied. | ||
|
||
**Note:** List methods **may** return the complete collection to any user with | ||
permission to make a successful List request on the collection, _or_ **may** | ||
return a collection only containing resources for which the user has read permission. | ||
This behavior **should** be clearly documented either for each List method or as | ||
a standard convention in service-level documentation. Permission checks on individual | ||
resources may have a negative performance impact so should be used only where | ||
absolutely necessary. | ||
|
||
### Errors | ||
|
||
If the user does not have sufficient permission to know that the collection | ||
exists, the service **should** reply with an HTTP 404 error, regardless of | ||
whether or not the collection exists. Permission **must** be checked prior to | ||
checking whether the collection exists. | ||
|
||
If the user does have proper permission, but the requested collection does not | ||
exist (generally because the parent does not exist), the service **must** reply | ||
with an HTTP 404 error. | ||
|
||
**Note:** An empty collection which the user has permission to access **must** | ||
return `200 OK` with an empty results array, and not `404 Not Found`. | ||
|
||
### Ordering | ||
|
||
`List` methods **may** allow clients to specify sorting order; if they do, the | ||
request message **should** contain a `string orderBy` field. | ||
|
||
- Values **should** be a comma separated list of fields. For example: | ||
`"foo,bar"`. | ||
- The default sorting order is ascending. To specify descending order for a | ||
field, users append a `-` prefix; for example: `"foo,-bar"`, `"-foo,bar"`. | ||
- Redundant space characters in the syntax are insignificant. `"foo, -bar"`, | ||
`" foo , -bar "`, and `"foo,-bar"` are all equivalent. | ||
- Subfields are specified with a `.` character, such as `foo.bar` or | ||
`address.street`. | ||
|
||
**Note:** Only include ordering if there is an established need to do so. It is | ||
always possible to add ordering later, but removing it is a breaking change. | ||
|
||
### Filtering | ||
|
||
List methods **may** allow clients to specify filters; if they do, the request | ||
message **should** contain a `string filter` field. Filtering is described in | ||
more detail in AIP-160. | ||
|
||
**Note:** Only include filtering if there is an established need to do so. It | ||
is always possible to add filtering later, but removing it is a breaking | ||
change. | ||
|
||
### Soft-deleted resources | ||
|
||
Some APIs need to "[soft delete][aip-135]" resources, marking them as deleted | ||
or pending deletion (and optionally purging them later). | ||
|
||
APIs that do this **should not** include deleted resources by default in list | ||
requests. APIs with soft deletion of a resource **should** include a | ||
`bool showDeleted` field in the list request that, if set, will cause | ||
soft-deleted resources to be included. | ||
|
||
## Interface Definitions | ||
|
||
{% tab proto -%} | ||
|
||
List operations are specified using the following pattern: | ||
|
||
{% sample 'list.proto', 'rpc ListBooks' %} | ||
|
||
- The RPC's name **must** begin with the word `List`. The remainder of the RPC | ||
name **should** be the plural form of the resource's message name. | ||
- The request message **must** match the RPC name, with a `-Request` suffix. | ||
- The response message **must** match the RPC name, with a `-Response` suffix. | ||
- The response **should** usually include fully-populated resources unless | ||
there is a reason to return a partial response (see AIP-157). | ||
- The HTTP verb **must** be `GET`. | ||
- The URI **should** contain a single variable field corresponding to the | ||
collection parent's name. | ||
- This field **should** be called `parent`. | ||
- The URI **should** have a variable corresponding to this field. | ||
- The `parent` 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 `"parent"`. | ||
|
||
List operations also implement a common request message pattern: | ||
|
||
{% sample 'list.proto', 'message ListBooksRequest' %} | ||
|
||
- A `parent` field **must** be included unless the resource being listed is a | ||
top-level resource. It **should** be called `parent`. | ||
- The field **should** be [annotated as required][aip-203]. | ||
- The field **should** identify the [resource type][aip-123] of the resource | ||
being listed. | ||
- The `max_page_size` and `page_token` fields, which support pagination, | ||
**must** be specified on all list request messages. For more information, see | ||
AIP-158. | ||
|
||
**Note:** The `parent` field in the request object corresponds to the `parent` | ||
variable in the `google.api.http` annotation on the RPC. This causes the | ||
`parent` field in the request to be populated based on the value in the URL | ||
when the REST/JSON interface is used. | ||
|
||
{% sample 'list.proto', 'message ListBooksResponse' %} | ||
|
||
- The response message **must** include a field corresponding to the resources | ||
being returned, named for the English plural term for the resource, and | ||
**should not** include any other repeated fields. | ||
- Fields providing metadata about the list request (such as | ||
`string next_page_token` or `int32 total_size`) **must** be included on the | ||
response message (not as part of the resource itself). | ||
|
||
{% tab oas %} | ||
|
||
Collection `GET` operations **must** be specified with consistent OpenAPI | ||
metadata: | ||
|
||
{% sample 'list.oas.yaml', 'paths' %} | ||
|
||
- The `operationId` **must** begin with the word `list`. The remainder of the | ||
`operationId` **should** be the plural form of the resource type's name. | ||
- The response content **must** be the resource itself. For example: | ||
`#/components/schemas/Book` | ||
- The response **should** usually include the fully-populated resource unless | ||
there is a reason to return a partial response (see AIP-157). | ||
- 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 parent resources **must** end with `Id`. | ||
|
||
{% endtabs %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
id: 132 | ||
state: approved | ||
created: 2019-01-22 | ||
placement: | ||
category: operations | ||
order: 20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
--- | ||
openapi: 3.0.3 | ||
info: | ||
title: Library | ||
version: 1.0.0 | ||
paths: | ||
/publishers/{publisherId}/books: | ||
parameters: | ||
- name: publisherId | ||
in: path | ||
description: The publisher to list books for. | ||
required: true | ||
schema: | ||
type: string | ||
get: | ||
operationId: listBooks | ||
description: Get a collection of books. | ||
parameters: | ||
- name: pageToken | ||
in: query | ||
description: | | ||
The page token. | ||
If a `next_page_token` value was received on a previous | ||
ListBooks call, providing it here will return the next page. | ||
schema: | ||
type: string | ||
- name: maxPageSize | ||
in: query | ||
description: | | ||
The maximum number of books to return. | ||
schema: | ||
type: integer | ||
responses: | ||
'200': | ||
description: OK | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/ListBooksResponse' | ||
components: | ||
schemas: | ||
ListBooksResponse: | ||
type: object | ||
description: A representation of a collection of books. | ||
properties: | ||
results: | ||
type: array | ||
items: | ||
'$ref': '#/components/schemas/Book' | ||
nextPageToken: | ||
type: string | ||
description: | | ||
The token to retrieve the next page. This is populated if and only | ||
if there are more pages. | ||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// 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 { | ||
// Get a single book. | ||
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) { | ||
option (google.api.http) = { | ||
get: "/v1/{parent=publishers/*}/books" | ||
}; | ||
option (google.api.method_signature) = "parent"; | ||
} | ||
} | ||
|
||
// Request message to list a collection of books. | ||
message ListBooksRequest { | ||
// The publisher to list books for. | ||
string parent = 1 [ | ||
(google.api.field_behavior) = REQUIRED, | ||
(google.api.resource_reference) = { | ||
child_type: "library.googleapis.com/Book" | ||
}]; | ||
|
||
// The maximum number of books to return. | ||
// The service may send fewer. | ||
int32 max_page_size = 2; | ||
|
||
// The page token. | ||
// If a `next_page_token` value was received on a previous | ||
// ListBooks call, providing it here will return the next page. | ||
string page_token = 3; | ||
} | ||
|
||
// Response message for listing a collection of books. | ||
message ListBooksResponse { | ||
// The books under the umbrella of the given publisher. | ||
repeated Book results = 1; | ||
|
||
// The token to retrieve the next page. This is populated if and only if | ||
// there are more pages. | ||
string next_page_token = 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; | ||
} |