Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions aep/general/0164/aep.md.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Soft delete

**NOTE: this pattern is now deprecated, and is no longer valid. Please use
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't created our first edition, so I don't think we should be "deprecating" AEPs. Let's delete it.

[undelete](/undelete) instead.**

There are several reasons why a client could desire soft delete and undelete
functionality, but one over-arching reason stands out: recovery from mistakes.
A service that supports undelete makes it possible for users to recover
Expand Down
2 changes: 1 addition & 1 deletion aep/general/0164/aep.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: 164
state: approved
state: deprecated
slug: soft-delete
created: 2020-10-06
placement:
Expand Down
86 changes: 86 additions & 0 deletions aep/general/0301/aep.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Undelete

There are several reasons why a client could desire undelete functionality, but
one over-arching reason stands out: recovery from mistakes. A service that
supports undelete makes it possible for users to recover resources that were
deleted by accident.

## Guidance

Services **may** support the ability to "undelete", to allow for situations
where users mistakenly delete resources and need the ability to recover.

These resources **must** be stored in a separate sibling collection, prefixed
with `deleted-`. (e.g. `deleted-books`). Resources deleted will remain in this
sibling collection until they expire, or until they are undeleted into the
original collection.

Resources that support soft delete **should** have an `expire_time` field on
the deleted version of the resource, as described in AEP-148.

### Sibling collection

To implement the undelete pattern, a sibling collection,
`deleted-{resource_plural}`, **should** be created where all undeletable
resources can be listed and retrieved, as well as undeleted via the `Undelete`
custom method.

### Undelete

The `Undelete` custom method **should** be available. A successful call to this
method will:

1. remove the resource from the deleted collection.
2. restore the resource back into the original collection.

{% tab proto %}

{% sample '../example.proto', 'rpc UndeleteDeletedPublisher', 'message UndeleteDeletedPublisherRequest' %}

- The HTTP method **must** be `POST`.
- The `body` clause **must** be `"*"`.
- The response **may** include the fully-populated resource or an empty
response.
- A `path` field **must** be included in the request message; it **should** be
called `path`.
- The field **should** be [annotated as required][aep-203].
- The field **should** identify the [resource type][aep-4] 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 AEP.

{% tab oas %}

{% sample '../example.oas.yaml', '$.paths./deleted-publishers/{deleted_publisher_id}:undelete' %}

- The HTTP method **must** be `POST`.
- The response message **must** be the resource itself.
- The response **may** include the fully-populated resource or an empty
response.
- The operation **must not** require any other fields, and **should not**
contain other optional query parameters except those described in this or
another AEP.

{% endtabs %}

### Long-running undelete

Some resources take longer to undelete a resource than is reasonable for a
regular API request. In this situation, the API **should** follow the
long-running request pattern AEP-151.

### Errors

If the user calling `Undelete` has proper permission, but the requested
resource is not deleted, the service **must** error with `409 Conflict`.

For additional guiance, see [errors](/errors).

## Further reading

- For the `Delete` standard method, see AEP-135.
- For long-running operations, see AEP-151.
- For resource freshness validation (`etag`), see AEP-154.
- For change validation (`validate_only`), see AEP-163.
8 changes: 8 additions & 0 deletions aep/general/0301/aep.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
id: 301
state: approved
slug: undelete
created: 2020-10-06
placement:
category: design-patterns
order: 95
88 changes: 88 additions & 0 deletions aep/general/example.oas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,27 @@ components:
- publishers/{publisher_id}/books/{book_id}/editions/{book_edition_id}
plural: book-editions
singular: book-edition
deleted_publisher:
properties:
description:
type: string
expire_time:
description:
The time when this deleted publisher will expire and be permanently
removed
format: date-time
type: string
path:
description:
The server-assigned path of the resource, which is unique within
the service.
type: string
type: object
x-aep-resource:
patterns:
- deleted-publishers/{deleted_publisher_id}
plural: deleted_publishers
singular: deleted_publisher
isbn:
properties:
path:
Expand Down Expand Up @@ -144,6 +165,73 @@ info:
version: version not set
openapi: 3.1.0
paths:
/deleted-publishers:
get:
description: List method for deleted_publisher
operationId: ListDeletedPublisher
parameters:
- in: query
name: max_page_size
schema:
type: integer
- in: query
name: page_token
schema:
type: string
responses:
'200':
content:
application/json:
schema:
properties:
next_page_token:
type: string
results:
items:
$ref: '#/components/schemas/deleted_publisher'
type: array
type: object
description: Successful response
/deleted-publishers/{deleted_publisher_id}:
get:
description: Get method for deleted_publisher
operationId: GetDeletedPublisher
parameters:
- in: path
name: deleted_publisher_id
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/deleted_publisher'
description: Successful response
/deleted-publishers/{deleted_publisher_id}:undelete:
post:
description: Custom method undelete for deleted_publisher
operationId: :UndeleteDeletedPublisher
parameters:
- in: path
name: deleted_publisher_id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
required: true
responses:
'200':
content:
application/json:
schema:
type: object
description: Successful response
/isbns:
get:
description: List method for isbn
Expand Down
94 changes: 94 additions & 0 deletions aep/general/example.proto
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,28 @@ service Bookstore {
option (google.api.method_signature) = "parent";
}

// An aep-compliant Get method for deleted_publisher.
rpc GetDeletedPublisher(GetDeletedPublisherRequest) returns (DeletedPublisher) {
option (google.api.http) = {get: "/{path=deleted-publishers/*}"};

option (google.api.method_signature) = "path";
}

// An aep-compliant List method for deleted_publishers.
rpc ListDeletedPublishers(ListDeletedPublishersRequest) returns (ListDeletedPublishersResponse) {
option (google.api.http) = {get: "/deleted_publishers"};

option (google.api.method_signature) = "parent";
}

// undelete a deleted_publisher.
rpc UndeleteDeletedPublisher(UndeleteDeletedPublisherRequest) returns (UndeleteDeletedPublisherResponse) {
option (google.api.http) = {
post: "/{path=deleted-publishers/*}:undelete"
body: "*"
};
}

// An aep-compliant Create method for isbn.
rpc CreateIsbn(CreateIsbnRequest) returns (Isbn) {
option (google.api.http) = {
Expand Down Expand Up @@ -351,6 +373,25 @@ message BookEdition {
string path = 10018;
}

// A DeletedPublisher.
message DeletedPublisher {
option (google.api.resource) = {
type: "bookstore.example.com/deleted_publisher"
pattern: ["deleted-publishers/{deleted_publisher_id}"]
plural: "deleted_publishers"
singular: "deleted_publisher"
};

// Field for description.
string description = 1;

// Field for expire_time.
string expire_time = 2 [json_name = "expire_time"];

// Field for path.
string path = 10018;
}

// A Isbn.
message Isbn {
option (google.api.resource) = {
Expand Down Expand Up @@ -650,6 +691,59 @@ message ListBookEditionsResponse {
string next_page_token = 10011 [json_name = "next_page_token"];
}

// Request message for the Getdeleted_publisher method
message GetDeletedPublisherRequest {
// The globally unique identifier for the resource
string path = 10018 [
(aep.api.field_info) = {
resource_reference: ["bookstore.example.com/deleted_publisher"]
field_behavior: [FIELD_BEHAVIOR_REQUIRED]
},
(google.api.field_behavior) = REQUIRED
];
}

// Request message for the Listdeleted_publisher method
message ListDeletedPublishersRequest {
// A field for the parent of deleted_publisher
string parent = 10013 [
(aep.api.field_info) = {
field_behavior: [FIELD_BEHAVIOR_REQUIRED]
},
(google.api.field_behavior) = REQUIRED
];

// The page token indicating the starting point of the page
string page_token = 10010 [json_name = "page_token"];

// The maximum number of resources to return in a single page.
int32 max_page_size = 10017 [json_name = "max_page_size"];
}

// Response message for the Listdeleted_publisher method
message ListDeletedPublishersResponse {
// A list of deleted_publishers
repeated DeletedPublisher results = 10016;

// The page token indicating the ending point of this response.
string next_page_token = 10011 [json_name = "next_page_token"];
}

// Response message for the undelete method
message UndeleteDeletedPublisherResponse {}

// Request message for the undelete method
message UndeleteDeletedPublisherRequest {
// The globally unique identifier for the resource
string path = 10018 [
(aep.api.field_info) = {
resource_reference: ["bookstore.example.com/deleted_publisher"]
field_behavior: [FIELD_BEHAVIOR_REQUIRED]
},
(google.api.field_behavior) = REQUIRED
];
}

// A Create request for a isbn resource.
message CreateIsbnRequest {
// A field for the parent of isbn
Expand Down
3 changes: 2 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ cd "${SG_DIRECTORY}" || exit
mkdir -p src/content/docs/tooling/linter/rules
mkdir -p src/content/docs/tooling/openapi-linter/rules
mkdir -p src/content/docs/tooling/website
npm install @playwright/test@latest
npm install
npx playwright install --with-deps chromium
npx playwright install
npm run generate
npm run build
Loading