-
Notifications
You must be signed in to change notification settings - Fork 4
library search
Lets add a search by author operation which should find all the books by author pattern. It will expose the same
map[BookId,BookRecord]
type but will take an input parameter of type AuthorRecord
specifying search pattern.
Output projection will be the same as for the read operation, so lets extract and reuse it. Resulting schema:
resource books: map[BookId, BookRecord] {
// named output projection, to be reused by multiple operations
outputProjection bookProjection: BookRecord = (
title,
author :(
id,
`record` (firstName, middleName, lastName)
),
text :plain {
;offset: Long, // input parameter
;count: Long, // input parameter
meta: (offset, count) // supported meta-data projection
}
)
// default read operatoin
read {
outputProjection [ required ] ( // map keys are required
$bookProjection // book record projection defined above
)
}
// search operation
read searchByAuthor {
outputProjection {
// parameter declaration
;+author: AuthorRecord (firstName, middleName, lastName)
} [ forbidden ] ( // map keys are forbidden
$bookProjection
)
}
}
Every field of theauthor
parameter works as a filter if present. For instance if firstName
is not specified, then
any first name matches, otherwise only authors with this specific first name will be selected. Lets add corresponsing
lookup function to the AuthorsBackend
:
/**
* Finds authors by name
*
* @param firstName author first name or {@code null} for any
* @param middleName author middle name or {@code null} for any
* @param lastName author last name or {@code null} for any
*
* @return collection of matching author's IDs
*/
public static @NotNull Collection<@NotNull AuthorId> findAuthors(
@Nullable Optional<String> firstName,
@Nullable Optional<String> middleName,
@Nullable Optional<String> lastName) {
return authors.entrySet().stream()
.filter(e ->
(firstName != null && Objects.equals(firstName.orElse(null), e.getValue().firstName)) ||
(middleName != null && Objects.equals(middleName.orElse(null), e.getValue().middleName)) ||
(lastName != null && Objects.equals(lastName.orElse(null), e.getValue().lastName)) ||
(firstName == null && middleName == null && lastName == null)
)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
With this in place search action implementation is quite straightforward. It looks up the authors and then
reuses BookBuilder.buildBook
to populate results map.
/**
* Search by author custom operation implementation
*/
public class SearchByAuthorOperation extends AbstractReadSearchByAuthorOperation {
protected SearchByAuthorOperation(final @NotNull ReadOperationDeclaration declaration) {
super(declaration);
}
/**
* Runs the operation
*
* @param booksDataBuilder result builder to be populated, initially empty
* @param outputProjection request projection
*
* @return {@code Future} of the books map data
*/
@Override
protected @NotNull CompletableFuture<BookId_BookRecord_Map.Data> process(
final @NotNull BookId_BookRecord_Map.Builder.@NotNull Data booksDataBuilder,
final @NotNull OutputBooksFieldProjection outputProjection) {
final BookId_BookRecord_Map.Builder booksMap = BookId_BookRecord_Map.create();
final OutputBookId_BookRecord_MapProjection booksMapProjection = outputProjection.dataProjection();
final OutputBookRecordProjection bookRecordProjection = booksMapProjection.itemsProjection();
for (AuthorId author : findAuthors(booksMapProjection.getAuthorParameter())) {
for (BooksBackend.BookData book : BooksBackend.findByAuthor(author)) {
booksMap.put_(book.id, BookBuilder.buildBook(book.id, bookRecordProjection));
}
}
booksDataBuilder.set(booksMap);
return CompletableFuture.completedFuture(booksDataBuilder);
}
/**
* Find authors by pattern
*
* @param authorData author pattern. Absent fields are ignored
*
* @return collection of matching author IDs
*/
private @NotNull Collection<AuthorId> findAuthors(@NotNull AuthorRecord authorData) {
return AuthorsBackend.findAuthors(
authorData.getFirstName_() == null ? null : Optional.ofNullable(authorData.getFirstName()),
authorData.getMiddleName_() == null ? null : Optional.ofNullable(authorData.getMiddleName()),
authorData.getLastName_() == null ? null : Optional.ofNullable(authorData.getLastName())
);
}
}
Last step is to add new operation to BooksResourceFactory
@Override
protected @NotNull ReadOperation<BookId_BookRecord_Map.Data> constructSearchByAuthorReadOperation(
final @NotNull ReadOperationDeclaration operationDeclaration) throws ServiceInitializationException {
return new SearchByAuthorOperation(operationDeclaration);
}
Lets see how it works
curl -s -g -H "Epigraph-Operation: searchByAuthor" "http://localhost:8888/books;author={firstName:'Arthur'}[](title,author:record(firstName,middleName,lastName),text:plain;count=80)" | jq
[
{
"K": 2,
"V": {
"title": "A Study In Scarlet",
"author": {
"firstName": "Arthur",
"middleName": "Conan",
"lastName": "Doyle"
},
"text": "IN the year 1878 I took my degree of Doctor of Medicine of the University of Lon"
}
}
]
We have explicitly specifid operation name using Epigraph-Operation
HTTP header to ease up router's task,
but it would work just fine without it too since author
parameter presence allows to correctly pick the right
operation.
Lets find books by all authors without a middle name
curl -s -g "http://localhost:8888/books;author={middleName:null}[](title,author:record(firstName,middleName,lastName),text:plain;count=80)" | jq
[
{
"K": 3,
"V": {
"title": "The Adventures of Tom Sawyer",
"author": {
"firstName": "Mark",
"lastName": "Twain"
},
"text": "\"TOM!\"\nNo answer.\n\"TOM!\"\nNo answer.\n\"What's gone with that boy, I wonder? You TO"
}
},
{
"K": 1,
"V": {
"title": "The Gold Bug",
"author": {
"firstName": "Allan",
"lastName": "Poe"
},
"text": "MANY years ago, I contracted an intimacy with a Mr. William Legrand. He was of a"
}
}
]
Notice how framework requires author
parameter to be present:
curl -s -g -H "Epigraph-Operation: searchByAuthor" "http://localhost:8888/books[](title,author:record(firstName,middleName,lastName),text:plain;count=80)"
Failed to parse read operation 'searchByAuthor' request in resource 'books'
Required parameter 'author' is missing
/books[](title,author:record(firstName,middleName,lastName),text:plain;count=80)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Next section: adding create operation