mirror of
https://github.com/rjNemo/go-microservices-tuto
synced 2026-06-06 02:16:46 +00:00
🎓 update documentation
This commit is contained in:
parent
e9f20850ef
commit
b8ef90f3d2
11 changed files with 299 additions and 101 deletions
|
|
@ -28,6 +28,18 @@ func AddProduct(p *models.Product) {
|
|||
productList = append(productList, p)
|
||||
}
|
||||
|
||||
// GetProductByID returns a single product which matches the id from the
|
||||
// database.
|
||||
// If a product is not found this function returns a ProductNotFound error
|
||||
func GetProductByID(id int) (*models.Product, error) {
|
||||
i, _, err := findProduct(id)
|
||||
if err != nil {
|
||||
return nil, ErrorProductNotFound
|
||||
}
|
||||
|
||||
return productList[i], nil
|
||||
}
|
||||
|
||||
// UpdateProduct edits a Product identified by its id
|
||||
func UpdateProduct(id int, p *models.Product) error {
|
||||
idx, _, err := findProduct(id)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ import (
|
|||
"github.com/rjNemo/go-micro/products/data"
|
||||
)
|
||||
|
||||
// swagger:route DELETE /products/{id} products product
|
||||
// Deletes a product
|
||||
// swagger:route DELETE /products/{id} products deleteProduct
|
||||
// Update a products details
|
||||
//
|
||||
// responses:
|
||||
// 200: productResponse
|
||||
// 204: noContent
|
||||
// 404: errorResponse
|
||||
// 501: errorResponse
|
||||
|
||||
// DeleteProduct edit product identified by id
|
||||
// DeleteProduct delete product from datastore identified by id
|
||||
func (p *Products) DeleteProduct(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
|
|
@ -31,4 +34,6 @@ func (p *Products) DeleteProduct(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, errMsg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// write the no content success header
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
|||
61
products/handlers/docs.go
Normal file
61
products/handlers/docs.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Package classification Product API
|
||||
//
|
||||
// Documentation for Product API
|
||||
//
|
||||
// Schemes: http
|
||||
// BasePath: /
|
||||
// Version: 1.0.0
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
// swagger:meta
|
||||
package handlers
|
||||
|
||||
import "github.com/rjNemo/go-micro/products/models"
|
||||
|
||||
// list of products in the response.
|
||||
// swagger:response productsResponse
|
||||
type productsResponse struct {
|
||||
// All products in the datastore
|
||||
// in: body
|
||||
Body []models.Product
|
||||
}
|
||||
|
||||
// product in the response.
|
||||
// swagger:response productResponse
|
||||
type productResponse struct {
|
||||
// One product in the datastore
|
||||
// in: body
|
||||
Body models.Product
|
||||
}
|
||||
|
||||
// swagger:parameters deleteProduct updateProduct
|
||||
type productIDParameter struct {
|
||||
// The ID of a product in the database
|
||||
// in: path
|
||||
// required: true
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
// empty response
|
||||
// swagger:response noContent
|
||||
type productNoContent struct{}
|
||||
|
||||
// Generic error message returned as a string
|
||||
// swagger:response errorResponse
|
||||
type errorResponse struct {
|
||||
// Description of the error
|
||||
// in: body
|
||||
Body string
|
||||
}
|
||||
|
||||
// Validation errors defined as an array of strings
|
||||
// swagger:response errorValidation
|
||||
type errorValidation struct {
|
||||
// Collection of the errors
|
||||
// in: body
|
||||
Body string
|
||||
}
|
||||
|
|
@ -8,11 +8,11 @@ import (
|
|||
)
|
||||
|
||||
// swagger:route GET /products products listProducts
|
||||
// Returns a list of products
|
||||
// Return a list of products from the database
|
||||
// responses:
|
||||
// 200: productsResponse
|
||||
// 200: productsResponse
|
||||
|
||||
// GetProducts writes all products to response in JSON format
|
||||
// GetProducts returns all products to response in JSON format
|
||||
func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) {
|
||||
p.logger.Println("Handle 'GET' request")
|
||||
// fetch products from the datastore
|
||||
|
|
@ -25,3 +25,36 @@ func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:route GET /products/{id} products getProduct
|
||||
// Return a list of products from the database
|
||||
// responses:
|
||||
// 200: productResponse
|
||||
// 404: errorResponse
|
||||
|
||||
// GetOneProduct handles GET requests
|
||||
func (p *Products) GetOneProduct(w http.ResponseWriter, r *http.Request) {
|
||||
id := getProductID(r)
|
||||
p.logger.Println("[DEBUG] get record id", id)
|
||||
prod, err := data.GetProductByID(id)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
|
||||
case data.ErrorProductNotFound:
|
||||
p.logger.Println("[ERROR] fetching product", err)
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
|
||||
default:
|
||||
p.logger.Println("[ERROR] fetching product", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = prod.ToJSON(w)
|
||||
if err != nil {
|
||||
// we should never be here but log the error just incase
|
||||
p.logger.Println("[ERROR] serializing product", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
products/handlers/middleware.go
Normal file
40
products/handlers/middleware.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rjNemo/go-micro/products/models"
|
||||
)
|
||||
|
||||
// ProductValidationMiddleware validates the data passed by the user
|
||||
func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// create a new product
|
||||
newProd := &models.Product{}
|
||||
// deserialize JSON to product
|
||||
err := newProd.FromJSON(r.Body)
|
||||
if err != nil {
|
||||
p.logger.Printf("Error deserializing %v", err)
|
||||
errMsg := fmt.Sprintf("Unable to decode data: %s\n", err)
|
||||
http.Error(w, errMsg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// validate the product
|
||||
err = newProd.Validate()
|
||||
if err != nil {
|
||||
p.logger.Printf("Error deserializing %v", err)
|
||||
errMsg := fmt.Sprintf("Validation error: %s\n", err)
|
||||
http.Error(w, errMsg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// add product to the context
|
||||
ctx := context.WithValue(r.Context(), KeyProduct{}, newProd)
|
||||
req := r.WithContext(ctx)
|
||||
|
||||
// call the next handler
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
|
@ -7,10 +7,13 @@ import (
|
|||
"github.com/rjNemo/go-micro/products/models"
|
||||
)
|
||||
|
||||
// swagger:route POST /products products product
|
||||
// Creates a product
|
||||
// swagger:route POST /products products createProduct
|
||||
// Create a new product
|
||||
//
|
||||
// responses:
|
||||
// 201: productResponse
|
||||
// 200: productResponse
|
||||
// 422: errorValidation
|
||||
// 501: errorResponse
|
||||
|
||||
// AddProduct reads request body and creates new product
|
||||
func (p *Products) AddProduct(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,11 @@
|
|||
// Package classification Product API
|
||||
//
|
||||
// Documentation for Product API
|
||||
//
|
||||
// Schemes: http
|
||||
// BasePath: /
|
||||
// Version: 1.0.0
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
// swagger:meta
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/rjNemo/go-micro/products/models"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Products is a handler for Products API service
|
||||
|
|
@ -28,22 +13,6 @@ type Products struct {
|
|||
logger *log.Logger
|
||||
}
|
||||
|
||||
// list of products in the response. For go-swagger
|
||||
// swagger:response productsResponse
|
||||
type productsResponse struct {
|
||||
// All products in the datastore
|
||||
// in: body
|
||||
Body []models.Product
|
||||
}
|
||||
|
||||
// product in the response. For go-swagger
|
||||
// swagger:response productResponse
|
||||
type productResponse struct {
|
||||
// One product in the datastore
|
||||
// in: body
|
||||
Body models.Product
|
||||
}
|
||||
|
||||
// New creates a Products handler
|
||||
func New(logger *log.Logger) *Products {
|
||||
return &Products{logger: logger}
|
||||
|
|
@ -52,33 +21,20 @@ func New(logger *log.Logger) *Products {
|
|||
// KeyProduct is a key used to pass validated product to handler
|
||||
type KeyProduct struct{}
|
||||
|
||||
// ProductValidationMiddleware validates the data passed by the user
|
||||
func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// create a new product
|
||||
newProd := &models.Product{}
|
||||
// deserialize JSON to product
|
||||
err := newProd.FromJSON(r.Body)
|
||||
if err != nil {
|
||||
p.logger.Printf("Error deserializing %v", err)
|
||||
errMsg := fmt.Sprintf("Unable to decode data: %s\n", err)
|
||||
http.Error(w, errMsg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// validate the product
|
||||
err = newProd.Validate()
|
||||
if err != nil {
|
||||
p.logger.Printf("Error deserializing %v", err)
|
||||
errMsg := fmt.Sprintf("Validation error: %s\n", err)
|
||||
http.Error(w, errMsg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// getProductID returns the product ID from the URL
|
||||
// Panics if cannot convert the id into an integer
|
||||
// this should never happen as the router ensures that
|
||||
// this is a valid number
|
||||
func getProductID(r *http.Request) int {
|
||||
// parse the product id from the url
|
||||
vars := mux.Vars(r)
|
||||
|
||||
// add product to the context
|
||||
ctx := context.WithValue(r.Context(), KeyProduct{}, newProd)
|
||||
req := r.WithContext(ctx)
|
||||
// convert the id into an integer and return
|
||||
id, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
// should never happen
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// call the next handler
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,22 @@ package handlers
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rjNemo/go-micro/products/data"
|
||||
"github.com/rjNemo/go-micro/products/models"
|
||||
)
|
||||
|
||||
// swagger:route PUT /products/{id} products product
|
||||
// Updates a product
|
||||
// swagger:route PUT /products products updateProduct
|
||||
// Update a products details
|
||||
//
|
||||
// responses:
|
||||
// 204: productResponse
|
||||
// 204: noContent
|
||||
// 404: errorResponse
|
||||
// 422: errorValidation
|
||||
|
||||
// UpdateProduct edit product identified by id
|
||||
func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.Atoi(vars["id"])
|
||||
id := getProductID(r)
|
||||
|
||||
p.logger.Println("Handle 'PUT' request", id)
|
||||
// get product from the request
|
||||
|
|
@ -35,4 +35,6 @@ func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, errMsg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// write the no content success header
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func (p *Products) RegisterRoutes(r *mux.Router) {
|
|||
// GET
|
||||
getRouter := r.Methods(http.MethodGet).Subrouter()
|
||||
getRouter.HandleFunc("/", p.GetProducts)
|
||||
getRouter.HandleFunc("/{id:[0-9]+}", p.GetOneProduct)
|
||||
// POST
|
||||
postRouter := r.Methods(http.MethodPost).Subrouter()
|
||||
postRouter.HandleFunc("/", p.AddProduct)
|
||||
|
|
|
|||
|
|
@ -9,15 +9,36 @@ import (
|
|||
)
|
||||
|
||||
// Product defines the structure of a product
|
||||
// swagger:model
|
||||
type Product struct {
|
||||
ID int `json:"id"` //TODO: use uuid
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Price float32 `json:"price" validate:"gt=0"` // TODO: use int
|
||||
SKU string `json:"sku" validate:"required,sku"`
|
||||
CreatedOn string `json:"-"`
|
||||
UpdatedOn string `json:"-"`
|
||||
DeletedOn string `json:"-"`
|
||||
// the id for this product
|
||||
//
|
||||
// required: true
|
||||
// min: 1
|
||||
ID int `json:"id"` //TODO: use uuid
|
||||
// the name for this poduct
|
||||
//
|
||||
// required: true
|
||||
// max length: 255
|
||||
Name string `json:"name" validate:"required"`
|
||||
// the description for this poduct
|
||||
//
|
||||
// required: false
|
||||
// max length: 10000
|
||||
Description string `json:"description"`
|
||||
// the price for the product
|
||||
//
|
||||
// required: true
|
||||
// min: 0.01
|
||||
Price float32 `json:"price" validate:"gt=0"` // TODO: use int
|
||||
// the SKU for the product
|
||||
//
|
||||
// required: true
|
||||
// pattern: [a-z]+-[a-z]+-[a-z]+
|
||||
SKU string `json:"sku" validate:"required,sku"`
|
||||
CreatedOn string `json:"-"`
|
||||
UpdatedOn string `json:"-"`
|
||||
DeletedOn string `json:"-"`
|
||||
}
|
||||
|
||||
// FromJSON read JSON data to create a new product
|
||||
|
|
@ -25,6 +46,11 @@ func (p *Product) FromJSON(r io.Reader) error {
|
|||
return json.NewDecoder(r).Decode(p)
|
||||
}
|
||||
|
||||
// ToJSON convert product to JSON
|
||||
func (p *Product) ToJSON(w io.Writer) error {
|
||||
return json.NewEncoder(w).Encode(p)
|
||||
}
|
||||
|
||||
// Validate checks object validity
|
||||
func (p *Product) Validate() error {
|
||||
validate := validator.New()
|
||||
|
|
|
|||
91
swagger.yaml
91
swagger.yaml
|
|
@ -6,22 +6,37 @@ definitions:
|
|||
description: Product defines the structure of a product
|
||||
properties:
|
||||
description:
|
||||
description: the description for this poduct
|
||||
maxLength: 10000
|
||||
type: string
|
||||
x-go-name: Description
|
||||
id:
|
||||
description: the id for this product
|
||||
format: int64
|
||||
minimum: 1
|
||||
type: integer
|
||||
x-go-name: ID
|
||||
name:
|
||||
description: the name for this poduct
|
||||
maxLength: 255
|
||||
type: string
|
||||
x-go-name: Name
|
||||
price:
|
||||
description: the price for the product
|
||||
format: float
|
||||
minimum: 0.01
|
||||
type: number
|
||||
x-go-name: Price
|
||||
sku:
|
||||
description: the SKU for the product
|
||||
pattern: '[a-z]+-[a-z]+-[a-z]+'
|
||||
type: string
|
||||
x-go-name: SKU
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- price
|
||||
- sku
|
||||
type: object
|
||||
x-go-package: github.com/rjNemo/go-micro/products/models
|
||||
info:
|
||||
|
|
@ -31,7 +46,7 @@ info:
|
|||
paths:
|
||||
/products:
|
||||
get:
|
||||
description: Returns a list of products
|
||||
description: Return a list of products from the database
|
||||
operationId: listProducts
|
||||
responses:
|
||||
"200":
|
||||
|
|
@ -39,39 +54,83 @@ paths:
|
|||
tags:
|
||||
- products
|
||||
post:
|
||||
description: Creates a product
|
||||
operationId: product
|
||||
description: Create a new product
|
||||
operationId: createProduct
|
||||
responses:
|
||||
"201":
|
||||
"200":
|
||||
$ref: '#/responses/productResponse'
|
||||
"422":
|
||||
$ref: '#/responses/errorValidation'
|
||||
"501":
|
||||
$ref: '#/responses/errorResponse'
|
||||
tags:
|
||||
- products
|
||||
put:
|
||||
description: Update a products details
|
||||
operationId: updateProduct
|
||||
parameters:
|
||||
- description: The ID of a product in the database
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
x-go-name: ID
|
||||
responses:
|
||||
"204":
|
||||
$ref: '#/responses/noContent'
|
||||
"404":
|
||||
$ref: '#/responses/errorResponse'
|
||||
"422":
|
||||
$ref: '#/responses/errorValidation'
|
||||
tags:
|
||||
- products
|
||||
/products/{id}:
|
||||
delete:
|
||||
description: Deletes a product
|
||||
operationId: product
|
||||
description: Update a products details
|
||||
operationId: deleteProduct
|
||||
parameters:
|
||||
- description: The ID of a product in the database
|
||||
format: int64
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
x-go-name: ID
|
||||
responses:
|
||||
"204":
|
||||
$ref: '#/responses/noContent'
|
||||
"404":
|
||||
$ref: '#/responses/errorResponse'
|
||||
"501":
|
||||
$ref: '#/responses/errorResponse'
|
||||
tags:
|
||||
- products
|
||||
get:
|
||||
description: Return a list of products from the database
|
||||
operationId: getProduct
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/productResponse'
|
||||
tags:
|
||||
- products
|
||||
put:
|
||||
description: Updates a product
|
||||
operationId: product
|
||||
responses:
|
||||
"204":
|
||||
$ref: '#/responses/productResponse'
|
||||
"404":
|
||||
$ref: '#/responses/errorResponse'
|
||||
tags:
|
||||
- products
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
errorResponse:
|
||||
description: Generic error message returned as a string
|
||||
errorValidation:
|
||||
description: Validation errors defined as an array of strings
|
||||
noContent:
|
||||
description: empty response
|
||||
productResponse:
|
||||
description: product in the response. For go-swagger
|
||||
description: product in the response.
|
||||
schema:
|
||||
$ref: '#/definitions/Product'
|
||||
productsResponse:
|
||||
description: list of products in the response. For go-swagger
|
||||
description: list of products in the response.
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/Product'
|
||||
|
|
|
|||
Loading…
Reference in a new issue