Last active
December 21, 2022 21:27
-
-
Save scottcagno/e2719c9172c2970588b6c85884619be6 to your computer and use it in GitHub Desktop.
API ideas and examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"reflect" | |
"runtime" | |
"strconv" | |
"strings" | |
"sync" | |
) | |
type BooksController struct { | |
books sync.Map | |
} | |
type BookT struct { | |
ID int `json:"id,omitempty"` | |
Title string `json:"title"` | |
} | |
func addBooks(bc *BooksController) { | |
book1 := BookT{1, "book 1"} | |
book2 := BookT{2, "book 2"} | |
book3 := BookT{3, "book 3"} | |
book4 := BookT{4, "book 4"} | |
book5 := BookT{5, "book 5"} | |
bc.books.Store(book1.ID, book1) | |
bc.books.Store(book2.ID, book2) | |
bc.books.Store(book3.ID, book3) | |
bc.books.Store(book4.ID, book4) | |
bc.books.Store(book5.ID, book5) | |
} | |
func (bc *BooksController) getAllBooks(c *Ctx) { | |
var books []BookT | |
bc.books.Range( | |
func(id, book any) bool { | |
if book == nil { | |
return false | |
} | |
books = append(books, book.(BookT)) | |
return true | |
}) | |
c.WriteJSON(books) | |
} | |
func (bc *BooksController) getBookByID(c *Ctx) { | |
v := c.r.URL.Query().Get("id") | |
bid, err := strconv.Atoi(v) | |
if err != nil { | |
Error(c, http.StatusExpectationFailed) | |
return | |
} | |
var foundBook BookT | |
bc.books.Range( | |
func(id, book any) bool { | |
if book.(BookT).ID == bid { | |
foundBook = book.(BookT) | |
return false | |
} | |
return true | |
}) | |
c.WriteJSON(foundBook) | |
} | |
func main() { | |
bc := new(BooksController) | |
addBooks(bc) | |
api := NewAPIServer() | |
api.GET("/api/books", bc.getAllBooks, bc.getBookByID) | |
log.Println(http.ListenAndServe(":8080", api)) | |
} | |
type APIServer struct { | |
ctxPool sync.Pool | |
routes sync.Map | |
} | |
func initAPIServer(api *APIServer) { | |
api.ctxPool = sync.Pool{ | |
New: func() any { | |
return new(Ctx) | |
}, | |
} | |
} | |
func NewAPIServer() *APIServer { | |
api := new(APIServer) | |
initAPIServer(api) | |
return api | |
} | |
type Route struct { | |
method string | |
path string | |
handler string | |
fn CtxHandlerFunc | |
} | |
func (rte *Route) isMatch(c *Ctx) bool { | |
return c.r.Method == rte.method && c.r.URL.Path == rte.path | |
} | |
var allowedMethods = strings.Join([]string{ | |
http.MethodGet, | |
http.MethodPost, | |
http.MethodPut, | |
http.MethodDelete, | |
http.MethodOptions, | |
}, "") | |
func makeRoute(method, path string, fn CtxHandlerFunc) (*Route, error) { | |
if !strings.ContainsAny(method, allowedMethods) { | |
return nil, fmt.Errorf("method %q is not allowed", method) | |
} | |
if path == "" { | |
return nil, fmt.Errorf("empty path is not allowed") | |
} | |
if fn == nil { | |
return nil, fmt.Errorf("a CtxHandlerFunc is required") | |
} | |
return &Route{ | |
method: method, | |
path: path, | |
handler: nameOfFunction(fn), | |
fn: fn, | |
}, nil | |
} | |
func nameOfFunction(f any) string { | |
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() | |
} | |
func (api *APIServer) register(method, path string, fn ...CtxHandlerFunc) { | |
for _, h := range fn { | |
r, err := makeRoute(method, path, h) | |
if err != nil { | |
panic(err) | |
} | |
api.routes.Store(r.handler, r) | |
} | |
} | |
func (api *APIServer) Handle(method, path string, fn CtxHandlerFunc) { | |
api.register(method, path, fn) | |
} | |
func (api *APIServer) GET(path string, fn ...CtxHandlerFunc) { | |
api.register(http.MethodGet, path, fn...) | |
} | |
func (api *APIServer) POST(path string, fn CtxHandlerFunc) { | |
api.register(http.MethodGet, path, fn) | |
} | |
func (api *APIServer) PUT(path string, fn CtxHandlerFunc) { | |
api.register(http.MethodGet, path, fn) | |
} | |
func (api *APIServer) DELETE(path string, fn CtxHandlerFunc) { | |
api.register(http.MethodGet, path, fn) | |
} | |
func (api *APIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
ctx := api.ctxPool.Get().(*Ctx) | |
ctx.init(w, r) | |
api.ServeCtxHTTP(ctx) | |
api.ctxPool.Put(ctx) | |
} | |
func (api *APIServer) ServeCtxHTTP(c *Ctx) { | |
api.routes.Range(func(k, v any) bool { | |
rte, ok := v.(*Route) | |
if !ok { | |
return false | |
} | |
if rte.isMatch(c) { | |
rte.fn(c) | |
return false | |
} | |
return true | |
}) | |
Error(c, http.StatusNotFound) | |
} | |
func Error(c *Ctx, code int) { | |
c.w.Header().Set("Content-Type", "text/plain; charset=utf-8") | |
c.w.Header().Set("X-Content-Type-Options", "nosniff") | |
c.w.WriteHeader(code) | |
c.w.Write([]byte(http.StatusText(code))) | |
} | |
func WrapHandlerFunc(hf http.HandlerFunc) CtxHandlerFunc { | |
return func(c *Ctx) { | |
hf(c.w, c.r) | |
} | |
} | |
func WrapHandler(h http.Handler) CtxHandlerFunc { | |
return func(c *Ctx) { | |
h.ServeHTTP(c.w, c.r) | |
} | |
} | |
type CtxHandler interface { | |
ServeCtxHTTP(c *Ctx) | |
} | |
type CtxHandlerFunc func(c *Ctx) | |
func (cfn CtxHandlerFunc) ServeCtxHTTP(c *Ctx) { | |
cfn(c) | |
} | |
type Response struct { | |
http.ResponseWriter | |
} | |
type Ctx struct { | |
r *http.Request | |
w *Response | |
mu sync.Mutex | |
args map[string]any | |
} | |
func (c *Ctx) init(w http.ResponseWriter, r *http.Request) { | |
c.r = r | |
c.w = &Response{w} | |
if c.args == nil { | |
c.args = make(map[string]any) | |
} | |
} | |
func (c *Ctx) WriteJSON(v any) { | |
c.w.Header().Set("Content-Type", "application/json") | |
c.w.WriteHeader(http.StatusOK) | |
err := json.NewEncoder(c.w).Encode(v) | |
if err != nil { | |
Error(c, http.StatusExpectationFailed) | |
return | |
} | |
} | |
func (c *Ctx) BindJSON(v any) { | |
if c.r.Header.Get("Content-Type") != "application/json" { | |
Error(c, http.StatusBadRequest) | |
return | |
} | |
err := json.NewDecoder(c.r.Body).Decode(v) | |
if err != nil { | |
Error(c, http.StatusExpectationFailed) | |
return | |
} | |
c.w.WriteHeader(http.StatusOK) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"log" | |
"net/http" | |
"strconv" | |
"strings" | |
"sync" | |
) | |
func main() { | |
bookController := &BookController{ | |
service: new(BookService), | |
} | |
bookController.service.init() | |
http.Handle("/api/books", bookController) | |
http.Handle("/api/books/count", bookController) | |
log.Panic(http.ListenAndServe(":8080", nil)) | |
} | |
type Book struct { | |
ID int `json:"id,omitempty"` | |
Title string `json:"title"` | |
} | |
type BookService struct { | |
repo sync.Map | |
nextID int | |
} | |
func (s *BookService) init() { | |
book1 := Book{1, "The Bible"} | |
book2 := Book{2, "Lord of the Rings"} | |
book3 := Book{3, "The Hobbit"} | |
book4 := Book{4, "A Scanner Darkly"} | |
book5 := Book{5, "1984"} | |
s.repo.Store(book1.ID, book1) | |
s.repo.Store(book2.ID, book2) | |
s.repo.Store(book3.ID, book3) | |
s.repo.Store(book4.ID, book4) | |
s.repo.Store(book5.ID, book5) | |
} | |
func (s *BookService) getBookCount() int { | |
var count int | |
s.repo.Range( | |
func(k, v any) bool { | |
if v != nil { | |
count++ | |
} | |
return true | |
}) | |
return count | |
} | |
func (s *BookService) addBook(book Book) bool { | |
if book.ID > 0 { | |
return false | |
} | |
s.nextID++ | |
book.ID = s.nextID | |
s.repo.Store(book.ID, book) | |
return true | |
} | |
func (s *BookService) updateBook(book Book) bool { | |
if book.ID < 1 { | |
return false | |
} | |
s.repo.Store(book.ID, book) | |
return true | |
} | |
func (s *BookService) getAllBooks() []Book { | |
// get all the books | |
var allBooks []Book | |
s.repo.Range(func(k, v any) bool { | |
book, isBook := v.(Book) | |
if isBook { | |
allBooks = append(allBooks, book) | |
} | |
return true | |
}) | |
// return list of books | |
return allBooks | |
} | |
func (s *BookService) getBook(id int) *Book { | |
// find the book using the id | |
v, found := s.repo.Load(id) | |
if found { | |
book, isBook := v.(Book) | |
// return the found book | |
if isBook && book.ID == id { | |
return &book | |
} | |
} | |
return nil | |
} | |
type RequestMapping struct { | |
Method string `json:"method"` | |
Path string `json:"path"` | |
Content string `json:"content"` | |
} | |
// BookController must somehow be able to be used with | |
// the standard library easily enough, so to make this | |
// http compatible, we will implement ServeHTTP(w, r) | |
type BookController struct { | |
service *BookService | |
} | |
func (c *BookController) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
handleErr := func(w http.ResponseWriter, r *http.Request, code int) { | |
http.Error(w, http.StatusText(code), code) | |
} | |
if !strings.HasPrefix(r.URL.Path, "/api/books") { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
// handle returning one or all books | |
if r.Method == http.MethodGet && r.URL.Path == "/api/books" { | |
if r.URL.Query().Has("id") { | |
// handle get book by id | |
id, err := strconv.Atoi(r.URL.Query().Get("id")) | |
if err != nil { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
book := c.service.getBook(id) | |
data, err := json.Marshal(book) | |
if err != nil { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
w.Header().Set("Content-Type", "application/json") | |
w.WriteHeader(http.StatusOK) | |
w.Write(data) | |
return | |
} | |
// handle get all books | |
books := c.service.getAllBooks() | |
data, err := json.Marshal(books) | |
if err != nil { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
w.Header().Set("Content-Type", "application/json") | |
w.WriteHeader(http.StatusOK) | |
w.Write(data) | |
return | |
} | |
// handle add new book | |
if r.Method == http.MethodPost && r.URL.Path == "/api/books" { | |
var book Book | |
err := json.NewDecoder(r.Body).Decode(&book) | |
if err != nil { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
if ok := c.service.addBook(book); !ok { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
w.Header().Set("Content-Type", "application/json") | |
w.WriteHeader(http.StatusOK) | |
return | |
} | |
// handle update book | |
if r.Method == http.MethodPut && r.URL.Path == "/api/books" { | |
var book Book | |
err := json.NewDecoder(r.Body).Decode(&book) | |
if err != nil { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
if ok := c.service.updateBook(book); !ok { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
w.Header().Set("Content-Type", "application/json") | |
w.WriteHeader(http.StatusOK) | |
return | |
} | |
// handle a special api endpoint case | |
if r.Method == http.MethodGet && r.URL.Path == "/api/books/count" { | |
count := c.service.getBookCount() | |
bookCount, err := json.Marshal(struct { | |
NumberOfBooks int `json:"number_of_books"` | |
}{ | |
NumberOfBooks: count, | |
}) | |
if err != nil { | |
handleErr(w, r, http.StatusExpectationFailed) | |
return | |
} | |
w.Header().Set("Content-Type", "application/json") | |
w.WriteHeader(http.StatusOK) | |
w.Write(bookCount) | |
return | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"strconv" | |
"sync" | |
) | |
func main() { | |
bookController := &BookController{ | |
service: new(BookService), | |
} | |
bookController.service.init() | |
http.Handle("/api/books", bookController) | |
http.Handle("/api/books/count", bookController) | |
log.Panic(http.ListenAndServe(":8080", nil)) | |
} | |
type Book struct { | |
ID int `json:"id,omitempty"` | |
Title string `json:"title"` | |
} | |
type BookService struct { | |
repo sync.Map | |
nextID int | |
} | |
func (s *BookService) init() { | |
book1 := Book{1, "The Bible"} | |
book2 := Book{2, "Lord of the Rings"} | |
book3 := Book{3, "The Hobbit"} | |
book4 := Book{4, "A Scanner Darkly"} | |
book5 := Book{5, "1984"} | |
s.repo.Store(book1.ID, book1) | |
s.repo.Store(book2.ID, book2) | |
s.repo.Store(book3.ID, book3) | |
s.repo.Store(book4.ID, book4) | |
s.repo.Store(book5.ID, book5) | |
} | |
func (s *BookService) getBookCount() int { | |
var count int | |
s.repo.Range( | |
func(k, v any) bool { | |
if v != nil { | |
count++ | |
} | |
return true | |
}) | |
return count | |
} | |
func (s *BookService) addBook(book Book) bool { | |
if book.ID > 0 { | |
return false | |
} | |
s.nextID++ | |
book.ID = s.nextID | |
s.repo.Store(book.ID, book) | |
return true | |
} | |
func (s *BookService) updateBook(book Book) bool { | |
if book.ID < 1 { | |
return false | |
} | |
s.repo.Store(book.ID, book) | |
return true | |
} | |
func (s *BookService) getAllBooks() []Book { | |
// get all the books | |
var allBooks []Book | |
s.repo.Range(func(k, v any) bool { | |
book, isBook := v.(Book) | |
if isBook { | |
allBooks = append(allBooks, book) | |
} | |
return true | |
}) | |
// return list of books | |
return allBooks | |
} | |
func (s *BookService) getBook(id int) *Book { | |
// find the book using the id | |
v, found := s.repo.Load(id) | |
if found { | |
book, isBook := v.(Book) | |
// return the found book | |
if isBook && book.ID == id { | |
return &book | |
} | |
} | |
return nil | |
} | |
type RequestMapping struct { | |
Method string `json:"method"` | |
Path string `json:"path"` | |
Content string `json:"content"` | |
} | |
func WriteAsJSON(w http.ResponseWriter, data map[string]any) { | |
w.Header().Set("Content-Type", "application/json") | |
b, err := json.Marshal(data) | |
if err != nil { | |
w.WriteHeader(http.StatusExpectationFailed) | |
w.Write([]byte(`{"error":"could not marshal supplied data"}`)) | |
return | |
} | |
w.WriteHeader(http.StatusOK) | |
w.Write(b) | |
} | |
func HandleErr(w http.ResponseWriter, code int, data map[string]any) { | |
w.Header().Set("Content-Type", "application/json") | |
if data != nil { | |
b, err := json.Marshal(data) | |
if err != nil { | |
w.WriteHeader(http.StatusExpectationFailed) | |
w.Write([]byte(`{"error"": "could not marshal supplied data"}`)) | |
return | |
} | |
w.WriteHeader(code) | |
w.Write(b) | |
return | |
} | |
w.WriteHeader(code) | |
w.Write([]byte(fmt.Sprintf(`{"error":%q,"code":%d}`, http.StatusText(code), code))) | |
} | |
// BookController must somehow be able to be used with | |
// the standard library easily enough, so to make this | |
// http compatible, we will implement ServeHTTP(w, r) | |
type BookController struct { | |
service *BookService | |
} | |
func (c *BookController) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
// handle returning one or all books | |
if r.Method == http.MethodGet && r.URL.Path == "/api/books" { | |
if r.URL.Query().Has("id") { | |
// handle get book by id | |
id, err := strconv.Atoi(r.URL.Query().Get("id")) | |
if err != nil { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
book := c.service.getBook(id) | |
WriteAsJSON(w, map[string]any{"count": 1, "book": book}) | |
return | |
} | |
// handle get all books | |
books := c.service.getAllBooks() | |
WriteAsJSON(w, map[string]any{"count": len(books), "books": books}) | |
return | |
} | |
// handle add new book | |
if r.Method == http.MethodPost && r.URL.Path == "/api/books" { | |
var book Book | |
err := json.NewDecoder(r.Body).Decode(&book) | |
if err != nil { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
if ok := c.service.addBook(book); !ok { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
WriteAsJSON(w, map[string]any{"added": true}) | |
return | |
} | |
// handle update book | |
if r.Method == http.MethodPut && r.URL.Path == "/api/books" { | |
var book Book | |
err := json.NewDecoder(r.Body).Decode(&book) | |
if err != nil { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
if ok := c.service.updateBook(book); !ok { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
WriteAsJSON(w, map[string]any{"book.id": book.ID, "updated": true}) | |
return | |
} | |
// handle a special api endpoint case | |
if r.Method == http.MethodGet && r.URL.Path == "/api/books/count" { | |
count := c.service.getBookCount() | |
WriteAsJSON(w, map[string]any{"number_of_books": count}) | |
return | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"strconv" | |
"strings" | |
"sync" | |
) | |
func main() { | |
bookController := &BookController{ | |
service: new(BookService), | |
} | |
bookController.service.init() | |
api := NewAPI() | |
api.RegisterController(bookController) | |
log.Panic(http.ListenAndServe(":8080", api)) | |
} | |
type ControllerResource interface { | |
Prefix() string | |
http.Handler | |
} | |
type API struct { | |
controllers []ControllerResource | |
} | |
func NewAPI() *API { | |
return &API{ | |
controllers: make([]ControllerResource, 0), | |
} | |
} | |
func (api *API) RegisterController(cr ControllerResource) { | |
api.controllers = append(api.controllers, cr) | |
} | |
func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
for _, c := range api.controllers { | |
if strings.HasPrefix(r.URL.Path, c.Prefix()) { | |
c.ServeHTTP(w, r) | |
return | |
} | |
} | |
http.NotFound(w, r) | |
return | |
} | |
type Book struct { | |
ID int `json:"id,omitempty"` | |
Title string `json:"title"` | |
} | |
type BookService struct { | |
repo sync.Map | |
nextID int | |
} | |
func (s *BookService) init() { | |
book1 := Book{1, "The Bible"} | |
book2 := Book{2, "Lord of the Rings"} | |
book3 := Book{3, "The Hobbit"} | |
book4 := Book{4, "A Scanner Darkly"} | |
book5 := Book{5, "1984"} | |
s.repo.Store(book1.ID, book1) | |
s.repo.Store(book2.ID, book2) | |
s.repo.Store(book3.ID, book3) | |
s.repo.Store(book4.ID, book4) | |
s.repo.Store(book5.ID, book5) | |
} | |
func (s *BookService) getBookCount() int { | |
var count int | |
s.repo.Range( | |
func(k, v any) bool { | |
if v != nil { | |
count++ | |
} | |
return true | |
}) | |
return count | |
} | |
func (s *BookService) addBook(book Book) bool { | |
if book.ID > 0 { | |
return false | |
} | |
s.nextID++ | |
book.ID = s.nextID | |
s.repo.Store(book.ID, book) | |
return true | |
} | |
func (s *BookService) updateBook(book Book) bool { | |
if book.ID < 1 { | |
return false | |
} | |
s.repo.Store(book.ID, book) | |
return true | |
} | |
func (s *BookService) getAllBooks() []Book { | |
// get all the books | |
var allBooks []Book | |
s.repo.Range(func(k, v any) bool { | |
book, isBook := v.(Book) | |
if isBook { | |
allBooks = append(allBooks, book) | |
} | |
return true | |
}) | |
// return list of books | |
return allBooks | |
} | |
func (s *BookService) getBook(id int) *Book { | |
// find the book using the id | |
v, found := s.repo.Load(id) | |
if found { | |
book, isBook := v.(Book) | |
// return the found book | |
if isBook && book.ID == id { | |
return &book | |
} | |
} | |
return nil | |
} | |
type RequestMapping struct { | |
Method string `json:"method"` | |
Path string `json:"path"` | |
Content string `json:"content"` | |
} | |
func WriteAsJSON(w http.ResponseWriter, data map[string]any) { | |
w.Header().Set("Content-Type", "application/json") | |
b, err := json.Marshal(data) | |
if err != nil { | |
w.WriteHeader(http.StatusExpectationFailed) | |
w.Write([]byte(`{"error":"could not marshal supplied data"}`)) | |
return | |
} | |
w.WriteHeader(http.StatusOK) | |
w.Write(b) | |
} | |
func HandleErr(w http.ResponseWriter, code int, data map[string]any) { | |
w.Header().Set("Content-Type", "application/json") | |
if data != nil { | |
b, err := json.Marshal(data) | |
if err != nil { | |
w.WriteHeader(http.StatusExpectationFailed) | |
w.Write([]byte(`{"error"": "could not marshal supplied data"}`)) | |
return | |
} | |
w.WriteHeader(code) | |
w.Write(b) | |
return | |
} | |
w.WriteHeader(code) | |
w.Write([]byte(fmt.Sprintf(`{"error":%q,"code":%d}`, http.StatusText(code), code))) | |
} | |
// BookController must somehow be able to be used with | |
// the standard library easily enough, so to make this | |
// http compatible, we will implement ServeHTTP(w, r) | |
type BookController struct { | |
service *BookService | |
} | |
func (c *BookController) Prefix() string { | |
return "/api/books" | |
} | |
func (c *BookController) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
// handle returning one or all books | |
if r.Method == http.MethodGet && r.URL.Path == "/api/books" { | |
if r.URL.Query().Has("id") { | |
// handle get book by id | |
id, err := strconv.Atoi(r.URL.Query().Get("id")) | |
if err != nil { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
book := c.service.getBook(id) | |
WriteAsJSON(w, map[string]any{"count": 1, "book": book}) | |
return | |
} | |
// handle get all books | |
books := c.service.getAllBooks() | |
WriteAsJSON(w, map[string]any{"count": len(books), "books": books}) | |
return | |
} | |
// handle add new book | |
if r.Method == http.MethodPost && r.URL.Path == "/api/books" { | |
var book Book | |
err := json.NewDecoder(r.Body).Decode(&book) | |
if err != nil { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
if ok := c.service.addBook(book); !ok { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
WriteAsJSON(w, map[string]any{"added": true}) | |
return | |
} | |
// handle update book | |
if r.Method == http.MethodPut && r.URL.Path == "/api/books" { | |
var book Book | |
err := json.NewDecoder(r.Body).Decode(&book) | |
if err != nil { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
if ok := c.service.updateBook(book); !ok { | |
HandleErr(w, http.StatusExpectationFailed, nil) | |
return | |
} | |
WriteAsJSON(w, map[string]any{"book.id": book.ID, "updated": true}) | |
return | |
} | |
// handle a special api endpoint case | |
if r.Method == http.MethodGet && r.URL.Path == "/api/books/count" { | |
count := c.service.getBookCount() | |
WriteAsJSON(w, map[string]any{"number_of_books": count}) | |
return | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment