diff --git a/resource/resource.go b/resource/resource.go index 95ffb663..16db02d8 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -381,6 +381,23 @@ func (r *Resource) Update(ctx context.Context, item *Item, original *Item) (err return } +// Replace implements Storer interface +func (r *Resource) Replace(ctx context.Context, item *Item, original *Item) (err error) { + if LoggerLevel <= LogLevelDebug && Logger != nil { + defer func(t time.Time) { + Logger(ctx, LogLevelDebug, fmt.Sprintf("%s.Replace(%v, %v)", r.path, item.ID, original.ID), map[string]interface{}{ + "duration": time.Since(t), + "error": err, + }) + }(time.Now()) + } + if err = r.hooks.onUpdate(ctx, item, original); err == nil { + err = r.storage.Replace(ctx, item, original) + } + r.hooks.onUpdated(ctx, item, original, &err) + return +} + // Delete implements Storer interface func (r *Resource) Delete(ctx context.Context, item *Item) (err error) { if LoggerLevel <= LogLevelDebug && Logger != nil { diff --git a/resource/storage.go b/resource/storage.go index 5dd67542..e3254791 100644 --- a/resource/storage.go +++ b/resource/storage.go @@ -47,6 +47,20 @@ type Storer interface { // on the passed ctx. If the operation is stopped due to context cancellation, the // function must return the result of the ctx.Err() method. Update(ctx context.Context, item *Item, original *Item) error + // Replace replace an item in the backend store by a new version. The ResourceHandler must) + // ensure that the original item exists in the database and has the same Etag field. + // This check should be performed atomically. If the original item is not + // found, a resource.ErrNotFound must be returned. If the etags don't match, a + // resource.ErrConflict must be returned. + // + // The item payload must be stored together with the etag and the updated field. + // The item.ID and the payload["id"] is garantied to be identical, so there's not need + // to store both. + // + // If the storage of the data is not immediate, the method must listen for cancellation + // on the passed ctx. If the operation is stopped due to context cancellation, the + // function must return the result of the ctx.Err() method. + Replace(ctx context.Context, item *Item, original *Item) error // Delete deletes the provided item by its ID. The Etag of the item stored in the // backend store must match the Etag of the provided item or a resource.ErrConflict // must be returned. This check should be performed atomically. @@ -221,6 +235,16 @@ func (s storageWrapper) Update(ctx context.Context, item *Item, original *Item) return s.Storer.Update(ctx, item, original) } +func (s storageWrapper) Replace(ctx context.Context, item *Item, original *Item) (err error) { + if s.Storer == nil { + return ErrNoStorage + } + if ctx.Err() != nil { + return ctx.Err() + } + return s.Storer.Replace(ctx, item, original) +} + func (s storageWrapper) Delete(ctx context.Context, item *Item) (err error) { if s.Storer == nil { return ErrNoStorage diff --git a/rest/method_item_put.go b/rest/method_item_put.go index 9751cee8..8873128c 100644 --- a/rest/method_item_put.go +++ b/rest/method_item_put.go @@ -87,7 +87,7 @@ func itemPut(ctx context.Context, r *http.Request, route *RouteMatch) (status in // supposed check the original etag before storing when an original object // is provided. if original != nil { - if err := rsrc.Update(ctx, item, original); err != nil { + if err := rsrc.Replace(ctx, item, original); err != nil { e = NewError(err) return e.Code, nil, e }