--- /dev/null
+package errors
+
+import "fmt"
+
+type ErrorCode int
+
+const (
+ DefaultError ErrorCode = iota
+ BadInputError
+ ConflictError
+ NotFoundError
+ BadIdError
+)
+
+func (c ErrorCode) MapToHTTP() int {
+ switch c {
+ case BadIdError:
+ fallthrough
+ case BadInputError:
+ return 400
+
+ case ConflictError:
+ return 409
+
+ case NotFoundError:
+ return 404
+
+ default:
+ return 500
+ }
+}
+
+type Error struct {
+ Code ErrorCode
+ Message string
+}
+
+func (e Error) Error() string {
+ return fmt.Sprintf("(%d)%s", e.Code, e.Message)
+}
+
+func MapError(err error) Error {
+ switch err.Error() {
+ case "bad_id":
+ return Error{Code: BadIdError, Message: err.Error()}
+ case "name_in_use":
+ return Error{Code: ConflictError, Message: err.Error()}
+ case "not_found":
+ return Error{Code: NotFoundError, Message: err.Error()}
+ default:
+ return Error{Code: DefaultError, Message: "Something bad happened"}
+ }
+}
return core.Ingredient{}, errors.New("name_in_use")
}
-
id := len(p.ingredients) + 1
createdAt := time.Now()
in.Id = id
in.CreatedAt = &createdAt
- p.ingredients = append(p.ingredients, in)
+ p.ingredients = append(p.ingredients, in)
return in, nil
}
func (p *InmemoryPersistence) IngredientUpdate(in core.Ingredient) error {
+ ingredient, err := p.IngredientGet(in.Id)
+ if err != nil {
+ return err
+ }
+ if ingredient == nil {
+ return errors.New("not_found")
+ }
+
+ // IRW this would be an index, so np leaving it here
+ found, err := p.IngredientGetByName(in.Name)
+ if err != nil {
+ return err
+ }
+ if found != nil && found.Id != in.Id {
+ return errors.New("name_in_use")
+ }
+
+ ingredient.Name = in.Name
+ ingredient.Quantity = in.Quantity
+ ingredient.Description = in.Description
+
return nil
}
}
func (p *InmemoryPersistence) IngredientGet(id int) (*core.Ingredient, error) {
- if id < 0 || id > len(p.ingredients) {
+ if id < 1 || id > len(p.ingredients) {
return nil, nil
}
return &p.ingredients[id-1], nil
ingredients: []core.Ingredient{},
}
}
-
type Persistence interface {
IngredientRepository
}
-
package core
-import "time"
+import (
+ "time"
+
+ "jsdaj.tq/pf/internal/errors"
+)
type Ingredient struct {
- Id int `json:"id,omitempty"`
- Name string `json:"name"`
- Quantity int `json:"quantity"`
- Description string `json:"description"`
+ Id int `json:"id,omitempty"`
+ Name string `json:"name"`
+ Quantity int `json:"quantity"`
+ Description string `json:"description"`
CreatedAt *time.Time `json:"createdAt,omitempty"`
}
+func (ingredient Ingredient) IsValid() error {
+ isMissingRequiredFields := ingredient.Name == "" || ingredient.Quantity == 0
+ isBadName := len(ingredient.Name) < 3
+ isBadQuantity := ingredient.Quantity < 0
+
+ if isMissingRequiredFields {
+ return errors.Error{Code: errors.BadInputError, Message: "fields required: name, quantity"}
+ }
+ if isBadName {
+ return errors.Error{Code: errors.BadInputError, Message: "name should be have more than 2 chars"}
+ }
+ if isBadQuantity {
+ return errors.Error{Code: errors.BadInputError, Message: "quantity should be positive"}
+ }
+
+ return nil
+}
+++ /dev/null
-package http
-
-import (
- "encoding/json"
- "log"
- shttp "net/http"
-
- "jsdaj.tq/pf/pkg/services"
-)
-
-type httpError struct {
- Status int `json:"status"`
- Message string `json:"message"`
-}
-
-func handleError(w shttp.ResponseWriter, status int, message string) {
- response := httpError{
- Status: status,
- Message: message,
- }
-
- body, err := json.Marshal(response)
- if err != nil {
- body = []byte("Something went wrong")
- }
-
- w.WriteHeader(status)
- w.Write(body)
-}
-
-func handleErrorByCode(w shttp.ResponseWriter, status int) {
- switch status {
- case shttp.StatusBadRequest:
- handleError(w, status, "Bad body")
- default:
- handleError(w, shttp.StatusInternalServerError, "Something went wrong")
- }
-}
-
-func handleServiceError(w shttp.ResponseWriter, err error) {
- switch err.(type) {
- case services.Error:
- e := err.(services.Error)
- status := e.Code.MapToHTTP()
- handleError(w, status, e.Message)
- default:
- log.Printf("error: Expecting 'services.Error', received: %s\n", err)
- handleErrorByCode(w, shttp.StatusInternalServerError)
- }
-}
shttp "net/http"
"jsdaj.tq/pf/persistence"
+ "jsdaj.tq/pf/pkg/http/util"
"jsdaj.tq/pf/pkg/services"
)
func (s *Server) Start() {
s.initRoutes()
- s.serv.Handler = middlewares(s.mux)
+ s.serv.Handler = util.Middlewares(s.mux)
log.Printf(">> Starting server at :%d\n", s.port)
s.serv.ListenAndServe()
func (s *Server) initRoutes() {
s.mux = shttp.NewServeMux()
- s.mux.Handle("/ingredient", s.ingredientHandler())
+ s.mux.HandleFunc("POST /ingredient", s.ingredientHandleCreate)
+ s.mux.HandleFunc("PUT /ingredient/{id}", s.ingredientHandleUpdate)
+ s.mux.HandleFunc("DELETE /ingredient/{id}", s.ingredientHandleDelete)
+ s.mux.HandleFunc("GET /ingredient/{id}", s.ingredientHandleGet)
+ s.mux.HandleFunc("GET /ingredient", s.ingredientHandleList)
+
s.mux.Handle("/dish", dishHandler())
s.mux.Handle("/menu", menuHandler())
}
-
package http
import (
- "encoding/json"
- "log"
+ serrors "errors"
shttp "net/http"
+ "strconv"
+ "jsdaj.tq/pf/internal/errors"
"jsdaj.tq/pf/pkg/core"
+ "jsdaj.tq/pf/pkg/http/util"
)
func (s *Server) ingredientHandleCreate(w shttp.ResponseWriter, r *shttp.Request) {
var input core.Ingredient
- err := json.NewDecoder(r.Body).Decode(&input)
+ err := util.ParseRequestAndErr(w, r, &input)
if err != nil {
- log.Printf("!! %s\n", err)
- handleErrorByCode(w, shttp.StatusBadRequest)
return
}
ingredient, err := s.ingredients.Create(input)
if err != nil {
- handleServiceError(w, err)
+ util.HandleServiceError(w, err)
return
}
- response, err := json.Marshal(ingredient)
+ util.ParseAndWriteResponse(w, ingredient, shttp.StatusCreated)
+}
+
+func (s *Server) ingredientHandleUpdate(w shttp.ResponseWriter, r *shttp.Request) {
+ id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
- handleErrorByCode(w, shttp.StatusInternalServerError)
+ util.HandleServiceError(w, errors.MapError(serrors.New("bad_id")))
return
}
-
- w.WriteHeader(shttp.StatusCreated)
- w.Write(response)
-}
-
-func (s *Server) ingredientHandleUpdate(w shttp.ResponseWriter, r *shttp.Request) {}
-
-func (s *Server) ingredientHandleDelete(w shttp.ResponseWriter, r *shttp.Request) {}
-func (s *Server) ingredientHandleGet(w shttp.ResponseWriter, r *shttp.Request) {}
+ var ingredient core.Ingredient
-func (s *Server) ingredientHandleList(w shttp.ResponseWriter, r *shttp.Request) {
- ingredients, err := s.ingredients.List()
+ err = util.ParseRequestAndErr(w, r, &ingredient)
if err != nil {
- handleError(w, shttp.StatusInternalServerError, err.Error())
return
}
- response, err := json.Marshal(ingredients)
+ ingredient.Id = id
+
+ err = s.ingredients.Update(ingredient)
if err != nil {
- handleErrorByCode(w, shttp.StatusInternalServerError)
+ util.HandleServiceError(w, err)
return
}
-
- w.Write(response)
+
+ util.ParseAndWriteResponse(w, ingredient, shttp.StatusOK)
}
-func (s *Server) ingredientHandler() shttp.Handler {
- mux := shttp.NewServeMux()
+func (s *Server) ingredientHandleDelete(w shttp.ResponseWriter, r *shttp.Request) {}
- mux.HandleFunc("POST /ingredient", s.ingredientHandleCreate)
- mux.HandleFunc("PUT /ingredient/{id}", s.ingredientHandleUpdate)
- mux.HandleFunc("DELETE /ingredient/{id}", s.ingredientHandleDelete)
- mux.HandleFunc("GET /ingredient/{id}", s.ingredientHandleGet)
- mux.HandleFunc("GET /ingredient", s.ingredientHandleList)
+func (s *Server) ingredientHandleGet(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func (s *Server) ingredientHandleList(w shttp.ResponseWriter, r *shttp.Request) {
+ ingredients, err := s.ingredients.List()
+ if err != nil {
+ util.HandleError(w, shttp.StatusInternalServerError, err.Error())
+ return
+ }
- return mux
+ util.ParseAndWriteResponse(w, ingredients, shttp.StatusOK)
}
--- /dev/null
+package util
+
+import (
+ "encoding/json"
+ "log"
+ shttp "net/http"
+
+ "jsdaj.tq/pf/internal/errors"
+)
+
+type httpError struct {
+ Status int `json:"status"`
+ Message string `json:"message"`
+}
+
+func HandleError(w shttp.ResponseWriter, status int, message string) {
+ response := httpError{
+ Status: status,
+ Message: message,
+ }
+
+ body, err := json.Marshal(response)
+ if err != nil {
+ body = []byte("Something went wrong")
+ }
+
+ w.WriteHeader(status)
+ w.Write(body)
+}
+
+func HandleErrorByCode(w shttp.ResponseWriter, status int) {
+ switch status {
+ case shttp.StatusBadRequest:
+ HandleError(w, status, "Bad body")
+ default:
+ HandleError(w, shttp.StatusInternalServerError, "Something went wrong")
+ }
+}
+
+func HandleServiceError(w shttp.ResponseWriter, err error) {
+ switch err := err.(type) {
+ case errors.Error:
+ status := err.Code.MapToHTTP()
+ HandleError(w, status, err.Message)
+ default:
+ log.Printf("error: Expecting 'services.Error', received: %s\n", err)
+ HandleErrorByCode(w, shttp.StatusInternalServerError)
+ }
+}
-package http
+package util
import (
"log"
})
}
-func middlewares(next shttp.Handler) shttp.Handler {
+func Middlewares(next shttp.Handler) shttp.Handler {
return middlewareLog(middlewareJson(next))
}
--- /dev/null
+package util
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+func ParseRequestAndErr[T any](w http.ResponseWriter, r *http.Request, input T) error {
+ err := json.NewDecoder(r.Body).Decode(input)
+ if err != nil {
+ log.Printf("!! %s\n", err)
+ HandleErrorByCode(w, http.StatusBadRequest)
+ return err
+ }
+ return nil
+}
+
+func ParseAndWriteResponse[T any](w http.ResponseWriter, input T, status int) {
+ response, err := json.Marshal(input)
+ if err != nil {
+ HandleErrorByCode(w, http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(status)
+ w.Write(response)
+}
+++ /dev/null
-package services
-
-import "fmt"
-
-type ErrorCode int
-
-const (
- DefaultError ErrorCode = iota
- BadInputError
- ConflictError
-)
-
-func (c ErrorCode) MapToHTTP() int {
- switch c {
- case BadInputError:
- return 400
- case ConflictError:
- return 409
- default:
- return 500
- }
-}
-
-type Error struct {
- Code ErrorCode
- Message string
-}
-
-func (e Error) Error() string {
- return fmt.Sprintf("(%d)%s", e.Code, e.Message)
-}
package services
import (
+ "jsdaj.tq/pf/internal/errors"
"jsdaj.tq/pf/persistence"
"jsdaj.tq/pf/pkg/core"
)
}
func (s *Ingredient) Create(ingredient core.Ingredient) (*core.Ingredient, error) {
- isMissingRequiredFields := ingredient.Name == "" || ingredient.Quantity == 0
- isBadName := len(ingredient.Name) < 3
- isBadQuantity := ingredient.Quantity < 0
-
- if isMissingRequiredFields {
- return nil, Error{Code: BadInputError, Message: "fields required: name, quantity"}
- }
- if isBadName {
- return nil, Error{Code: BadInputError, Message: "name should be have more than 2 chars"}
- }
- if isBadQuantity {
- return nil, Error{Code: BadInputError, Message: "quantity should be positive"}
+ if err := ingredient.IsValid(); err != nil {
+ return nil, errors.MapError(err)
}
ingredient, err := s.repository.IngredientCreate(ingredient)
if err != nil {
- if err.Error() == "name_in_use" {
- return nil, Error{Code: ConflictError, Message: err.Error()}
- }
- return nil, err
+ return nil, errors.MapError(err)
}
+
return &ingredient, nil
}
+func (s *Ingredient) Update(ingredient core.Ingredient) error {
+ if err := ingredient.IsValid(); err != nil {
+ return errors.MapError(err)
+ }
+
+ err := s.repository.IngredientUpdate(ingredient)
+ if err != nil {
+ return errors.MapError(err)
+ }
+
+ return nil
+}
+
func (s *Ingredient) List() ([]core.Ingredient, error) {
ingredients, err := s.repository.IngredientList()
if err != nil {
repository,
}
}
-