vendor dependencies with dep
This commit is contained in:
parent
93d8310491
commit
1384296a47
2712 changed files with 965742 additions and 0 deletions
15
vendor/github.com/go-chi/chi/_examples/chi.svg
generated
vendored
Executable file
15
vendor/github.com/go-chi/chi/_examples/chi.svg
generated
vendored
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="203px" height="83px" viewBox="0 0 203 83" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="EPS-RGB">
|
||||
<path d="M27.082,82.653 C21.835,82.666 17.381,81.184 13.743,79.358 C4.828,74.882 0.404,67.014 0.404,55.897 C0.404,44.783 4.85,36.963 13.743,32.439 C17.405,30.66 21.85,29.56 27.082,29.143 L69.451,29.143 C72.066,29.143 74.107,30.241 75.571,32.439 C77.035,34.635 77.767,37.198 77.767,40.127 C77.767,43.057 77.035,45.62 75.571,47.817 C74.107,50.013 72.066,51.112 69.451,51.112 L27.082,51.112 C23.942,51.112 22.374,52.708 22.374,55.897 C22.374,59.089 23.942,60.694 27.082,60.685 L99.876,60.486 C102.491,60.479 104.532,61.586 105.996,63.782 C107.458,65.978 108.193,68.541 108.193,71.47 C108.193,74.4 107.46,76.963 105.996,79.16 C104.532,81.356 102.49,82.449 99.876,82.455 L27.082,82.653" id="Fill-1" fill="#01933F"></path>
|
||||
<path d="M180.865,74.676 L180.865,37.119 C180.865,34.504 181.964,32.491 184.16,31.027 C186.358,29.56 188.921,28.829 191.85,28.829 C194.778,28.829 197.341,29.56 199.54,31.027 C201.736,32.491 202.835,34.504 202.835,37.119 L202.835,74.676 C202.835,77.291 201.736,79.306 199.54,80.77 C197.341,82.234 194.778,82.967 191.85,82.967 C188.921,82.967 186.358,82.234 184.16,80.77 C181.964,79.306 180.865,77.291 180.865,74.676" id="Fill-2" fill="#01933F"></path>
|
||||
<path d="M191.85,22.866 C188.816,22.866 186.227,21.793 184.082,19.649 C181.938,17.504 180.865,14.915 180.865,11.881 C180.865,8.847 181.938,6.258 184.082,4.113 C186.227,1.969 188.816,0.898 191.85,0.898 C194.884,0.898 197.473,1.969 199.617,4.113 C201.762,6.258 202.835,8.847 202.835,11.881 C202.835,14.915 201.762,17.504 199.617,19.649 C197.473,21.793 194.884,22.866 191.85,22.866" id="Fill-3" fill="#83C21F"></path>
|
||||
<path d="M149.481,74.65 L149.481,55.82 C149.481,52.682 147.911,51.112 144.773,51.112 L113.389,51.112 C102.301,51.112 94.505,46.666 90.007,37.773 C88.229,34.113 87.13,29.666 86.711,24.436 L86.711,11.881 C86.711,9.266 87.811,7.225 90.007,5.761 C92.205,4.297 94.768,3.565 97.697,3.565 C100.625,3.565 103.188,4.297 105.386,5.761 C107.583,7.225 108.682,9.266 108.682,11.881 L108.682,24.436 C108.682,27.574 110.25,29.143 113.389,29.143 L144.773,29.143 C150.004,29.56 154.45,30.66 158.112,32.439 C167.005,36.937 171.451,44.731 171.451,55.82 L171.451,74.65 C171.451,77.266 170.352,79.306 168.156,80.77 C165.958,82.234 163.395,82.967 160.466,82.967 C157.536,82.967 154.973,82.234 152.776,80.77 C150.58,79.306 149.481,77.266 149.481,74.65" id="Fill-4" fill="#01933F"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
35
vendor/github.com/go-chi/chi/_examples/custom-handler/main.go
generated
vendored
Normal file
35
vendor/github.com/go-chi/chi/_examples/custom-handler/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type Handler func(w http.ResponseWriter, r *http.Request) error
|
||||
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h(w, r); err != nil {
|
||||
// handle returned error here.
|
||||
w.WriteHeader(503)
|
||||
w.Write([]byte("bad"))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Method("GET", "/", Handler(customHandler))
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func customHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
q := r.URL.Query().Get("err")
|
||||
|
||||
if q != "" {
|
||||
return errors.New(q)
|
||||
}
|
||||
|
||||
w.Write([]byte("foo"))
|
||||
return nil
|
||||
}
|
||||
1
vendor/github.com/go-chi/chi/_examples/fileserver/files/notes.txt
generated
vendored
Normal file
1
vendor/github.com/go-chi/chi/_examples/fileserver/files/notes.txt
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Notessszzz
|
||||
44
vendor/github.com/go-chi/chi/_examples/fileserver/main.go
generated
vendored
Normal file
44
vendor/github.com/go-chi/chi/_examples/fileserver/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
|
||||
workDir, _ := os.Getwd()
|
||||
filesDir := filepath.Join(workDir, "files")
|
||||
FileServer(r, "/files", http.Dir(filesDir))
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
// FileServer conveniently sets up a http.FileServer handler to serve
|
||||
// static files from a http.FileSystem.
|
||||
func FileServer(r chi.Router, path string, root http.FileSystem) {
|
||||
if strings.ContainsAny(path, "{}*") {
|
||||
panic("FileServer does not permit URL parameters.")
|
||||
}
|
||||
|
||||
fs := http.StripPrefix(path, http.FileServer(root))
|
||||
|
||||
if path != "/" && path[len(path)-1] != '/' {
|
||||
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
||||
path += "/"
|
||||
}
|
||||
path += "*"
|
||||
|
||||
r.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fs.ServeHTTP(w, r)
|
||||
}))
|
||||
}
|
||||
115
vendor/github.com/go-chi/chi/_examples/graceful/main.go
generated
vendored
Normal file
115
vendor/github.com/go-chi/chi/_examples/graceful/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/valve"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Our graceful valve shut-off package to manage code preemption and
|
||||
// shutdown signaling.
|
||||
valv := valve.New()
|
||||
baseCtx := valv.Context()
|
||||
|
||||
// Example of a long running background worker thing..
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
func() {
|
||||
valve.Lever(ctx).Open()
|
||||
defer valve.Lever(ctx).Close()
|
||||
|
||||
// actual code doing stuff..
|
||||
fmt.Println("tick..")
|
||||
time.Sleep(2 * time.Second)
|
||||
// end-logic
|
||||
|
||||
// signal control..
|
||||
select {
|
||||
case <-valve.Lever(ctx).Stop():
|
||||
fmt.Println("valve is closed")
|
||||
return
|
||||
|
||||
case <-ctx.Done():
|
||||
fmt.Println("context is cancelled, go home.")
|
||||
return
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
}(baseCtx)
|
||||
|
||||
// HTTP service running in this program as well. The valve context is set
|
||||
// as a base context on the server listener at the point where we instantiate
|
||||
// the server - look lower.
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("sup"))
|
||||
})
|
||||
|
||||
r.Get("/slow", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
valve.Lever(r.Context()).Open()
|
||||
defer valve.Lever(r.Context()).Close()
|
||||
|
||||
select {
|
||||
case <-valve.Lever(r.Context()).Stop():
|
||||
fmt.Println("valve is closed. finish up..")
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
// The above channel simulates some hard work.
|
||||
// We want this handler to complete successfully during a shutdown signal,
|
||||
// so consider the work here as some background routine to fetch a long running
|
||||
// search query to find as many results as possible, but, instead we cut it short
|
||||
// and respond with what we have so far. How a shutdown is handled is entirely
|
||||
// up to the developer, as some code blocks are preemptable, and others are not.
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("all done.\n")))
|
||||
})
|
||||
|
||||
srv := http.Server{Addr: ":3333", Handler: chi.ServerBaseContext(baseCtx, r)}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
// sig is a ^C, handle it
|
||||
fmt.Println("shutting down..")
|
||||
|
||||
// first valv
|
||||
valv.Shutdown(20 * time.Second)
|
||||
|
||||
// create context with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// start http shutdown
|
||||
srv.Shutdown(ctx)
|
||||
|
||||
// verify, in worst case call cancel via defer
|
||||
select {
|
||||
case <-time.After(21 * time.Second):
|
||||
fmt.Println("not all connections done")
|
||||
case <-ctx.Done():
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
srv.ListenAndServe()
|
||||
}
|
||||
18
vendor/github.com/go-chi/chi/_examples/hello-world/main.go
generated
vendored
Normal file
18
vendor/github.com/go-chi/chi/_examples/hello-world/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hello world"))
|
||||
})
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
105
vendor/github.com/go-chi/chi/_examples/limits/main.go
generated
vendored
Normal file
105
vendor/github.com/go-chi/chi/_examples/limits/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Limits
|
||||
// ======
|
||||
// This example demonstrates the use of Timeout, CloseNotify, and
|
||||
// Throttle middlewares.
|
||||
//
|
||||
// Timeout:
|
||||
// cancel a request if processing takes longer than 2.5 seconds,
|
||||
// server will respond with a http.StatusGatewayTimeout.
|
||||
//
|
||||
// CloseNotify:
|
||||
// cancel a request if the client disconnects.
|
||||
//
|
||||
// Throttle:
|
||||
// limit the number of in-flight requests along a particular
|
||||
// routing path and backlog the others.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root."))
|
||||
})
|
||||
|
||||
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("pong"))
|
||||
})
|
||||
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("test")
|
||||
})
|
||||
|
||||
// Slow handlers/operations.
|
||||
r.Group(func(r chi.Router) {
|
||||
// Stop processing when client disconnects.
|
||||
r.Use(middleware.CloseNotify)
|
||||
|
||||
// Stop processing after 2.5 seconds.
|
||||
r.Use(middleware.Timeout(2500 * time.Millisecond))
|
||||
|
||||
r.Get("/slow", func(w http.ResponseWriter, r *http.Request) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
// Processing will take 1-5 seconds.
|
||||
processTime := time.Duration(rand.Intn(4)+1) * time.Second
|
||||
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
|
||||
case <-time.After(processTime):
|
||||
// The above channel simulates some hard work.
|
||||
}
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("Processed in %v seconds\n", processTime)))
|
||||
})
|
||||
})
|
||||
|
||||
// Throttle very expensive handlers/operations.
|
||||
r.Group(func(r chi.Router) {
|
||||
// Stop processing after 30 seconds.
|
||||
r.Use(middleware.Timeout(30 * time.Second))
|
||||
|
||||
// Only one request will be processed at a time.
|
||||
r.Use(middleware.Throttle(1))
|
||||
|
||||
r.Get("/throttled", func(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
switch r.Context().Err() {
|
||||
case context.DeadlineExceeded:
|
||||
w.WriteHeader(504)
|
||||
w.Write([]byte("Processing too slow\n"))
|
||||
default:
|
||||
w.Write([]byte("Canceled\n"))
|
||||
}
|
||||
return
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
// The above channel simulates some hard work.
|
||||
}
|
||||
|
||||
w.Write([]byte("Processed\n"))
|
||||
})
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
141
vendor/github.com/go-chi/chi/_examples/logging/main.go
generated
vendored
Normal file
141
vendor/github.com/go-chi/chi/_examples/logging/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// Custom Structured Logger
|
||||
// ========================
|
||||
// This example demonstrates how to use middleware.RequestLogger,
|
||||
// middleware.LogFormatter and middleware.LogEntry to build a structured
|
||||
// logger using the amazing sirupsen/logrus package as the logging
|
||||
// backend.
|
||||
//
|
||||
// Also: check out https://github.com/pressly/lg for an improved context
|
||||
// logger with support for HTTP request logging, based on the example
|
||||
// below.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Setup the logger backend using sirupsen/logrus and configure
|
||||
// it to use a custom JSONFormatter. See the logrus docs for how to
|
||||
// configure the backend at github.com/sirupsen/logrus
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{
|
||||
// disable, as we set our own
|
||||
DisableTimestamp: true,
|
||||
}
|
||||
|
||||
// Routes
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(NewStructuredLogger(logger))
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("welcome"))
|
||||
})
|
||||
r.Get("/wait", func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(1 * time.Second)
|
||||
LogEntrySetField(r, "wait", true)
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("oops")
|
||||
})
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
// StructuredLogger is a simple, but powerful implementation of a custom structured
|
||||
// logger backed on logrus. I encourage users to copy it, adapt it and make it their
|
||||
// own. Also take a look at https://github.com/pressly/lg for a dedicated pkg based
|
||||
// on this work, designed for context-based http routers.
|
||||
|
||||
func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler {
|
||||
return middleware.RequestLogger(&StructuredLogger{logger})
|
||||
}
|
||||
|
||||
type StructuredLogger struct {
|
||||
Logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
|
||||
entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)}
|
||||
logFields := logrus.Fields{}
|
||||
|
||||
logFields["ts"] = time.Now().UTC().Format(time.RFC1123)
|
||||
|
||||
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
|
||||
logFields["req_id"] = reqID
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
logFields["http_scheme"] = scheme
|
||||
logFields["http_proto"] = r.Proto
|
||||
logFields["http_method"] = r.Method
|
||||
|
||||
logFields["remote_addr"] = r.RemoteAddr
|
||||
logFields["user_agent"] = r.UserAgent()
|
||||
|
||||
logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
|
||||
|
||||
entry.Logger = entry.Logger.WithFields(logFields)
|
||||
|
||||
entry.Logger.Infoln("request started")
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
type StructuredLoggerEntry struct {
|
||||
Logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
l.Logger = l.Logger.WithFields(logrus.Fields{
|
||||
"resp_status": status, "resp_bytes_length": bytes,
|
||||
"resp_elasped_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
|
||||
})
|
||||
|
||||
l.Logger.Infoln("request complete")
|
||||
}
|
||||
|
||||
func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
|
||||
l.Logger = l.Logger.WithFields(logrus.Fields{
|
||||
"stack": string(stack),
|
||||
"panic": fmt.Sprintf("%+v", v),
|
||||
})
|
||||
}
|
||||
|
||||
// Helper methods used by the application to get the request-scoped
|
||||
// logger entry and set additional fields between handlers.
|
||||
//
|
||||
// This is a useful pattern to use to set state on the entry as it
|
||||
// passes through the handler chain, which at any point can be logged
|
||||
// with a call to .Print(), .Info(), etc.
|
||||
|
||||
func GetLogEntry(r *http.Request) logrus.FieldLogger {
|
||||
entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry)
|
||||
return entry.Logger
|
||||
}
|
||||
|
||||
func LogEntrySetField(r *http.Request, key string, value interface{}) {
|
||||
if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
|
||||
entry.Logger = entry.Logger.WithField(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func LogEntrySetFields(r *http.Request, fields map[string]interface{}) {
|
||||
if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
|
||||
entry.Logger = entry.Logger.WithFields(fields)
|
||||
}
|
||||
}
|
||||
515
vendor/github.com/go-chi/chi/_examples/rest/main.go
generated
vendored
Normal file
515
vendor/github.com/go-chi/chi/_examples/rest/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
//
|
||||
// REST
|
||||
// ====
|
||||
// This example demonstrates a HTTP REST web service with some fixture data.
|
||||
// Follow along the example and patterns.
|
||||
//
|
||||
// Also check routes.json for the generated docs from passing the -routes flag
|
||||
//
|
||||
// Boot the server:
|
||||
// ----------------
|
||||
// $ go run main.go
|
||||
//
|
||||
// Client requests:
|
||||
// ----------------
|
||||
// $ curl http://localhost:3333/
|
||||
// root.
|
||||
//
|
||||
// $ curl http://localhost:3333/articles
|
||||
// [{"id":"1","title":"Hi"},{"id":"2","title":"sup"}]
|
||||
//
|
||||
// $ curl http://localhost:3333/articles/1
|
||||
// {"id":"1","title":"Hi"}
|
||||
//
|
||||
// $ curl -X DELETE http://localhost:3333/articles/1
|
||||
// {"id":"1","title":"Hi"}
|
||||
//
|
||||
// $ curl http://localhost:3333/articles/1
|
||||
// "Not Found"
|
||||
//
|
||||
// $ curl -X POST -d '{"id":"will-be-omitted","title":"awesomeness"}' http://localhost:3333/articles
|
||||
// {"id":"97","title":"awesomeness"}
|
||||
//
|
||||
// $ curl http://localhost:3333/articles/97
|
||||
// {"id":"97","title":"awesomeness"}
|
||||
//
|
||||
// $ curl http://localhost:3333/articles
|
||||
// [{"id":"2","title":"sup"},{"id":"97","title":"awesomeness"}]
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/docgen"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var routes = flag.Bool("routes", false, "Generate router documentation")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.URLFormat)
|
||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root."))
|
||||
})
|
||||
|
||||
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("pong"))
|
||||
})
|
||||
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("test")
|
||||
})
|
||||
|
||||
// RESTy routes for "articles" resource
|
||||
r.Route("/articles", func(r chi.Router) {
|
||||
r.With(paginate).Get("/", ListArticles)
|
||||
r.Post("/", CreateArticle) // POST /articles
|
||||
r.Get("/search", SearchArticles) // GET /articles/search
|
||||
|
||||
r.Route("/{articleID}", func(r chi.Router) {
|
||||
r.Use(ArticleCtx) // Load the *Article on the request context
|
||||
r.Get("/", GetArticle) // GET /articles/123
|
||||
r.Put("/", UpdateArticle) // PUT /articles/123
|
||||
r.Delete("/", DeleteArticle) // DELETE /articles/123
|
||||
})
|
||||
|
||||
// GET /articles/whats-up
|
||||
r.With(ArticleCtx).Get("/{articleSlug:[a-z-]+}", GetArticle)
|
||||
})
|
||||
|
||||
// Mount the admin sub-router, which btw is the same as:
|
||||
// r.Route("/admin", func(r chi.Router) { admin routes here })
|
||||
r.Mount("/admin", adminRouter())
|
||||
|
||||
// Passing -routes to the program will generate docs for the above
|
||||
// router definition. See the `routes.json` file in this folder for
|
||||
// the output.
|
||||
if *routes {
|
||||
// fmt.Println(docgen.JSONRoutesDoc(r))
|
||||
fmt.Println(docgen.MarkdownRoutesDoc(r, docgen.MarkdownOpts{
|
||||
ProjectPath: "github.com/go-chi/chi",
|
||||
Intro: "Welcome to the chi/_examples/rest generated docs.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func ListArticles(w http.ResponseWriter, r *http.Request) {
|
||||
if err := render.RenderList(w, r, NewArticleListResponse(articles)); err != nil {
|
||||
render.Render(w, r, ErrRender(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ArticleCtx middleware is used to load an Article object from
|
||||
// the URL parameters passed through as the request. In case
|
||||
// the Article could not be found, we stop here and return a 404.
|
||||
func ArticleCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var article *Article
|
||||
var err error
|
||||
|
||||
if articleID := chi.URLParam(r, "articleID"); articleID != "" {
|
||||
article, err = dbGetArticle(articleID)
|
||||
} else if articleSlug := chi.URLParam(r, "articleSlug"); articleSlug != "" {
|
||||
article, err = dbGetArticleBySlug(articleSlug)
|
||||
} else {
|
||||
render.Render(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "article", article)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// SearchArticles searches the Articles data for a matching article.
|
||||
// It's just a stub, but you get the idea.
|
||||
func SearchArticles(w http.ResponseWriter, r *http.Request) {
|
||||
render.RenderList(w, r, NewArticleListResponse(articles))
|
||||
}
|
||||
|
||||
// CreateArticle persists the posted Article and returns it
|
||||
// back to the client as an acknowledgement.
|
||||
func CreateArticle(w http.ResponseWriter, r *http.Request) {
|
||||
data := &ArticleRequest{}
|
||||
if err := render.Bind(r, data); err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
article := data.Article
|
||||
dbNewArticle(article)
|
||||
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.Render(w, r, NewArticleResponse(article))
|
||||
}
|
||||
|
||||
// GetArticle returns the specific Article. You'll notice it just
|
||||
// fetches the Article right off the context, as its understood that
|
||||
// if we made it this far, the Article must be on the context. In case
|
||||
// its not due to a bug, then it will panic, and our Recoverer will save us.
|
||||
func GetArticle(w http.ResponseWriter, r *http.Request) {
|
||||
// Assume if we've reach this far, we can access the article
|
||||
// context because this handler is a child of the ArticleCtx
|
||||
// middleware. The worst case, the recoverer middleware will save us.
|
||||
article := r.Context().Value("article").(*Article)
|
||||
|
||||
if err := render.Render(w, r, NewArticleResponse(article)); err != nil {
|
||||
render.Render(w, r, ErrRender(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateArticle updates an existing Article in our persistent store.
|
||||
func UpdateArticle(w http.ResponseWriter, r *http.Request) {
|
||||
article := r.Context().Value("article").(*Article)
|
||||
|
||||
data := &ArticleRequest{Article: article}
|
||||
if err := render.Bind(r, data); err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
article = data.Article
|
||||
dbUpdateArticle(article.ID, article)
|
||||
|
||||
render.Render(w, r, NewArticleResponse(article))
|
||||
}
|
||||
|
||||
// DeleteArticle removes an existing Article from our persistent store.
|
||||
func DeleteArticle(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
// Assume if we've reach this far, we can access the article
|
||||
// context because this handler is a child of the ArticleCtx
|
||||
// middleware. The worst case, the recoverer middleware will save us.
|
||||
article := r.Context().Value("article").(*Article)
|
||||
|
||||
article, err = dbRemoveArticle(article.ID)
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
render.Render(w, r, NewArticleResponse(article))
|
||||
}
|
||||
|
||||
// A completely separate router for administrator routes
|
||||
func adminRouter() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
r.Use(AdminOnly)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("admin: index"))
|
||||
})
|
||||
r.Get("/accounts", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("admin: list accounts.."))
|
||||
})
|
||||
r.Get("/users/{userId}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(fmt.Sprintf("admin: view user id %v", chi.URLParam(r, "userId"))))
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// AdminOnly middleware restricts access to just administrators.
|
||||
func AdminOnly(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
isAdmin, ok := r.Context().Value("acl.admin").(bool)
|
||||
if !ok || !isAdmin {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// paginate is a stub, but very possible to implement middleware logic
|
||||
// to handle the request params for handling a paginated request.
|
||||
func paginate(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// just a stub.. some ideas are to look at URL query params for something like
|
||||
// the page number, or the limit, and send a query cursor down the chain
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// This is entirely optional, but I wanted to demonstrate how you could easily
|
||||
// add your own logic to the render.Respond method.
|
||||
func init() {
|
||||
render.Respond = func(w http.ResponseWriter, r *http.Request, v interface{}) {
|
||||
if err, ok := v.(error); ok {
|
||||
|
||||
// We set a default error status response code if one hasn't been set.
|
||||
if _, ok := r.Context().Value(render.StatusCtxKey).(int); !ok {
|
||||
w.WriteHeader(400)
|
||||
}
|
||||
|
||||
// We log the error
|
||||
fmt.Printf("Logging err: %s\n", err.Error())
|
||||
|
||||
// We change the response to not reveal the actual error message,
|
||||
// instead we can transform the message something more friendly or mapped
|
||||
// to some code / language, etc.
|
||||
render.DefaultResponder(w, r, render.M{"status": "error"})
|
||||
return
|
||||
}
|
||||
|
||||
render.DefaultResponder(w, r, v)
|
||||
}
|
||||
}
|
||||
|
||||
//--
|
||||
// Request and Response payloads for the REST api.
|
||||
//
|
||||
// The payloads embed the data model objects an
|
||||
//
|
||||
// In a real-world project, it would make sense to put these payloads
|
||||
// in another file, or another sub-package.
|
||||
//--
|
||||
|
||||
type UserPayload struct {
|
||||
*User
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func NewUserPayloadResponse(user *User) *UserPayload {
|
||||
return &UserPayload{User: user}
|
||||
}
|
||||
|
||||
// Bind on UserPayload will run after the unmarshalling is complete, its
|
||||
// a good time to focus some post-processing after a decoding.
|
||||
func (u *UserPayload) Bind(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserPayload) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
u.Role = "collaborator"
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArticleRequest is the request payload for Article data model.
|
||||
//
|
||||
// NOTE: It's good practice to have well defined request and response payloads
|
||||
// so you can manage the specific inputs and outputs for clients, and also gives
|
||||
// you the opportunity to transform data on input or output, for example
|
||||
// on request, we'd like to protect certain fields and on output perhaps
|
||||
// we'd like to include a computed field based on other values that aren't
|
||||
// in the data model. Also, check out this awesome blog post on struct composition:
|
||||
// http://attilaolah.eu/2014/09/10/json-and-struct-composition-in-go/
|
||||
type ArticleRequest struct {
|
||||
*Article
|
||||
|
||||
User *UserPayload `json:"user,omitempty"`
|
||||
|
||||
ProtectedID string `json:"id"` // override 'id' json to have more control
|
||||
}
|
||||
|
||||
func (a *ArticleRequest) Bind(r *http.Request) error {
|
||||
// just a post-process after a decode..
|
||||
a.ProtectedID = "" // unset the protected ID
|
||||
a.Article.Title = strings.ToLower(a.Article.Title) // as an example, we down-case
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArticleResponse is the response payload for the Article data model.
|
||||
// See NOTE above in ArticleRequest as well.
|
||||
//
|
||||
// In the ArticleResponse object, first a Render() is called on itself,
|
||||
// then the next field, and so on, all the way down the tree.
|
||||
// Render is called in top-down order, like a http handler middleware chain.
|
||||
type ArticleResponse struct {
|
||||
*Article
|
||||
|
||||
User *UserPayload `json:"user,omitempty"`
|
||||
|
||||
// We add an additional field to the response here.. such as this
|
||||
// elapsed computed property
|
||||
Elapsed int64 `json:"elapsed"`
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *Article) *ArticleResponse {
|
||||
resp := &ArticleResponse{Article: article}
|
||||
|
||||
if resp.User == nil {
|
||||
if user, _ := dbGetUser(resp.UserID); user != nil {
|
||||
resp.User = NewUserPayloadResponse(user)
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (rd *ArticleResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
// Pre-processing before a response is marshalled and sent across the wire
|
||||
rd.Elapsed = 10
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArticleListResponse []*ArticleResponse
|
||||
|
||||
func NewArticleListResponse(articles []*Article) []render.Renderer {
|
||||
list := []render.Renderer{}
|
||||
for _, article := range articles {
|
||||
list = append(list, NewArticleResponse(article))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// NOTE: as a thought, the request and response payloads for an Article could be the
|
||||
// same payload type, perhaps will do an example with it as well.
|
||||
// type ArticlePayload struct {
|
||||
// *Article
|
||||
// }
|
||||
|
||||
//--
|
||||
// Error response payloads & renderers
|
||||
//--
|
||||
|
||||
// ErrResponse renderer type for handling all sorts of errors.
|
||||
//
|
||||
// In the best case scenario, the excellent github.com/pkg/errors package
|
||||
// helps reveal information on the error, setting it on Err, and in the Render()
|
||||
// method, using it to set the application-specific error code in AppCode.
|
||||
type ErrResponse struct {
|
||||
Err error `json:"-"` // low-level runtime error
|
||||
HTTPStatusCode int `json:"-"` // http response status code
|
||||
|
||||
StatusText string `json:"status"` // user-level status message
|
||||
AppCode int64 `json:"code,omitempty"` // application-specific error code
|
||||
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
|
||||
}
|
||||
|
||||
func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
render.Status(r, e.HTTPStatusCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ErrInvalidRequest(err error) render.Renderer {
|
||||
return &ErrResponse{
|
||||
Err: err,
|
||||
HTTPStatusCode: 400,
|
||||
StatusText: "Invalid request.",
|
||||
ErrorText: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func ErrRender(err error) render.Renderer {
|
||||
return &ErrResponse{
|
||||
Err: err,
|
||||
HTTPStatusCode: 422,
|
||||
StatusText: "Error rendering response.",
|
||||
ErrorText: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
var ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: "Resource not found."}
|
||||
|
||||
//--
|
||||
// Data model objects and persistence mocks:
|
||||
//--
|
||||
|
||||
// User data model
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Article data model. I suggest looking at https://upper.io for an easy
|
||||
// and powerful data persistence adapter.
|
||||
type Article struct {
|
||||
ID string `json:"id"`
|
||||
UserID int64 `json:"user_id"` // the author
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
// Article fixture data
|
||||
var articles = []*Article{
|
||||
{ID: "1", UserID: 100, Title: "Hi", Slug: "hi"},
|
||||
{ID: "2", UserID: 200, Title: "sup", Slug: "sup"},
|
||||
{ID: "3", UserID: 300, Title: "alo", Slug: "alo"},
|
||||
{ID: "4", UserID: 400, Title: "bonjour", Slug: "bonjour"},
|
||||
{ID: "5", UserID: 500, Title: "whats up", Slug: "whats-up"},
|
||||
}
|
||||
|
||||
// User fixture data
|
||||
var users = []*User{
|
||||
{ID: 100, Name: "Peter"},
|
||||
{ID: 200, Name: "Julia"},
|
||||
}
|
||||
|
||||
func dbNewArticle(article *Article) (string, error) {
|
||||
article.ID = fmt.Sprintf("%d", rand.Intn(100)+10)
|
||||
articles = append(articles, article)
|
||||
return article.ID, nil
|
||||
}
|
||||
|
||||
func dbGetArticle(id string) (*Article, error) {
|
||||
for _, a := range articles {
|
||||
if a.ID == id {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbGetArticleBySlug(slug string) (*Article, error) {
|
||||
for _, a := range articles {
|
||||
if a.Slug == slug {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbUpdateArticle(id string, article *Article) (*Article, error) {
|
||||
for i, a := range articles {
|
||||
if a.ID == id {
|
||||
articles[i] = article
|
||||
return article, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbRemoveArticle(id string) (*Article, error) {
|
||||
for i, a := range articles {
|
||||
if a.ID == id {
|
||||
articles = append((articles)[:i], (articles)[i+1:]...)
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbGetUser(id int64) (*User, error) {
|
||||
for _, u := range users {
|
||||
if u.ID == id {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("user not found.")
|
||||
}
|
||||
260
vendor/github.com/go-chi/chi/_examples/rest/routes.json
generated
vendored
Normal file
260
vendor/github.com/go-chi/chi/_examples/rest/routes.json
generated
vendored
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
{
|
||||
"router": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "RequestID",
|
||||
"comment": "RequestID is a middleware that injects a request ID into the context of each\nrequest. A request ID is a string of the form \"host.example.com/random-0001\",\nwhere \"random\" is a base62 random string that uniquely identifies this go\nprocess, and where the last number is an atomically incremented request\ncounter.\n",
|
||||
"file": "github.com/go-chi/chi/middleware/request_id.go",
|
||||
"line": 63
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "Logger",
|
||||
"comment": "Logger is a middleware that logs the start and end of each request, along\nwith some useful data about what was requested, what the response status was,\nand how long it took to return. When standard output is a TTY, Logger will\nprint in color, otherwise it will print in black and white. Logger prints a\nrequest ID if one is provided.\n\nAlternatively, look at https://github.com/pressly/lg and the `lg.RequestLogger`\nmiddleware pkg.\n",
|
||||
"file": "github.com/go-chi/chi/middleware/logger.go",
|
||||
"line": 26
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "Recoverer",
|
||||
"comment": "Recoverer is a middleware that recovers from panics, logs the panic (and a\nbacktrace), and returns a HTTP 500 (Internal Server Error) status if\npossible. Recoverer prints a request ID if one is provided.\n\nAlternatively, look at https://github.com/pressly/lg middleware pkgs.\n",
|
||||
"file": "github.com/go-chi/chi/middleware/recoverer.go",
|
||||
"line": 18
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "URLFormat",
|
||||
"comment": "URLFormat is a middleware that parses the url extension from a request path and stores it\non the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will\ntrim the suffix from the routing path and continue routing.\n\nRouters should not include a url parameter for the suffix when using this middleware.\n\nSample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml`\n\n func routes() http.Handler {\n r := chi.NewRouter()\n r.Use(middleware.URLFormat)\n\n r.Get(\"/articles/{id}\", ListArticles)\n\n return r\n }\n\n func ListArticles(w http.ResponseWriter, r *http.Request) {\n\t urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string)\n\n\t switch urlFormat {\n\t case \"json\":\n\t \trender.JSON(w, r, articles)\n\t case \"xml:\"\n\t \trender.XML(w, r, articles)\n\t default:\n\t \trender.JSON(w, r, articles)\n\t }\n}\n",
|
||||
"file": "github.com/go-chi/chi/middleware/url_format.go",
|
||||
"line": 45
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/render",
|
||||
"func": "SetContentType.func1",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/render/content_type.go",
|
||||
"line": 49,
|
||||
"anonymous": true
|
||||
}
|
||||
],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.main.func1",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 69,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/*": {
|
||||
"router": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.AdminOnly",
|
||||
"comment": "AdminOnly middleware restricts access to just administrators.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 238
|
||||
}
|
||||
],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.adminRouter.func1",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 225,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/accounts": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.adminRouter.func2",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 228,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{userId}": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.adminRouter.func3",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 231,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/articles/*": {
|
||||
"router": {
|
||||
"middlewares": [],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.paginate",
|
||||
"comment": "paginate is a stub, but very possible to implement middleware logic\nto handle the request params for handling a paginated request.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 251
|
||||
}
|
||||
],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.ListArticles",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 117
|
||||
},
|
||||
"POST": {
|
||||
"middlewares": [],
|
||||
"method": "POST",
|
||||
"pkg": "",
|
||||
"func": "main.CreateArticle",
|
||||
"comment": "CreateArticle persists the posted Article and returns it\nback to the client as an acknowledgement.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 158
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.SearchArticles",
|
||||
"comment": "SearchArticles searches the Articles data for a matching article.\nIt's just a stub, but you get the idea.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 152
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{articleID}/*": {
|
||||
"router": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.ArticleCtx",
|
||||
"comment": "ArticleCtx middleware is used to load an Article object from\nthe URL parameters passed through as the request. In case\nthe Article could not be found, we stop here and return a 404.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 127
|
||||
}
|
||||
],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"DELETE": {
|
||||
"middlewares": [],
|
||||
"method": "DELETE",
|
||||
"pkg": "",
|
||||
"func": "main.DeleteArticle",
|
||||
"comment": "DeleteArticle removes an existing Article from our persistent store.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 204
|
||||
},
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.GetArticle",
|
||||
"comment": "GetArticle returns the specific Article. You'll notice it just\nfetches the Article right off the context, as its understood that\nif we made it this far, the Article must be on the context. In case\nits not due to a bug, then it will panic, and our Recoverer will save us.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 176
|
||||
},
|
||||
"PUT": {
|
||||
"middlewares": [],
|
||||
"method": "PUT",
|
||||
"pkg": "",
|
||||
"func": "main.UpdateArticle",
|
||||
"comment": "UpdateArticle updates an existing Article in our persistent store.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 189
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{articleSlug:[a-z-]+}": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.ArticleCtx",
|
||||
"comment": "ArticleCtx middleware is used to load an Article object from\nthe URL parameters passed through as the request. In case\nthe Article could not be found, we stop here and return a 404.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 127
|
||||
}
|
||||
],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.GetArticle",
|
||||
"comment": "GetArticle returns the specific Article. You'll notice it just\nfetches the Article right off the context, as its understood that\nif we made it this far, the Article must be on the context. In case\nits not due to a bug, then it will panic, and our Recoverer will save us.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 176
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panic": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.main.func3",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 77,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ping": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.main.func2",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 73,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
vendor/github.com/go-chi/chi/_examples/rest/routes.md
generated
vendored
Normal file
159
vendor/github.com/go-chi/chi/_examples/rest/routes.md
generated
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# github.com/go-chi/chi
|
||||
|
||||
Welcome to the chi/_examples/rest generated docs.
|
||||
|
||||
## Routes
|
||||
|
||||
<details>
|
||||
<summary>`/`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/**
|
||||
- _GET_
|
||||
- [main.main.func1](/_examples/rest/main.go#L69)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/admin/*`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/admin/***
|
||||
- [main.AdminOnly](/_examples/rest/main.go#L238)
|
||||
- **/**
|
||||
- _GET_
|
||||
- [main.adminRouter.func1](/_examples/rest/main.go#L225)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/admin/*/accounts`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/admin/***
|
||||
- [main.AdminOnly](/_examples/rest/main.go#L238)
|
||||
- **/accounts**
|
||||
- _GET_
|
||||
- [main.adminRouter.func2](/_examples/rest/main.go#L228)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/admin/*/users/{userId}`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/admin/***
|
||||
- [main.AdminOnly](/_examples/rest/main.go#L238)
|
||||
- **/users/{userId}**
|
||||
- _GET_
|
||||
- [main.adminRouter.func3](/_examples/rest/main.go#L231)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/**
|
||||
- _GET_
|
||||
- [main.paginate](/_examples/rest/main.go#L251)
|
||||
- [main.ListArticles](/_examples/rest/main.go#L117)
|
||||
- _POST_
|
||||
- [main.CreateArticle](/_examples/rest/main.go#L158)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*/search`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/search**
|
||||
- _GET_
|
||||
- [main.SearchArticles](/_examples/rest/main.go#L152)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*/{articleID}/*`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/{articleID}/***
|
||||
- [main.ArticleCtx](/_examples/rest/main.go#L127)
|
||||
- **/**
|
||||
- _DELETE_
|
||||
- [main.DeleteArticle](/_examples/rest/main.go#L204)
|
||||
- _GET_
|
||||
- [main.GetArticle](/_examples/rest/main.go#L176)
|
||||
- _PUT_
|
||||
- [main.UpdateArticle](/_examples/rest/main.go#L189)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*/{articleSlug:[a-z-]+}`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/{articleSlug:[a-z-]+}**
|
||||
- _GET_
|
||||
- [main.ArticleCtx](/_examples/rest/main.go#L127)
|
||||
- [main.GetArticle](/_examples/rest/main.go#L176)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/panic`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/panic**
|
||||
- _GET_
|
||||
- [main.main.func3](/_examples/rest/main.go#L77)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/ping`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/ping**
|
||||
- _GET_
|
||||
- [main.main.func2](/_examples/rest/main.go#L73)
|
||||
|
||||
</details>
|
||||
|
||||
Total # of routes: 10
|
||||
|
||||
34
vendor/github.com/go-chi/chi/_examples/todos-resource/main.go
generated
vendored
Normal file
34
vendor/github.com/go-chi/chi/_examples/todos-resource/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Todos Resource
|
||||
// ==============
|
||||
// This example demonstrates a project structure that defines a subrouter and its
|
||||
// handlers on a struct, and mounting them as subrouters to a parent router.
|
||||
// See also _examples/rest for an in-depth example of a REST service, and apply
|
||||
// those same patterns to this structure.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("."))
|
||||
})
|
||||
|
||||
r.Mount("/users", usersResource{}.Routes())
|
||||
r.Mount("/todos", todosResource{}.Routes())
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
53
vendor/github.com/go-chi/chi/_examples/todos-resource/todos.go
generated
vendored
Normal file
53
vendor/github.com/go-chi/chi/_examples/todos-resource/todos.go
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type todosResource struct{}
|
||||
|
||||
// Routes creates a REST router for the todos resource
|
||||
func (rs todosResource) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
// r.Use() // some middleware..
|
||||
|
||||
r.Get("/", rs.List) // GET /todos - read a list of todos
|
||||
r.Post("/", rs.Create) // POST /todos - create a new todo and persist it
|
||||
r.Put("/", rs.Delete)
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
// r.Use(rs.TodoCtx) // lets have a todos map, and lets actually load/manipulate
|
||||
r.Get("/", rs.Get) // GET /todos/{id} - read a single todo by :id
|
||||
r.Put("/", rs.Update) // PUT /todos/{id} - update a single todo by :id
|
||||
r.Delete("/", rs.Delete) // DELETE /todos/{id} - delete a single todo by :id
|
||||
r.Get("/sync", rs.Sync)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (rs todosResource) List(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todos list of stuff.."))
|
||||
}
|
||||
|
||||
func (rs todosResource) Create(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todos create"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Get(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo get"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Update(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo update"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo delete"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Sync(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo sync"))
|
||||
}
|
||||
48
vendor/github.com/go-chi/chi/_examples/todos-resource/users.go
generated
vendored
Normal file
48
vendor/github.com/go-chi/chi/_examples/todos-resource/users.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type usersResource struct{}
|
||||
|
||||
// Routes creates a REST router for the todos resource
|
||||
func (rs usersResource) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
// r.Use() // some middleware..
|
||||
|
||||
r.Get("/", rs.List) // GET /todos - read a list of todos
|
||||
r.Post("/", rs.Create) // POST /todos - create a new todo and persist it
|
||||
r.Put("/", rs.Delete)
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
// r.Use(rs.TodoCtx) // lets have a todos map, and lets actually load/manipulate
|
||||
r.Get("/", rs.Get) // GET /todos/{id} - read a single todo by :id
|
||||
r.Put("/", rs.Update) // PUT /todos/{id} - update a single todo by :id
|
||||
r.Delete("/", rs.Delete) // DELETE /todos/{id} - delete a single todo by :id
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (rs usersResource) List(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa list of stuff.."))
|
||||
}
|
||||
|
||||
func (rs usersResource) Create(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa create"))
|
||||
}
|
||||
|
||||
func (rs usersResource) Get(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa get"))
|
||||
}
|
||||
|
||||
func (rs usersResource) Update(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa update"))
|
||||
}
|
||||
|
||||
func (rs usersResource) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa delete"))
|
||||
}
|
||||
9
vendor/github.com/go-chi/chi/_examples/versions/data/article.go
generated
vendored
Normal file
9
vendor/github.com/go-chi/chi/_examples/versions/data/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package data
|
||||
|
||||
// Article is runtime object, that's not meant to be sent via REST.
|
||||
type Article struct {
|
||||
ID int `db:"id" json:"id" xml:"id"`
|
||||
Title string `db:"title" json:"title" xml:"title"`
|
||||
Data []string `db:"data,stringarray" json:"data" xml:"data"`
|
||||
CustomDataForAuthUsers string `db:"custom_data" json:"-" xml:"-"`
|
||||
}
|
||||
28
vendor/github.com/go-chi/chi/_examples/versions/data/errors.go
generated
vendored
Normal file
28
vendor/github.com/go-chi/chi/_examples/versions/data/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnauthorized = errors.New("Unauthorized")
|
||||
ErrForbidden = errors.New("Forbidden")
|
||||
ErrNotFound = errors.New("Resource not found")
|
||||
)
|
||||
|
||||
func PresentError(r *http.Request, err error) (*http.Request, interface{}) {
|
||||
switch err {
|
||||
case ErrUnauthorized:
|
||||
render.Status(r, 401)
|
||||
case ErrForbidden:
|
||||
render.Status(r, 403)
|
||||
case ErrNotFound:
|
||||
render.Status(r, 404)
|
||||
default:
|
||||
render.Status(r, 500)
|
||||
}
|
||||
return r, map[string]string{"error": err.Error()}
|
||||
}
|
||||
158
vendor/github.com/go-chi/chi/_examples/versions/main.go
generated
vendored
Normal file
158
vendor/github.com/go-chi/chi/_examples/versions/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// Versions
|
||||
// ========
|
||||
// This example demonstrates the use of the render subpackage, with
|
||||
// a quick concept for how to support multiple api versions.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
"github.com/go-chi/chi/_examples/versions/presenter/v1"
|
||||
"github.com/go-chi/chi/_examples/versions/presenter/v2"
|
||||
"github.com/go-chi/chi/_examples/versions/presenter/v3"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// API version 3.
|
||||
r.Route("/v3", func(r chi.Router) {
|
||||
r.Use(apiVersionCtx("v3"))
|
||||
r.Mount("/articles", articleRouter())
|
||||
})
|
||||
|
||||
// API version 2.
|
||||
r.Route("/v2", func(r chi.Router) {
|
||||
r.Use(apiVersionCtx("v2"))
|
||||
r.Mount("/articles", articleRouter())
|
||||
})
|
||||
|
||||
// API version 1.
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
r.Use(randomErrorMiddleware) // Simulate random error, ie. version 1 is buggy.
|
||||
r.Use(apiVersionCtx("v1"))
|
||||
r.Mount("/articles", articleRouter())
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func apiVersionCtx(version string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(context.WithValue(r.Context(), "api.version", version))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func articleRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", listArticles)
|
||||
r.Route("/{articleID}", func(r chi.Router) {
|
||||
r.Get("/", getArticle)
|
||||
// r.Put("/", updateArticle)
|
||||
// r.Delete("/", deleteArticle)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func listArticles(w http.ResponseWriter, r *http.Request) {
|
||||
articles := make(chan render.Renderer, 5)
|
||||
|
||||
// Load data asynchronously into the channel (simulate slow storage):
|
||||
go func() {
|
||||
for i := 1; i <= 10; i++ {
|
||||
article := &data.Article{
|
||||
ID: i,
|
||||
Title: fmt.Sprintf("Article #%v", i),
|
||||
Data: []string{"one", "two", "three", "four"},
|
||||
CustomDataForAuthUsers: "secret data for auth'd users only",
|
||||
}
|
||||
|
||||
apiVersion := r.Context().Value("api.version").(string)
|
||||
switch apiVersion {
|
||||
case "v1":
|
||||
articles <- v1.NewArticleResponse(article)
|
||||
case "v2":
|
||||
articles <- v2.NewArticleResponse(article)
|
||||
default:
|
||||
articles <- v3.NewArticleResponse(article)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
close(articles)
|
||||
}()
|
||||
|
||||
// Start streaming data from the channel.
|
||||
render.Respond(w, r, articles)
|
||||
}
|
||||
|
||||
func getArticle(w http.ResponseWriter, r *http.Request) {
|
||||
// Load article.
|
||||
if chi.URLParam(r, "articleID") != "1" {
|
||||
render.Respond(w, r, data.ErrNotFound)
|
||||
return
|
||||
}
|
||||
article := &data.Article{
|
||||
ID: 1,
|
||||
Title: "Article #1",
|
||||
Data: []string{"one", "two", "three", "four"},
|
||||
CustomDataForAuthUsers: "secret data for auth'd users only",
|
||||
}
|
||||
|
||||
// Simulate some context values:
|
||||
// 1. ?auth=true simluates authenticated session/user.
|
||||
// 2. ?error=true simulates random error.
|
||||
if r.URL.Query().Get("auth") != "" {
|
||||
r = r.WithContext(context.WithValue(r.Context(), "auth", true))
|
||||
}
|
||||
if r.URL.Query().Get("error") != "" {
|
||||
render.Respond(w, r, errors.New("error"))
|
||||
return
|
||||
}
|
||||
|
||||
var payload render.Renderer
|
||||
|
||||
apiVersion := r.Context().Value("api.version").(string)
|
||||
switch apiVersion {
|
||||
case "v1":
|
||||
payload = v1.NewArticleResponse(article)
|
||||
case "v2":
|
||||
payload = v2.NewArticleResponse(article)
|
||||
default:
|
||||
payload = v3.NewArticleResponse(article)
|
||||
}
|
||||
|
||||
render.Render(w, r, payload)
|
||||
}
|
||||
|
||||
func randomErrorMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
// One in three chance of random error.
|
||||
if rand.Int31n(3) == 0 {
|
||||
errors := []error{data.ErrUnauthorized, data.ErrForbidden, data.ErrNotFound}
|
||||
render.Respond(w, r, errors[rand.Intn(len(errors))])
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
22
vendor/github.com/go-chi/chi/_examples/versions/presenter/v1/article.go
generated
vendored
Normal file
22
vendor/github.com/go-chi/chi/_examples/versions/presenter/v1/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
)
|
||||
|
||||
// Article presented in API version 1.
|
||||
type Article struct {
|
||||
*data.Article
|
||||
|
||||
Data map[string]bool `json:"data" xml:"data"`
|
||||
}
|
||||
|
||||
func (a *Article) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *data.Article) *Article {
|
||||
return &Article{Article: article}
|
||||
}
|
||||
30
vendor/github.com/go-chi/chi/_examples/versions/presenter/v2/article.go
generated
vendored
Normal file
30
vendor/github.com/go-chi/chi/_examples/versions/presenter/v2/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
)
|
||||
|
||||
// Article presented in API version 2.
|
||||
type Article struct {
|
||||
// *v3.Article `json:",inline" xml:",inline"`
|
||||
|
||||
*data.Article
|
||||
|
||||
// Additional fields.
|
||||
SelfURL string `json:"self_url" xml:"self_url"`
|
||||
|
||||
// Omitted fields.
|
||||
URL interface{} `json:"url,omitempty" xml:"url,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Article) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
a.SelfURL = fmt.Sprintf("http://localhost:3333/v2?id=%v", a.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *data.Article) *Article {
|
||||
return &Article{Article: article}
|
||||
}
|
||||
39
vendor/github.com/go-chi/chi/_examples/versions/presenter/v3/article.go
generated
vendored
Normal file
39
vendor/github.com/go-chi/chi/_examples/versions/presenter/v3/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package v3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
)
|
||||
|
||||
// Article presented in API version 2.
|
||||
type Article struct {
|
||||
*data.Article `json:",inline" xml:",inline"`
|
||||
|
||||
// Additional fields.
|
||||
URL string `json:"url" xml:"url"`
|
||||
ViewsCount int64 `json:"views_count" xml:"views_count"`
|
||||
APIVersion string `json:"api_version" xml:"api_version"`
|
||||
|
||||
// Omitted fields.
|
||||
// Show custom_data explicitly for auth'd users only.
|
||||
CustomDataForAuthUsers interface{} `json:"custom_data,omitempty" xml:"custom_data,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Article) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
a.ViewsCount = rand.Int63n(100000)
|
||||
a.URL = fmt.Sprintf("http://localhost:3333/v3/?id=%v", a.ID)
|
||||
|
||||
// Only show to auth'd user.
|
||||
if _, ok := r.Context().Value("auth").(bool); ok {
|
||||
a.CustomDataForAuthUsers = a.Article.CustomDataForAuthUsers
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *data.Article) *Article {
|
||||
return &Article{Article: article}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue