...

Source file src/github.com/go-chi/chi/_examples/versions/main.go

Documentation: github.com/go-chi/chi/_examples/versions

     1  //
     2  // Versions
     3  // ========
     4  // This example demonstrates the use of the render subpackage, with
     5  // a quick concept for how to support multiple api versions.
     6  //
     7  package main
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"math/rand"
    14  	"net/http"
    15  	"time"
    16  
    17  	"github.com/go-chi/chi"
    18  	"github.com/go-chi/chi/_examples/versions/data"
    19  	v1 "github.com/go-chi/chi/_examples/versions/presenter/v1"
    20  	v2 "github.com/go-chi/chi/_examples/versions/presenter/v2"
    21  	v3 "github.com/go-chi/chi/_examples/versions/presenter/v3"
    22  	"github.com/go-chi/chi/middleware"
    23  	"github.com/go-chi/render"
    24  )
    25  
    26  func main() {
    27  	r := chi.NewRouter()
    28  
    29  	r.Use(middleware.RequestID)
    30  	r.Use(middleware.Logger)
    31  	r.Use(middleware.Recoverer)
    32  
    33  	// API version 3.
    34  	r.Route("/v3", func(r chi.Router) {
    35  		r.Use(apiVersionCtx("v3"))
    36  		r.Mount("/articles", articleRouter())
    37  	})
    38  
    39  	// API version 2.
    40  	r.Route("/v2", func(r chi.Router) {
    41  		r.Use(apiVersionCtx("v2"))
    42  		r.Mount("/articles", articleRouter())
    43  	})
    44  
    45  	// API version 1.
    46  	r.Route("/v1", func(r chi.Router) {
    47  		r.Use(randomErrorMiddleware) // Simulate random error, ie. version 1 is buggy.
    48  		r.Use(apiVersionCtx("v1"))
    49  		r.Mount("/articles", articleRouter())
    50  	})
    51  
    52  	http.ListenAndServe(":3333", r)
    53  }
    54  
    55  func apiVersionCtx(version string) func(next http.Handler) http.Handler {
    56  	return func(next http.Handler) http.Handler {
    57  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    58  			r = r.WithContext(context.WithValue(r.Context(), "api.version", version))
    59  			next.ServeHTTP(w, r)
    60  		})
    61  	}
    62  }
    63  
    64  func articleRouter() http.Handler {
    65  	r := chi.NewRouter()
    66  	r.Get("/", listArticles)
    67  	r.Route("/{articleID}", func(r chi.Router) {
    68  		r.Get("/", getArticle)
    69  		// r.Put("/", updateArticle)
    70  		// r.Delete("/", deleteArticle)
    71  	})
    72  	return r
    73  }
    74  
    75  func listArticles(w http.ResponseWriter, r *http.Request) {
    76  	articles := make(chan render.Renderer, 5)
    77  
    78  	// Load data asynchronously into the channel (simulate slow storage):
    79  	go func() {
    80  		for i := 1; i <= 10; i++ {
    81  			article := &data.Article{
    82  				ID:                     i,
    83  				Title:                  fmt.Sprintf("Article #%v", i),
    84  				Data:                   []string{"one", "two", "three", "four"},
    85  				CustomDataForAuthUsers: "secret data for auth'd users only",
    86  			}
    87  
    88  			apiVersion := r.Context().Value("api.version").(string)
    89  			switch apiVersion {
    90  			case "v1":
    91  				articles <- v1.NewArticleResponse(article)
    92  			case "v2":
    93  				articles <- v2.NewArticleResponse(article)
    94  			default:
    95  				articles <- v3.NewArticleResponse(article)
    96  			}
    97  
    98  			time.Sleep(100 * time.Millisecond)
    99  		}
   100  		close(articles)
   101  	}()
   102  
   103  	// Start streaming data from the channel.
   104  	render.Respond(w, r, articles)
   105  }
   106  
   107  func getArticle(w http.ResponseWriter, r *http.Request) {
   108  	// Load article.
   109  	if chi.URLParam(r, "articleID") != "1" {
   110  		render.Respond(w, r, data.ErrNotFound)
   111  		return
   112  	}
   113  	article := &data.Article{
   114  		ID:                     1,
   115  		Title:                  "Article #1",
   116  		Data:                   []string{"one", "two", "three", "four"},
   117  		CustomDataForAuthUsers: "secret data for auth'd users only",
   118  	}
   119  
   120  	// Simulate some context values:
   121  	// 1. ?auth=true simluates authenticated session/user.
   122  	// 2. ?error=true simulates random error.
   123  	if r.URL.Query().Get("auth") != "" {
   124  		r = r.WithContext(context.WithValue(r.Context(), "auth", true))
   125  	}
   126  	if r.URL.Query().Get("error") != "" {
   127  		render.Respond(w, r, errors.New("error"))
   128  		return
   129  	}
   130  
   131  	var payload render.Renderer
   132  
   133  	apiVersion := r.Context().Value("api.version").(string)
   134  	switch apiVersion {
   135  	case "v1":
   136  		payload = v1.NewArticleResponse(article)
   137  	case "v2":
   138  		payload = v2.NewArticleResponse(article)
   139  	default:
   140  		payload = v3.NewArticleResponse(article)
   141  	}
   142  
   143  	render.Render(w, r, payload)
   144  }
   145  
   146  func randomErrorMiddleware(next http.Handler) http.Handler {
   147  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   148  		rand.Seed(time.Now().Unix())
   149  
   150  		// One in three chance of random error.
   151  		if rand.Int31n(3) == 0 {
   152  			errors := []error{data.ErrUnauthorized, data.ErrForbidden, data.ErrNotFound}
   153  			render.Respond(w, r, errors[rand.Intn(len(errors))])
   154  			return
   155  		}
   156  		next.ServeHTTP(w, r)
   157  	})
   158  }
   159  

View as plain text