🎓 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) 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 // UpdateProduct edits a Product identified by its id
func UpdateProduct(id int, p *models.Product) error { func UpdateProduct(id int, p *models.Product) error {
idx, _, err := findProduct(id) idx, _, err := findProduct(id)

View file

@ -9,12 +9,15 @@ import (
"github.com/rjNemo/go-micro/products/data" "github.com/rjNemo/go-micro/products/data"
) )
// swagger:route DELETE /products/{id} products product // swagger:route DELETE /products/{id} products deleteProduct
// Deletes a product // Update a products details
//
// responses: // 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) { func (p *Products) DeleteProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"]) 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) http.Error(w, errMsg, http.StatusInternalServerError)
return 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 // swagger:route GET /products products listProducts
// Returns a list of products // Return a list of products from the database
// responses: // 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) { func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) {
p.logger.Println("Handle 'GET' request") p.logger.Println("Handle 'GET' request")
// fetch products from the datastore // fetch products from the datastore
@ -25,3 +25,36 @@ func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) {
return 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" "github.com/rjNemo/go-micro/products/models"
) )
// swagger:route POST /products products product // swagger:route POST /products products createProduct
// Creates a product // Create a new product
//
// responses: // responses:
// 201: productResponse // 200: productResponse
// 422: errorValidation
// 501: errorResponse
// AddProduct reads request body and creates new product // AddProduct reads request body and creates new product
func (p *Products) AddProduct(w http.ResponseWriter, r *http.Request) { 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 package handlers
import ( import (
"context"
"fmt"
"log" "log"
"net/http" "net/http"
"strconv"
"github.com/rjNemo/go-micro/products/models" "github.com/gorilla/mux"
) )
// Products is a handler for Products API service // Products is a handler for Products API service
@ -28,22 +13,6 @@ type Products struct {
logger *log.Logger 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 // New creates a Products handler
func New(logger *log.Logger) *Products { func New(logger *log.Logger) *Products {
return &Products{logger: logger} 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 // KeyProduct is a key used to pass validated product to handler
type KeyProduct struct{} type KeyProduct struct{}
// ProductValidationMiddleware validates the data passed by the user // getProductID returns the product ID from the URL
func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler { // Panics if cannot convert the id into an integer
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // this should never happen as the router ensures that
// create a new product // this is a valid number
newProd := &models.Product{} func getProductID(r *http.Request) int {
// deserialize JSON to product // parse the product id from the url
err := newProd.FromJSON(r.Body) vars := mux.Vars(r)
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 // convert the id into an integer and return
ctx := context.WithValue(r.Context(), KeyProduct{}, newProd) id, err := strconv.Atoi(vars["id"])
req := r.WithContext(ctx) if err != nil {
// should never happen
panic(err)
}
// call the next handler return id
next.ServeHTTP(w, req)
})
} }

View file

@ -3,22 +3,22 @@ package handlers
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/rjNemo/go-micro/products/data" "github.com/rjNemo/go-micro/products/data"
"github.com/rjNemo/go-micro/products/models" "github.com/rjNemo/go-micro/products/models"
) )
// swagger:route PUT /products/{id} products product // swagger:route PUT /products products updateProduct
// Updates a product // Update a products details
//
// responses: // responses:
// 204: productResponse // 204: noContent
// 404: errorResponse
// 422: errorValidation
// UpdateProduct edit product identified by id // UpdateProduct edit product identified by id
func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) { func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) id := getProductID(r)
id, _ := strconv.Atoi(vars["id"])
p.logger.Println("Handle 'PUT' request", id) p.logger.Println("Handle 'PUT' request", id)
// get product from the request // 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) http.Error(w, errMsg, http.StatusInternalServerError)
return 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 // GET
getRouter := r.Methods(http.MethodGet).Subrouter() getRouter := r.Methods(http.MethodGet).Subrouter()
getRouter.HandleFunc("/", p.GetProducts) getRouter.HandleFunc("/", p.GetProducts)
getRouter.HandleFunc("/{id:[0-9]+}", p.GetOneProduct)
// POST // POST
postRouter := r.Methods(http.MethodPost).Subrouter() postRouter := r.Methods(http.MethodPost).Subrouter()
postRouter.HandleFunc("/", p.AddProduct) postRouter.HandleFunc("/", p.AddProduct)

View file

@ -9,15 +9,36 @@ import (
) )
// Product defines the structure of a product // Product defines the structure of a product
// swagger:model
type Product struct { type Product struct {
ID int `json:"id"` //TODO: use uuid // the id for this product
Name string `json:"name" validate:"required"` //
Description string `json:"description"` // required: true
Price float32 `json:"price" validate:"gt=0"` // TODO: use int // min: 1
SKU string `json:"sku" validate:"required,sku"` ID int `json:"id"` //TODO: use uuid
CreatedOn string `json:"-"` // the name for this poduct
UpdatedOn string `json:"-"` //
DeletedOn string `json:"-"` // 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 // 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) 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 // Validate checks object validity
func (p *Product) Validate() error { func (p *Product) Validate() error {
validate := validator.New() validate := validator.New()

View file

@ -6,22 +6,37 @@ definitions:
description: Product defines the structure of a product description: Product defines the structure of a product
properties: properties:
description: description:
description: the description for this poduct
maxLength: 10000
type: string type: string
x-go-name: Description x-go-name: Description
id: id:
description: the id for this product
format: int64 format: int64
minimum: 1
type: integer type: integer
x-go-name: ID x-go-name: ID
name: name:
description: the name for this poduct
maxLength: 255
type: string type: string
x-go-name: Name x-go-name: Name
price: price:
description: the price for the product
format: float format: float
minimum: 0.01
type: number type: number
x-go-name: Price x-go-name: Price
sku: sku:
description: the SKU for the product
pattern: '[a-z]+-[a-z]+-[a-z]+'
type: string type: string
x-go-name: SKU x-go-name: SKU
required:
- id
- name
- price
- sku
type: object type: object
x-go-package: github.com/rjNemo/go-micro/products/models x-go-package: github.com/rjNemo/go-micro/products/models
info: info:
@ -31,7 +46,7 @@ info:
paths: paths:
/products: /products:
get: get:
description: Returns a list of products description: Return a list of products from the database
operationId: listProducts operationId: listProducts
responses: responses:
"200": "200":
@ -39,39 +54,83 @@ paths:
tags: tags:
- products - products
post: post:
description: Creates a product description: Create a new product
operationId: product operationId: createProduct
responses: responses:
"201": "200":
$ref: '#/responses/productResponse' $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: tags:
- products - products
/products/{id}: /products/{id}:
delete: delete:
description: Deletes a product description: Update a products details
operationId: product 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: responses:
"200": "200":
$ref: '#/responses/productResponse' $ref: '#/responses/productResponse'
tags: "404":
- products $ref: '#/responses/errorResponse'
put:
description: Updates a product
operationId: product
responses:
"204":
$ref: '#/responses/productResponse'
tags: tags:
- products - products
produces: produces:
- application/json - application/json
responses: 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: productResponse:
description: product in the response. For go-swagger description: product in the response.
schema: schema:
$ref: '#/definitions/Product' $ref: '#/definitions/Product'
productsResponse: productsResponse:
description: list of products in the response. For go-swagger description: list of products in the response.
schema: schema:
items: items:
$ref: '#/definitions/Product' $ref: '#/definitions/Product'