new exporter version
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/xNok/go-rest-demo/pkg/recipes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Gin router
|
||||
router := gin.Default()
|
||||
|
||||
// Instantiate recipe Handler and provide a data store
|
||||
store := recipes.NewMemStore()
|
||||
recipesHandler := NewRecipesHandler(store)
|
||||
|
||||
// Register Routes
|
||||
router.GET("/", homePage)
|
||||
router.GET("/recipes", recipesHandler.ListRecipes)
|
||||
router.POST("/recipes", recipesHandler.CreateRecipe)
|
||||
router.GET("/recipes/:id", recipesHandler.GetRecipe)
|
||||
router.PUT("/recipes/:id", recipesHandler.UpdateRecipe)
|
||||
router.DELETE("/recipes/:id", recipesHandler.DeleteRecipe)
|
||||
|
||||
// Start the server
|
||||
router.Run()
|
||||
}
|
||||
|
||||
func homePage(c *gin.Context) {
|
||||
c.String(http.StatusOK, "This is my home page")
|
||||
}
|
||||
|
||||
type RecipesHandler struct {
|
||||
store recipeStore
|
||||
}
|
||||
|
||||
func NewRecipesHandler(s recipeStore) *RecipesHandler {
|
||||
return &RecipesHandler{
|
||||
store: s,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (h RecipesHandler) CreateRecipe(c *gin.Context) {
|
||||
// Get request body and convert it to recipes.Recipe
|
||||
var recipe recipes.Recipe
|
||||
if err := c.ShouldBindJSON(&recipe); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// create a url friendly name
|
||||
id := slug.Make(recipe.Name)
|
||||
|
||||
// add to the store
|
||||
h.store.Add(id, recipe)
|
||||
|
||||
// return success payload
|
||||
c.JSON(http.StatusOK, gin.H{"status": "success"})
|
||||
}
|
||||
|
||||
func (h RecipesHandler) ListRecipes(c *gin.Context) {
|
||||
r, err := h.store.List()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
|
||||
c.JSON(200, r)
|
||||
}
|
||||
|
||||
func (h RecipesHandler) GetRecipe(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
recipe, err := h.store.Get(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
}
|
||||
|
||||
c.JSON(200, recipe)
|
||||
}
|
||||
|
||||
func (h RecipesHandler) UpdateRecipe(c *gin.Context) {
|
||||
// Get request body and convert it to recipes.Recipe
|
||||
var recipe recipes.Recipe
|
||||
if err := c.ShouldBindJSON(&recipe); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
err := h.store.Update(id, recipe)
|
||||
if err != nil {
|
||||
if err == recipes.NotFoundErr {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// return success payload
|
||||
c.JSON(http.StatusOK, gin.H{"status": "success"})
|
||||
}
|
||||
|
||||
func (h RecipesHandler) DeleteRecipe(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
err := h.store.Remove(id)
|
||||
if err != nil {
|
||||
if err == recipes.NotFoundErr {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// return success payload
|
||||
c.JSON(http.StatusOK, gin.H{"status": "success"})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
###
|
||||
GET http://localhost:8080/
|
||||
|
||||
###
|
||||
POST http://localhost:8080/recipes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Ham and cheese toasties",
|
||||
"ingredients": [
|
||||
{
|
||||
"name": "bread"
|
||||
},{
|
||||
"name": "ham"
|
||||
},{
|
||||
"name": "cheese"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
client.assert(JSON.stringify(response.body) === "{\"ham-and-cheese-toasties\":{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"}]}}", "Body match expected response")
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
client.assert(JSON.stringify(response.body) === "{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"}]}", "Body match expected response")
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
PUT http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Ham and cheese toasties",
|
||||
"ingredients": [
|
||||
{
|
||||
"name": "bread"
|
||||
},{
|
||||
"name": "ham"
|
||||
},{
|
||||
"name": "cheese"
|
||||
},{
|
||||
"name": "butter"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
client.assert(JSON.stringify(response.body) === "{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"},{\"name\":\"butter\"}]}", "Body match expected response")
|
||||
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
DELETE http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 404, "Response status is not 404");
|
||||
});
|
||||
%}
|
||||
@@ -0,0 +1,159 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/xNok/go-rest-demo/pkg/recipes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// create the Store and Recipe Handler
|
||||
store := recipes.NewMemStore()
|
||||
recipesHandler := NewRecipesHandler(store)
|
||||
home := homeHandler{}
|
||||
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/", home.ServeHTTP)
|
||||
router.HandleFunc("/recipes", recipesHandler.ListRecipes).Methods("GET")
|
||||
router.HandleFunc("/recipes", recipesHandler.CreateRecipe).Methods("POST")
|
||||
router.HandleFunc("/recipes/{id}", recipesHandler.GetRecipe).Methods("GET")
|
||||
router.HandleFunc("/recipes/{id}", recipesHandler.UpdateRecipe).Methods("PUT")
|
||||
router.HandleFunc("/recipes/{id}", recipesHandler.DeleteRecipe).Methods("DELETE")
|
||||
|
||||
http.ListenAndServe(":8010", router)
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
type RecipesHandler struct {
|
||||
store recipeStore
|
||||
}
|
||||
|
||||
func NewRecipesHandler(s recipeStore) *RecipesHandler {
|
||||
return &RecipesHandler{
|
||||
store: s,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
recipes, err := h.store.List()
|
||||
|
||||
jsonBytes, err := json.Marshal(recipes)
|
||||
if err != nil {
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
func (h *RecipesHandler) GetRecipe(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
|
||||
recipe, err := h.store.Get(id)
|
||||
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) {
|
||||
id := mux.Vars(r)["id"]
|
||||
|
||||
// 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(id, recipe); 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) DeleteRecipe(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
|
||||
if err := h.store.Remove(id); err != nil {
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
type homeHandler struct{}
|
||||
|
||||
func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("This is my home page"))
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
###
|
||||
POST http://localhost:8010/recipes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Ham and cheese toasties",
|
||||
"ingredients": [
|
||||
{
|
||||
"name": "bread"
|
||||
},{
|
||||
"name": "ham"
|
||||
},{
|
||||
"name": "cheese"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8010/recipes
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
client.assert(response.body === "{\"ham-and-cheese-toasties\":{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"}]}}", "Body match expected response")
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8010/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
client.assert(response.body === "{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"}]}", "Body match expected response")
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
PUT http://localhost:8010/recipes/ham-and-cheese-toasties
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Ham and cheese toasties",
|
||||
"ingredients": [
|
||||
{
|
||||
"name": "bread"
|
||||
},{
|
||||
"name": "ham"
|
||||
},{
|
||||
"name": "cheese"
|
||||
},{
|
||||
"name": "butter"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8010/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
client.assert(response.body === "{\"name\":\"Ham and cheese toasties\",\"ingredients\":[{\"name\":\"bread\"},{\"name\":\"ham\"},{\"name\":\"cheese\"},{\"name\":\"butter\"}]}", "Body match expected response")
|
||||
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
DELETE http://localhost:8010/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 200, "Response status is not 200");
|
||||
});
|
||||
%}
|
||||
|
||||
###
|
||||
GET http://localhost:8010/recipes/ham-and-cheese-toasties
|
||||
|
||||
> {%
|
||||
client.test("Request executed successfully", function() {
|
||||
client.assert(response.status === 404, "Response status is not 404");
|
||||
});
|
||||
%}
|
||||
@@ -0,0 +1,194 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xNok/go-rest-demo/pkg/recipes"
|
||||
)
|
||||
|
||||
func readTestData(t *testing.T, name string) []byte {
|
||||
t.Helper()
|
||||
content, err := os.ReadFile("../../testdata/" + name)
|
||||
if err != nil {
|
||||
t.Errorf("Could not read %v", name)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
func TestRecipesHandlerCRUD_Integration(t *testing.T) {
|
||||
|
||||
// Create a MemStore and Recipe Handler
|
||||
store := recipes.NewMemStore()
|
||||
recipesHandler := NewRecipesHandler(store)
|
||||
|
||||
// Testdata
|
||||
hamAndCheese := readTestData(t, "ham_and_cheese_recipe.json")
|
||||
hamAndCheeseReader := bytes.NewReader(hamAndCheese)
|
||||
|
||||
hamAndCheeseWithButter := readTestData(t, "ham_and_cheese_with_butter_recipe.json")
|
||||
hamAndCheeseWithButterReader := bytes.NewReader(hamAndCheeseWithButter)
|
||||
|
||||
// CREATE - add a new recipe
|
||||
req := httptest.NewRequest(http.MethodPost, "/recipes", hamAndCheeseReader)
|
||||
w := httptest.NewRecorder()
|
||||
recipesHandler.ServeHTTP(w, req)
|
||||
|
||||
res := w.Result()
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
|
||||
saved, _ := store.List()
|
||||
assert.Len(t, saved, 1)
|
||||
|
||||
// GET - find the record we just added
|
||||
req = httptest.NewRequest(http.MethodGet, "/recipes/ham-and-cheese-toasties", nil)
|
||||
w = httptest.NewRecorder()
|
||||
recipesHandler.ServeHTTP(w, req)
|
||||
|
||||
res = w.Result()
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assert.JSONEq(t, string(hamAndCheese), string(data))
|
||||
|
||||
// UPDATE - add butter to ham and cheese recipe
|
||||
req = httptest.NewRequest(http.MethodPut, "/recipes/ham-and-cheese-toasties", hamAndCheeseWithButterReader)
|
||||
w = httptest.NewRecorder()
|
||||
recipesHandler.ServeHTTP(w, req)
|
||||
|
||||
res = w.Result()
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
|
||||
updatedHamAndCheese, err := store.Get("ham-and-cheese-toasties")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, updatedHamAndCheese.Ingredients, recipes.Ingredient{Name: "butter"})
|
||||
|
||||
//DELETE - remove the ham and cheese recipe
|
||||
req = httptest.NewRequest(http.MethodDelete, "/recipes/ham-and-cheese-toasties", nil)
|
||||
w = httptest.NewRecorder()
|
||||
recipesHandler.ServeHTTP(w, req)
|
||||
|
||||
res = w.Result()
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
|
||||
saved, _ = store.List()
|
||||
assert.Len(t, saved, 0)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
###
|
||||
GET http://localhost:8080
|
||||
|
||||
###
|
||||
POST http://localhost:8080/recipes/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Ham and cheese toasties",
|
||||
"ingredients": [
|
||||
{
|
||||
"name": "bread"
|
||||
},{
|
||||
"name": "ham"
|
||||
},{
|
||||
"name": "cheese"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
###
|
||||
PUT http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Ham and cheese toasties",
|
||||
"ingredients": [
|
||||
{
|
||||
"name": "bread"
|
||||
},{
|
||||
"name": "ham"
|
||||
},{
|
||||
"name": "cheese"
|
||||
},{
|
||||
"name": "butter"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
###
|
||||
DELETE http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
|
||||
###
|
||||
GET http://localhost:8080/recipes/ham-and-cheese-toasties
|
||||
Reference in New Issue
Block a user