In order to prevent lost updates, REST APIs should send back an ETag header when querying an object, and subsequent DELETE/PATCH calls should include that ETag in an If-Match HTTP header.
If you are returning your Model directly (i.e. it also conforms to Content) you should now write your controller methods as follows. Note that they all return a Response now.
func get(req: Request) throws -> EventLoopFuture<Response> {
Todo.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { try .withETag($0) }
}
func patch(req: Request) throws -> EventLoopFuture<Response> {
return Todo.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { try $0.verifyETag(on: req) }
.flatMap { todo in
// Update all your properties here
return todo.update(on: req.db).flatMapThrowing {
// If your model update will modify related tables, you'll
// want to req-query the object here, before you create
// the response DTO.
return try .withETag(todo, includeBody: false)
}
}
}
func create(req: Request) throws -> EventLoopFuture<Response> {
let todo = try Todo(from: req.content.decode(TodoCreateDTO.self))
return todo.create(on: req.db)
.flatMapThrowing { try Response.created(todo, for: req) }
}
func delete(req: Request) throws -> EventLoopFuture<HTTPStatus> {
return Todo.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { try $0.verifyETag(on: req) }
.flatMap { $0.delete(on: req.db).transform(to: .noContent) }
}
If, however, you are using a DTO instead of directly returning the model, then conform your response object to ModelContent (instead of Content) and then write them like so.
func getDTO(req: Request) throws -> EventLoopFuture<Response> {
Todo.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { return try .withETag(TodoDTO(model: $0)) }
}
func patchDTO(req: Request) throws -> EventLoopFuture<Response> {
return Todo.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { try $0.verifyETag(TodoDTO.self, on: req)}
.flatMap { todo in
// Update all your properties here
return todo.update(on: req.db).flatMapThrowing {
// If your model update will modify related tables, you'll
// want to req-query the object here, before you create
// the response DTO.
return try .withETag(TodoDTO(model: todo), includeBody: false)
}
}
}
func createDTO(req: Request) throws -> EventLoopFuture<Response> {
let todo = try Todo(from: req.content.decode(TodoCreateDTO.self))
return todo.create(on: req.db)
.flatMapThrowing {
let dto = try TodoDTO(model: todo)
return try .created(dto, for: req, id: todo.requireID())
}
}
func deleteTDO(req: Request) throws -> EventLoopFuture<HTTPStatus> {
return Todo.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { try $0.verifyETag(TodoDTO.self, on: req) }
.flatMap { $0.delete(on: req.db).transform(to: .noContent) }
}
@tanner0101 This has to be deployed at the same time as https://github.com/vapor/vapor/pull/2246
In order to prevent lost updates, REST APIs should send back an ETag header when querying an object, and subsequent
DELETE
/PATCH
calls should include that ETag in anIf-Match
HTTP header.If you are returning your
Model
directly (i.e. it also conforms toContent
) you should now write your controller methods as follows. Note that they all return aResponse
now.If, however, you are using a DTO instead of directly returning the model, then conform your response object to
ModelContent
(instead ofContent
) and then write them like so.