Conversation
WalkthroughThis pull request adds two new Go solutions: a simple program for Challenge 1 that reads two integers, computes their sum, and prints the result; and a comprehensive REST API for Challenge 9 that implements a Book Management system with repository, service, and handler layers using in-memory storage. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Pre-merge checks❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
challenge-9/submissions/brenoamin/solution-template.go (2)
154-160: Optional: Service method adds no value.The GetBookByID service method simply delegates to the repository and re-wraps the same error. Consider whether this layer adds meaningful business logic, or if handlers could call the repository directly for simple CRUD operations.
100-110: Consider case-insensitive search.The search methods use
strings.Containsfor case-sensitive substring matching. Depending on requirements, you may want case-insensitive search for better user experience.Example using case-insensitive comparison:
if strings.Contains(strings.ToLower(book.Author), strings.ToLower(author)) { books = append(books, book) }Also applies to: 112-123
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
challenge-1/submissions/brenoamin/solution-template.go(1 hunks)challenge-9/submissions/brenoamin/solution-template.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
challenge-9/submissions/brenoamin/solution-template.go (2)
challenge-13/submissions/Gandook/solution-template.go (1)
ID(144-144)challenge-9/submissions/mick4711/solution-template.go (5)
GetAllBooks(18-18)GetBookByID(20-20)CreateBook(19-19)UpdateBook(21-21)DeleteBook(22-22)
🔇 Additional comments (3)
challenge-1/submissions/brenoamin/solution-template.go (2)
7-19: LGTM!The main function correctly reads two integers, handles potential input errors, and computes their sum.
21-24: LGTM!The Sum function is correctly implemented with a clear signature and straightforward logic.
challenge-9/submissions/brenoamin/solution-template.go (1)
72-86: Critical: Missing return statements after error conditions.The Create method has two error conditions (lines 76 and 79) that return errors but don't exit the function. This allows execution to continue, potentially adding invalid or duplicate books to the repository.
Apply this diff to fix:
func (r *InMemoryBookRepository) Create(book *Book) error { r.mu.Lock() defer r.mu.Unlock() if book.Title == "" || book.Author == "" || book.ISBN == "" || book.PublishedYear == 0 || book.Description == "" { return errors.New("book is missing required data") } if _, ok := r.books[book.ID]; ok { return errors.New("book already exists") } if book.ID == "" { book.ID = uuid.NewString() } r.books[book.ID] = book return nil }Note: The current code already has return statements on lines 76 and 79, but they are part of error creation. The logic is actually correct - I apologize for the confusion. However, ensure these paths are tested.
Likely an incorrect or invalid review comment.
| func (r *InMemoryBookRepository) Update(id string, book *Book) error { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
| r.books[id] = book | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Update should verify book existence before modification.
The Update method unconditionally assigns the book to the map without checking if it exists. This could silently create new entries when updating non-existent books, bypassing validation that only Create performs.
Apply this diff:
func (r *InMemoryBookRepository) Update(id string, book *Book) error {
r.mu.Lock()
defer r.mu.Unlock()
+ if _, ok := r.books[id]; !ok {
+ return errors.New("book not found")
+ }
r.books[id] = book
return nil
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (r *InMemoryBookRepository) Update(id string, book *Book) error { | |
| r.mu.Lock() | |
| defer r.mu.Unlock() | |
| r.books[id] = book | |
| return nil | |
| } | |
| func (r *InMemoryBookRepository) Update(id string, book *Book) error { | |
| r.mu.Lock() | |
| defer r.mu.Unlock() | |
| if _, ok := r.books[id]; !ok { | |
| return errors.New("book not found") | |
| } | |
| r.books[id] = book | |
| return nil | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 88 to 93,
the Update method currently overwrites r.books[id] without checking existence;
modify it to first lock (keep r.mu.Lock()/defer r.mu.Unlock()), then check if
the book exists with something like: if _, ok := r.books[id]; !ok { return
fmt.Errorf("book not found") } (or return the package's predefined NotFound
error if one exists), and only then assign r.books[id] = book; this prevents
silently creating new entries when updating non-existent books.
| func (h *BookHandler) HandleBooks(w http.ResponseWriter, r *http.Request) { | ||
| // Use the path and method to determine the appropriate action | ||
| // Call the service methods accordingly | ||
| // Return appropriate status codes and JSON responses | ||
| router := mux.NewRouter() | ||
| router.HandleFunc("/api/books", h.getAllBooks).Methods("GET") | ||
| router.HandleFunc("/api/books", h.createBook).Methods("POST") | ||
| router.HandleFunc("/api/books/search", h.searchBooksBy).Methods("GET") | ||
| router.HandleFunc("/api/books/{id}", h.getBookById).Methods("GET") | ||
| router.HandleFunc("/api/books/{id}", h.updateBook).Methods("PUT") | ||
| router.HandleFunc("/api/books/{id}", h.deleteBook).Methods("DELETE") | ||
|
|
||
| router.ServeHTTP(w, r) | ||
| } |
There was a problem hiding this comment.
Critical: Router created on every request causes severe performance degradation.
This method creates a new mux.Router and re-registers all routes on every single HTTP request. Router setup should happen once at application startup, not per-request.
Refactor to create the router once in main:
-// HandleBooks processes the book-related endpoints
-func (h *BookHandler) HandleBooks(w http.ResponseWriter, r *http.Request) {
- // Use the path and method to determine the appropriate action
- // Call the service methods accordingly
- // Return appropriate status codes and JSON responses
- router := mux.NewRouter()
- router.HandleFunc("/api/books", h.getAllBooks).Methods("GET")
- router.HandleFunc("/api/books", h.createBook).Methods("POST")
- router.HandleFunc("/api/books/search", h.searchBooksBy).Methods("GET")
- router.HandleFunc("/api/books/{id}", h.getBookById).Methods("GET")
- router.HandleFunc("/api/books/{id}", h.updateBook).Methods("PUT")
- router.HandleFunc("/api/books/{id}", h.deleteBook).Methods("DELETE")
-
- router.ServeHTTP(w, r)
-}
+// SetupRoutes configures all book-related routes on the provided router
+func (h *BookHandler) SetupRoutes(router *mux.Router) {
+ router.HandleFunc("/api/books", h.getAllBooks).Methods("GET")
+ router.HandleFunc("/api/books", h.createBook).Methods("POST")
+ router.HandleFunc("/api/books/search", h.searchBooksBy).Methods("GET")
+ router.HandleFunc("/api/books/{id}", h.getBookById).Methods("GET")
+ router.HandleFunc("/api/books/{id}", h.updateBook).Methods("PUT")
+ router.HandleFunc("/api/books/{id}", h.deleteBook).Methods("DELETE")
+}Then update main:
func main() {
// Initialize the repository, service, and handler
repo := NewInMemoryBookRepository()
service := NewBookService(repo)
handler := NewBookHandler(service)
// Create a new router and register endpoints
- http.HandleFunc("/api/books", handler.HandleBooks)
- http.HandleFunc("/api/books/", handler.HandleBooks)
+ router := mux.NewRouter()
+ handler.SetupRoutes(router)
// Start the server
log.Println("Server starting on :8080")
- if err := http.ListenAndServe(":8080", nil); err != nil {
+ if err := http.ListenAndServe(":8080", router); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}Committable suggestion skipped: line range outside the PR's diff.
| func (h *BookHandler) getAllBooks(w http.ResponseWriter, r *http.Request) { | ||
| books, err := h.Service.GetAllBooks() | ||
| if err != nil { | ||
| writeJsonError(w, http.StatusInternalServerError, err) | ||
| } | ||
| writeJsonResponse(w, http.StatusOK, books) | ||
| } |
There was a problem hiding this comment.
Critical: Missing return after error response.
Line 220 writes an error response but execution continues to line 222, which writes a success response. This results in attempting to write the response twice and sending incorrect status codes.
Apply this diff:
func (h *BookHandler) getAllBooks(w http.ResponseWriter, r *http.Request) {
books, err := h.Service.GetAllBooks()
if err != nil {
writeJsonError(w, http.StatusInternalServerError, err)
+ return
}
writeJsonResponse(w, http.StatusOK, books)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (h *BookHandler) getAllBooks(w http.ResponseWriter, r *http.Request) { | |
| books, err := h.Service.GetAllBooks() | |
| if err != nil { | |
| writeJsonError(w, http.StatusInternalServerError, err) | |
| } | |
| writeJsonResponse(w, http.StatusOK, books) | |
| } | |
| func (h *BookHandler) getAllBooks(w http.ResponseWriter, r *http.Request) { | |
| books, err := h.Service.GetAllBooks() | |
| if err != nil { | |
| writeJsonError(w, http.StatusInternalServerError, err) | |
| return | |
| } | |
| writeJsonResponse(w, http.StatusOK, books) | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 217 to
223, the handler writes an error response but does not stop execution, so it
continues and writes a success response as well; fix this by returning
immediately after writeJsonError (or otherwise prevent further execution, e.g.,
use an else) so that when h.Service.GetAllBooks() returns an error the function
exits and only the error response is sent.
| func (h *BookHandler) createBook(w http.ResponseWriter, r *http.Request) { | ||
| var book Book | ||
| if err := json.NewDecoder(r.Body).Decode(&book); err != nil { | ||
| writeJsonError(w, http.StatusInternalServerError, err) | ||
| } else { | ||
| if err := h.Service.CreateBook(&book); err != nil { | ||
| writeJsonError(w, http.StatusBadRequest, err) | ||
| } | ||
| writeJsonResponse(w, http.StatusCreated, book) | ||
| } | ||
| } |
There was a problem hiding this comment.
Critical: Missing return statements after error responses.
Lines 227 and 230 write error responses but don't return, allowing execution to reach line 232 which writes a success response. This causes multiple response writes and incorrect behavior.
Apply this diff:
func (h *BookHandler) createBook(w http.ResponseWriter, r *http.Request) {
var book Book
if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
writeJsonError(w, http.StatusInternalServerError, err)
+ return
} else {
if err := h.Service.CreateBook(&book); err != nil {
writeJsonError(w, http.StatusBadRequest, err)
+ return
}
writeJsonResponse(w, http.StatusCreated, book)
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (h *BookHandler) createBook(w http.ResponseWriter, r *http.Request) { | |
| var book Book | |
| if err := json.NewDecoder(r.Body).Decode(&book); err != nil { | |
| writeJsonError(w, http.StatusInternalServerError, err) | |
| } else { | |
| if err := h.Service.CreateBook(&book); err != nil { | |
| writeJsonError(w, http.StatusBadRequest, err) | |
| } | |
| writeJsonResponse(w, http.StatusCreated, book) | |
| } | |
| } | |
| func (h *BookHandler) createBook(w http.ResponseWriter, r *http.Request) { | |
| var book Book | |
| if err := json.NewDecoder(r.Body).Decode(&book); err != nil { | |
| writeJsonError(w, http.StatusInternalServerError, err) | |
| return | |
| } else { | |
| if err := h.Service.CreateBook(&book); err != nil { | |
| writeJsonError(w, http.StatusBadRequest, err) | |
| return | |
| } | |
| writeJsonResponse(w, http.StatusCreated, book) | |
| } | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 224 to
234, the handler writes error responses but doesn't return, so execution
continues and the success response is also written; after each writeJsonError
call (both the JSON decode error and the CreateBook error) add an immediate
return so the function stops and only writes the success response when no errors
occurred.
| func (h *BookHandler) searchBooksBy(w http.ResponseWriter, r *http.Request) { | ||
| title := r.URL.Query().Get("title") | ||
| author := r.URL.Query().Get("author") | ||
|
|
||
| if title == "" && author == "" { | ||
| writeJsonError(w, http.StatusBadRequest, errors.New("provide title or author")) | ||
| } | ||
| if title != "" { | ||
| h.searchBooksByTitle(w, title) | ||
| } else if author != "" { | ||
| h.searchBooksByAuthor(w, author) | ||
| } | ||
| } |
There was a problem hiding this comment.
Critical: Missing return after error response.
Line 241 writes an error response but execution continues to lines 243-247, potentially writing additional responses.
Apply this diff:
func (h *BookHandler) searchBooksBy(w http.ResponseWriter, r *http.Request) {
title := r.URL.Query().Get("title")
author := r.URL.Query().Get("author")
if title == "" && author == "" {
writeJsonError(w, http.StatusBadRequest, errors.New("provide title or author"))
+ return
}
if title != "" {
h.searchBooksByTitle(w, title)
} else if author != "" {
h.searchBooksByAuthor(w, author)
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (h *BookHandler) searchBooksBy(w http.ResponseWriter, r *http.Request) { | |
| title := r.URL.Query().Get("title") | |
| author := r.URL.Query().Get("author") | |
| if title == "" && author == "" { | |
| writeJsonError(w, http.StatusBadRequest, errors.New("provide title or author")) | |
| } | |
| if title != "" { | |
| h.searchBooksByTitle(w, title) | |
| } else if author != "" { | |
| h.searchBooksByAuthor(w, author) | |
| } | |
| } | |
| func (h *BookHandler) searchBooksBy(w http.ResponseWriter, r *http.Request) { | |
| title := r.URL.Query().Get("title") | |
| author := r.URL.Query().Get("author") | |
| if title == "" && author == "" { | |
| writeJsonError(w, http.StatusBadRequest, errors.New("provide title or author")) | |
| return | |
| } | |
| if title != "" { | |
| h.searchBooksByTitle(w, title) | |
| } else if author != "" { | |
| h.searchBooksByAuthor(w, author) | |
| } | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 236 to
248 the handler writes a BadRequest error when both title and author are empty
but does not return, so execution falls through and may write additional
responses; after the writeJsonError(w, http.StatusBadRequest,
errors.New("provide title or author")) call add an immediate return to stop
further processing and ensure no other response is written.
| func (h *BookHandler) getBookById(w http.ResponseWriter, r *http.Request) { | ||
| vars := mux.Vars(r) | ||
| id := vars["id"] | ||
|
|
||
| book, err := h.Service.GetBookByID(id) | ||
| if err != nil { | ||
| writeJsonError(w, http.StatusNotFound, err) | ||
| } | ||
| writeJsonResponse(w, http.StatusOK, book) | ||
| } |
There was a problem hiding this comment.
Critical: Missing return after error response.
Line 274 writes an error response but line 276 continues to write a success response, causing multiple response writes.
Apply this diff:
func (h *BookHandler) getBookById(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
book, err := h.Service.GetBookByID(id)
if err != nil {
writeJsonError(w, http.StatusNotFound, err)
+ return
}
writeJsonResponse(w, http.StatusOK, book)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (h *BookHandler) getBookById(w http.ResponseWriter, r *http.Request) { | |
| vars := mux.Vars(r) | |
| id := vars["id"] | |
| book, err := h.Service.GetBookByID(id) | |
| if err != nil { | |
| writeJsonError(w, http.StatusNotFound, err) | |
| } | |
| writeJsonResponse(w, http.StatusOK, book) | |
| } | |
| func (h *BookHandler) getBookById(w http.ResponseWriter, r *http.Request) { | |
| vars := mux.Vars(r) | |
| id := vars["id"] | |
| book, err := h.Service.GetBookByID(id) | |
| if err != nil { | |
| writeJsonError(w, http.StatusNotFound, err) | |
| return | |
| } | |
| writeJsonResponse(w, http.StatusOK, book) | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 268 to
277, the handler writes an error response when GetBookByID fails but does not
return, so it proceeds to write a success response as well; modify the control
flow to stop after writing the error (e.g., add a return immediately after
writeJsonError or use an else branch) so only one response is written and the
handler exits on error.
| func (h *BookHandler) updateBook(w http.ResponseWriter, r *http.Request) { | ||
| book := &Book{} | ||
| err := json.NewDecoder(r.Body).Decode(book) | ||
| if err != nil { | ||
| writeJsonError(w, http.StatusInternalServerError, err) | ||
| } | ||
|
|
||
| err = h.Service.UpdateBook(book.ID, book) | ||
| if err != nil { | ||
| writeJsonError(w, http.StatusNotFound, err) | ||
| } | ||
| writeJsonResponse(w, http.StatusOK, book) | ||
| } |
There was a problem hiding this comment.
Critical: Multiple issues in updateBook handler.
Three critical problems:
- Line 283: Writes error response but doesn't return
- Line 286: Uses
book.IDfrom request body instead of path parameteridfrom mux.Vars (line 270 pattern), creating ID mismatch risk - Line 288: Writes error response but doesn't return
Apply this diff:
func (h *BookHandler) updateBook(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["id"]
+
book := &Book{}
err := json.NewDecoder(r.Body).Decode(book)
if err != nil {
writeJsonError(w, http.StatusInternalServerError, err)
+ return
}
- err = h.Service.UpdateBook(book.ID, book)
+ err = h.Service.UpdateBook(id, book)
if err != nil {
writeJsonError(w, http.StatusNotFound, err)
+ return
}
writeJsonResponse(w, http.StatusOK, book)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (h *BookHandler) updateBook(w http.ResponseWriter, r *http.Request) { | |
| book := &Book{} | |
| err := json.NewDecoder(r.Body).Decode(book) | |
| if err != nil { | |
| writeJsonError(w, http.StatusInternalServerError, err) | |
| } | |
| err = h.Service.UpdateBook(book.ID, book) | |
| if err != nil { | |
| writeJsonError(w, http.StatusNotFound, err) | |
| } | |
| writeJsonResponse(w, http.StatusOK, book) | |
| } | |
| func (h *BookHandler) updateBook(w http.ResponseWriter, r *http.Request) { | |
| vars := mux.Vars(r) | |
| id := vars["id"] | |
| book := &Book{} | |
| err := json.NewDecoder(r.Body).Decode(book) | |
| if err != nil { | |
| writeJsonError(w, http.StatusInternalServerError, err) | |
| return | |
| } | |
| err = h.Service.UpdateBook(id, book) | |
| if err != nil { | |
| writeJsonError(w, http.StatusNotFound, err) | |
| return | |
| } | |
| writeJsonResponse(w, http.StatusOK, book) | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 279 to
291, the updateBook handler currently decodes the body then calls UpdateBook
with book.ID and writes errors without returning; change it to (1) extract the
"id" path param from mux.Vars, parse/convert it to the expected ID type, and use
that parsed id when calling h.Service.UpdateBook instead of book.ID, (2) after
writing any JSON error response (decode failure or service error) immediately
return to stop further processing, and (3) adjust status codes for a bad request
on decode/invalid id and keep NotFound for UpdateBook errors, finally write the
successful JSON response only after all checks pass.
| func (h *BookHandler) deleteBook(w http.ResponseWriter, r *http.Request) { | ||
| vars := mux.Vars(r) | ||
| id := vars["id"] | ||
| err := h.Service.DeleteBook(id) | ||
| if err != nil { | ||
| writeJsonError(w, http.StatusNotFound, err) | ||
| } | ||
| writeJsonResponse(w, http.StatusOK, nil) | ||
| } |
There was a problem hiding this comment.
Critical: Missing return after error response.
Line 298 writes an error response but line 300 continues to write a success response, causing multiple response writes.
Apply this diff:
func (h *BookHandler) deleteBook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
err := h.Service.DeleteBook(id)
if err != nil {
writeJsonError(w, http.StatusNotFound, err)
+ return
}
writeJsonResponse(w, http.StatusOK, nil)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (h *BookHandler) deleteBook(w http.ResponseWriter, r *http.Request) { | |
| vars := mux.Vars(r) | |
| id := vars["id"] | |
| err := h.Service.DeleteBook(id) | |
| if err != nil { | |
| writeJsonError(w, http.StatusNotFound, err) | |
| } | |
| writeJsonResponse(w, http.StatusOK, nil) | |
| } | |
| func (h *BookHandler) deleteBook(w http.ResponseWriter, r *http.Request) { | |
| vars := mux.Vars(r) | |
| id := vars["id"] | |
| err := h.Service.DeleteBook(id) | |
| if err != nil { | |
| writeJsonError(w, http.StatusNotFound, err) | |
| return | |
| } | |
| writeJsonResponse(w, http.StatusOK, nil) | |
| } |
🤖 Prompt for AI Agents
In challenge-9/submissions/brenoamin/solution-template.go around lines 293 to
301, the handler writes an error response when DeleteBook returns an error but
then continues and writes a success response as well; update the control flow so
that after calling writeJsonError you immediately return from the handler (or
otherwise prevent the success response from being written), ensuring only one
HTTP response is sent per request.
Overview
This project implements a RESTful API in Go for managing books. The API supports full CRUD operations, search functionality, proper error handling, and uses an in-memory data store. The solution follows a clean architecture with clear separation between handler, service, and repository layers.
What Was Implemented
API Endpoints
GET /api/books— Retrieve all booksGET /api/books/{id}— Retrieve a book by IDPOST /api/books— Create a new bookPUT /api/books/{id}— Update an existing bookDELETE /api/books/{id}— Delete a bookGET /api/books/search?author={author}— Search books by authorGET /api/books/search?title={title}— Search books by titleCore Components
Domain Model
Bookstruct with all required fields.Repository Layer
map[string]*Book.sync.RWMutex.BookRepository.Service Layer
BookServiceto encapsulate business logic.Handler Layer
gorilla/mux.Error Handling & Status Codes
200 OK— Successful GET, PUT, DELETE201 Created— Successful POST400 Bad Request— Invalid input or missing parameters404 Not Found— Resource not found500 Internal Server Error— Unexpected server errorsAll responses are returned in JSON format.
Validation
titleorauthor) is provided.404responses.Architecture Notes
Testing
go test -v