learning_go/testing/go-code-samples/go-rest-demo/cmd/standardlib/main.go
2025-12-20 17:00:05 +03:00

195 lines
4.5 KiB
Go

package main
import (
"encoding/json"
"net/http"
"regexp"
"github.com/gosimple/slug"
"github.com/xNok/go-rest-demo/pkg/recipes"
)
var (
RecipeRe = regexp.MustCompile(`^/recipes/*$`)
RecipeReWithID = regexp.MustCompile(`^/recipes/([a-z0-9]+(?:-[a-z0-9]+)+)$`)
)
func main() {
// create the Store and Recipe Handler
store := recipes.NewMemStore()
recipesHandler := NewRecipesHandler(store)
// Create a new request multiplexer
// Takes incoming requests and dispatch them to the matching handlers
mux := http.NewServeMux()
// Register the routes and handlers
mux.Handle("/", &homeHandler{})
mux.Handle("/recipes", recipesHandler)
mux.Handle("/recipes/", recipesHandler)
// Run the server
http.ListenAndServe(":8080", mux)
}
type homeHandler struct{}
func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("This is my home page"))
}
func InternalServerErrorHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 Internal Server Error"))
}
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("404 Not Found"))
}
// recipeStore represent a data storage containing recipes
type recipeStore interface {
Add(name string, recipe recipes.Recipe) error
Get(name string) (recipes.Recipe, error)
List() (map[string]recipes.Recipe, error)
Update(name string, recipe recipes.Recipe) error
Remove(name string) error
}
// RecipesHandler implements http.Handler and dispatch request to the store
type RecipesHandler struct {
store recipeStore
}
func NewRecipesHandler(s recipeStore) *RecipesHandler {
return &RecipesHandler{
store: s,
}
}
func (h *RecipesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodPost && RecipeRe.MatchString(r.URL.Path):
h.CreateRecipe(w, r)
return
case r.Method == http.MethodGet && RecipeRe.MatchString(r.URL.Path):
h.ListRecipes(w, r)
return
case r.Method == http.MethodGet && RecipeReWithID.MatchString(r.URL.Path):
h.GetRecipe(w, r)
return
case r.Method == http.MethodPut && RecipeReWithID.MatchString(r.URL.Path):
h.UpdateRecipe(w, r)
return
case r.Method == http.MethodDelete && RecipeReWithID.MatchString(r.URL.Path):
h.DeleteRecipe(w, r)
return
default:
NotFoundHandler(w, r)
return
}
}
func (h *RecipesHandler) CreateRecipe(w http.ResponseWriter, r *http.Request) {
// Recipe object that will be populated from json payload
var recipe recipes.Recipe
if err := json.NewDecoder(r.Body).Decode(&recipe); err != nil {
InternalServerErrorHandler(w, r)
return
}
resourceID := slug.Make(recipe.Name)
if err := h.store.Add(resourceID, recipe); err != nil {
InternalServerErrorHandler(w, r)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *RecipesHandler) ListRecipes(w http.ResponseWriter, r *http.Request) {
recipesList, err := h.store.List()
jsonBytes, err := json.Marshal(recipesList)
if err != nil {
InternalServerErrorHandler(w, r)
return
}
w.WriteHeader(http.StatusOK)
w.Write(jsonBytes)
}
func (h *RecipesHandler) GetRecipe(w http.ResponseWriter, r *http.Request) {
matches := RecipeReWithID.FindStringSubmatch(r.URL.Path)
if len(matches) < 2 {
InternalServerErrorHandler(w, r)
return
}
recipe, err := h.store.Get(matches[1])
if err != nil {
if err == recipes.NotFoundErr {
NotFoundHandler(w, r)
return
}
InternalServerErrorHandler(w, r)
return
}
jsonBytes, err := json.Marshal(recipe)
if err != nil {
InternalServerErrorHandler(w, r)
return
}
w.WriteHeader(http.StatusOK)
w.Write(jsonBytes)
}
func (h *RecipesHandler) UpdateRecipe(w http.ResponseWriter, r *http.Request) {
matches := RecipeReWithID.FindStringSubmatch(r.URL.Path)
if len(matches) < 2 {
InternalServerErrorHandler(w, r)
return
}
// Recipe object that will be populated from JSON payload
var recipe recipes.Recipe
if err := json.NewDecoder(r.Body).Decode(&recipe); err != nil {
InternalServerErrorHandler(w, r)
return
}
if err := h.store.Update(matches[1], recipe); err != nil {
if err == recipes.NotFoundErr {
NotFoundHandler(w, r)
return
}
InternalServerErrorHandler(w, r)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *RecipesHandler) DeleteRecipe(w http.ResponseWriter, r *http.Request) {
matches := RecipeReWithID.FindStringSubmatch(r.URL.Path)
if len(matches) < 2 {
InternalServerErrorHandler(w, r)
return
}
if err := h.store.Remove(matches[1]); err != nil {
InternalServerErrorHandler(w, r)
return
}
w.WriteHeader(http.StatusOK)
}