🎓 update documentation

This commit is contained in:
Ruidy Nemausat 2020-07-15 12:22:55 +02:00
parent e9f20850ef
commit b8ef90f3d2
11 changed files with 299 additions and 101 deletions

View file

@ -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)

View file

@ -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
View 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
}

View file

@ -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)
}
}

View 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)
})
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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()

View file

@ -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'