Skip to content

Commit

Permalink
feat: AIP-132 – GET for collections (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukesneeringer authored Oct 25, 2022
1 parent aa26319 commit 5748816
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 0 deletions.
208 changes: 208 additions & 0 deletions aip/general/0132/aip.md.j2
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 %}
7 changes: 7 additions & 0 deletions aip/general/0132/aip.yaml
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
78 changes: 78 additions & 0 deletions aip/general/0132/list.oas.yaml
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.
83 changes: 83 additions & 0 deletions aip/general/0132/list.proto
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;
}

0 comments on commit 5748816

Please sign in to comment.