-
Notifications
You must be signed in to change notification settings - Fork 4
library update
Update operation will take a map with book updates as an input and return a map of updated books as an output.
Operation schema is is shown below. Things to notice:
- keys are required for the input projection (in case input projection is specified in the request at all)
- keys are forbidden for the output projection: output will always contain keys from the input data
- output projection reuses
$bookProjection
(see search action)
update {
inputProjection [ required ] (
title,
author:id,
text:plain
)
outputProjection [ forbidden ] ( $bookProjection )
}
Operation implementation goes as follows: we extract all fields from the incoming BookRecord
instance,
apply them on top of existing book state and write resuling instance back to the backend. BooksBuilder
is then used to re-read updated instances and construct the response according to the output projection.
public class BooksUpdateOperation extends AbstractUpdateOperation {
BooksUpdateOperation(@NotNull UpdateOperationDeclaration declaration) {
super(declaration);
}
@Override
protected @NotNull CompletableFuture<BookId_BookRecord_Map.Data> process(
@NotNull BookId_BookRecord_Map.Builder.Data responseBuilder,
@NotNull BookId_BookRecord_Map updateData,
@Nullable UpdateBooksFieldProjection updateProjection,
@NotNull OutputBooksFieldProjection outputProjection) {
OutputBookRecordProjection bookOutputProjection = outputProjection.dataProjection().itemsProjection();
BookId_BookRecord_Map.Builder booksMapBuilder = BookId_BookRecord_Map.create();
responseBuilder.set(booksMapBuilder);
for (Map.Entry<BookId.Imm, ? extends BookRecord.Value> entry : updateData.values().entrySet()) {
BookId.Imm bookId = entry.getKey();
BookRecord bookUpdate = entry.getValue().getDatum();
assert bookUpdate != null; // ensured by framework
BooksBackend.BookData bookData = BooksBackend.get(bookId);
if (bookData == null) {
booksMapBuilder.putError(
bookId,
new ErrorValue(HttpStatusCode.NOT_FOUND, "Book " + bookId.getVal() + " not found")
);
} else {
// values from the update request
Optional<String> title = Optional.ofNullable(bookUpdate.getTitle());
Optional<AuthorId> author = Optional.ofNullable(bookUpdate.getAuthor()).map(Author::getId);
Optional<String> text = Optional.ofNullable(bookUpdate.getText()).map(Text::getPlain).map(PlainText::getVal);
// new fields state: either updates or existing data
String newTitle = title.orElse(bookData.title);
AuthorId newAuthorId = author.orElse(bookData.authorId);
String newText = text.orElse(bookData.text);
// check author ID
if (AuthorsBackend.get(newAuthorId) == null) {
booksMapBuilder.putError(
bookId,
new ErrorValue(HttpStatusCode.BAD_REQUEST, "Author " + newAuthorId.getVal() + " not found")
);
} else {
BooksBackend.set(
bookId,
new BooksBackend.BookData(bookId, newTitle, newAuthorId, newText)
);
// return updated book record
booksMapBuilder.put_(bookId, BookBuilder.buildBook(bookId, bookOutputProjection));
}
}
}
return CompletableFuture.completedFuture(responseBuilder);
}
}
Last step is to plug it into BooksResourceFactory
:
@Override
protected @NotNull UpdateOperation<BookId_BookRecord_Map.Data> constructUpdateOperation(
@NotNull UpdateOperationDeclaration operationDeclaration) throws ServiceInitializationException {
return new BooksUpdateOperation(operationDeclaration);
}
Lets modify two books in one call, updating title for 1
and author for 2
. Lets
use invalid author ID to see how errors are reported
curl -s -g -X PUT -d '[{"K":1,"V":{"title":"New Title"}},{"K":2,"V":{"author":14}}]' "http://localhost:8888/books>[](title,author:id)" | jq
[
{
"K": 1,
"V": {
"title": "New Title",
"author": 1
}
},
{
"K": 2,
"V": {
"ERROR": 400,
"message": "Author 14 not found"
}
}
]
Notice how >
is used to separate operation path from output projection. Full version with update projection specified
too would look like this:
http://localhost:8888/books<[1,2](title,author:id)>[](title,author:id)
Next section: delete operation