1
2
3
4
5
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
34 r.Route("/v3", func(r chi.Router) {
35 r.Use(apiVersionCtx("v3"))
36 r.Mount("/articles", articleRouter())
37 })
38
39
40 r.Route("/v2", func(r chi.Router) {
41 r.Use(apiVersionCtx("v2"))
42 r.Mount("/articles", articleRouter())
43 })
44
45
46 r.Route("/v1", func(r chi.Router) {
47 r.Use(randomErrorMiddleware)
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
70
71 })
72 return r
73 }
74
75 func listArticles(w http.ResponseWriter, r *http.Request) {
76 articles := make(chan render.Renderer, 5)
77
78
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
104 render.Respond(w, r, articles)
105 }
106
107 func getArticle(w http.ResponseWriter, r *http.Request) {
108
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
121
122
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
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