diff --git a/ipa/general/0104.mdx b/ipa/general/0104.mdx index 3ebd39b..b2c8c31 100644 --- a/ipa/general/0104.mdx +++ b/ipa/general/0104.mdx @@ -6,49 +6,699 @@ state: adopt # IPA-104: Get In REST APIs, it is customary to make a `GET` request to a resource's URI (for -example, `/groups/{groupId}/clusters/{clusterName}`) to retrieve that resource. +example, `/projects/{projectId}/tasks/{taskId}`) to retrieve that resource. ## Guidance -- APIs **must** provide a Get method for resources -- The purpose of the Get method is to return data from a single resource -- The HTTP verb **must** be - [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) -- The method **must not** [cause side effects](0103.mdx) -- The request **must not** include a body -- API producers **should** implement as a `Response` suffixed object - - A `Response` object **must not** include fields available only on creation - or update - - In OpenAPI, this means that the `Response` object **must not** include - fields with `writeOnly: true` -- The response status code **must** be - [200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) -- The response **may** include a - [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS) `links` field + + + + +APIs **must** provide a Get method for resources. + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: getProject + responses: + "200": + description: OK +``` + + + The single-resource path exposes a `GET`, so the resource can be read back. + + + + + + +```yaml +paths: + /projects/{projectId}: + put: + operationId: updateProject + delete: + operationId: deleteProject +``` + + + The resource can be updated and deleted but never retrieved, so callers have + no way to read its current state. + + + + + + + Enumerate every entry under `paths` and group the paths by the resource they + address. + + + For each resource that has a single-resource path (a path ending in a + resource identifier) or is a singleton, confirm that path defines a `get` + operation. + + + Report any resource whose single-resource or singleton path has no `get` + operation. + + + + + + + + + +A Get method **must** return data from a single resource, not a collection or a +paginated list. + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: getProject + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/ProjectResponse" +``` + + + The response is a single object describing one project. + + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: getProject + responses: + "200": + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ProjectResponse" +``` + + + An array response describes a collection, which belongs on the List method, + not the Get method for one resource. + + + + + + + Collect every `get` operation defined on a single-resource path or a + singleton. + + + For each `2xx` response, follow the content schema (resolving any `$ref`) + and inspect its `type`. + + + Confirm the schema is a single object and not an `array` or a paginated + envelope (a wrapper carrying `results`, `totalCount`, or similar list + metadata). + + + Report any Get response whose schema is an array or a paginated result. + + + + + + + + + +The HTTP verb **must** be +[`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET). + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder +``` + + + Retrieving a resource uses the `GET` verb, the safe, read-only HTTP method. + + + + + + +```yaml +paths: + /orders/{orderId}: + post: + operationId: getOrder +``` + + + `POST` is not safe and not idempotent, so it cannot stand in for a read of a + single resource. + + + + + + + For each operation whose intent is to retrieve a single resource, read the + HTTP method it is defined under in the path item. + + + Confirm the method is `get` and not `post`, `put`, `patch`, or `delete`. + + + Report any single-resource read modeled under a verb other than `get`. + + + + + + + + + +The Get method **must not** [cause side effects](0103.mdx). + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + description: Returns the order. Does not modify any stored state. +``` + + + A Get only reads, so repeated calls return the same data and leave server + state unchanged. + + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + description: + Returns the order and marks it as viewed, decrementing its TTL. +``` + + + Mutating state on read makes the call unsafe, so caching, retries, and + prefetching can silently change data. + + + + + + + Collect every `get` operation on a single resource or singleton. + + + Read the operation description and, where available, the handler source it + maps to, since whether a read mutates state cannot be told from the contract + alone. + + + Determine whether the operation writes to storage, enqueues work, sends + notifications, or otherwise changes observable state. + + + Report any Get whose implementation produces a side effect, per + [IPA-103](0103.mdx). + + + + + + + + + +The request **must not** include a body. + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + parameters: + - name: orderId + in: path + required: true + schema: + type: string +``` + + + The resource is identified entirely by the path, so no request body is + defined. + + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + requestBody: + content: + application/json: + schema: + type: object +``` + + + A `GET` body is ignored by many clients, proxies, and caches, so any input + carried there cannot be relied on. + + + + + + + + + + +A Get method **should** return a `Response` suffixed object. + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/OrderResponse" +``` + + + The response references a named, reusable `OrderResponse` schema, which keeps + output models distinct from input models. + + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + responses: + "200": + content: + application/json: + schema: + type: object + properties: + id: + type: string +``` + + + An inline, unnamed schema cannot be referenced or named consistently, so the + output model is not reusable across operations. + + + + + + + + + + +A `Response` object **must not** include fields available only on creation or +update — in OpenAPI, fields marked `writeOnly: true`. + + + + + +```yaml +components: + schemas: + OrderResponse: + type: object + properties: + id: + type: string + total: + type: number +``` + + + Every property is readable, so the response carries only fields a reader can + see. + + + + + + +```yaml +components: + schemas: + OrderResponse: + type: object + properties: + id: + type: string + cardNumber: + type: string + writeOnly: true +``` + + + A `writeOnly` field is only meaningful on input, so its presence in a response + schema advertises a value that is never returned. + + + + + + + + + + +The response status code **must** be +[200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200). + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + responses: + "200": + description: OK +``` + + + A successful read returns `200 OK`, the standard status for a retrieved + resource. + + + + + + +```yaml +paths: + /orders/{orderId}: + get: + operationId: getOrder + responses: + "201": + description: Created +``` + + + `201 Created` signals that a resource was created, which never happens on a + read. + + + + + + + + + + The response **may** include a + [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS) `links` field. + + + Example ```http request -GET /groups/${groupId}/clusters/${clusterName} +GET /projects/${projectId}/tasks/${taskId} ``` ### Naming -- Operation ID **must** be unique -- Operation ID **must** be in `camelCase` -- Operation ID **must** start with the verb “get” -- Operation ID **should** be followed by a noun or compound noun - - The noun(s) in the Operation ID **should** be the collection identifiers - from the resource identifier in singular form - - If the resource is a [singleton](0113.mdx), the last noun **may** be the - plural form of the collection identifier + + + + +Operation ID **must** be unique. + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: getProject + /orders/{orderId}: + get: + operationId: getOrder +``` + + + Each operation has a distinct `operationId`, so generated method names do not + collide. + + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: get + /orders/{orderId}: + get: + operationId: get +``` + + + Two operations share the `operationId` `get`, so one overwrites the other in + generated clients. + + + + + + + Collect the `operationId` of every operation across all paths and methods. + + + Compare the values and find any `operationId` that appears more than once. + + + Report each duplicated `operationId`, naming the operations that share it. + + + + + + + + + +Operation ID **must** be in `camelCase`. + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: getProject +``` + + + `getProject` is `camelCase`, matching the casing every generated client + expects. + + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: get_project +``` + + + `get_project` is snake_case, so generated method names are inconsistent with + the rest of the API. + + + + + + + + + + +Operation ID **must** start with the verb "get". + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: getProject +``` + + + The `operationId` begins with `get`, matching the read semantics of the + method. + + + + + + +```yaml +paths: + /projects/{projectId}: + get: + operationId: fetchProject +``` + + + `fetchProject` uses a different verb, so the name no longer signals a Get + method consistently across the API. + + + + + + + + + + +Operation ID **should** be followed by a noun or compound noun, and that noun +**should** be the collection identifiers from the resource identifier in +singular form. + + + + + +```yaml +paths: + /projects/{projectId}/tasks/{taskId}: + get: + operationId: getProjectTask +``` + + + `ProjectTask` is the collection identifiers `projects` and `tasks` in singular + form, so the name mirrors the resource path. + + + + + + +```yaml +paths: + /projects/{projectId}/tasks/{taskId}: + get: + operationId: getProjectsTasks +``` + + + The plural collection names do not match the singular-resource read, so the + name describes a collection rather than the single task being retrieved. + + + + + + + + + + If the resource is a [singleton](0113.mdx), the last noun **may** be the + plural form of the collection identifier. + + + Examples: -| Resource Identifier | Operation ID | -| -------------------------------------------- | ------------------ | -| `/groups/${groupId}/clusters/${clusterName}` | `getGroupCluster` | -| (Singleton) `/groups/${groupId}/settings` | `getGroupSettings` | +| Resource Identifier | Operation ID | +| --------------------------------------------- | -------------------- | +| `/projects/${projectId}/tasks/${taskId}` | `getProjectTask` | +| (Singleton) `/projects/${projectId}/settings` | `getProjectSettings` | ### Error Handling