snapshot of https://github.com/go-chi/chi.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Test / test (1.24.x, ubuntu-latest) (push) Successful in 2m56s
Test / test (1.25.x, ubuntu-latest) (push) Successful in 2m15s
Test / test (1.26.x, ubuntu-latest) (push) Successful in 2m23s
Test / test (1.27.x, ubuntu-latest) (push) Failing after 3m56s
Test / test (1.24.x, windows-latest) (push) Has been cancelled
Test / test (1.25.x, windows-latest) (push) Has been cancelled
Test / test (1.26.x, windows-latest) (push) Has been cancelled
Test / test (1.27.x, windows-latest) (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 21:48:17 +05:00
commit 72c9f2ee75
105 changed files with 14011 additions and 0 deletions

View 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:"-"`
}

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

10
_examples/versions/go.mod Normal file
View File

@@ -0,0 +1,10 @@
module versions
go 1.16
require (
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/render v1.0.3
)
require github.com/ajg/form v1.5.1 // indirect

View File

@@ -0,0 +1,6 @@
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=

154
_examples/versions/main.go Normal file
View File

@@ -0,0 +1,154 @@
// 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/v5"
"github.com/go-chi/chi/v5/_examples/versions/data"
v1 "github.com/go-chi/chi/v5/_examples/versions/presenter/v1"
v2 "github.com/go-chi/chi/v5/_examples/versions/presenter/v2"
v3 "github.com/go-chi/chi/v5/_examples/versions/presenter/v3"
"github.com/go-chi/chi/v5/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 simulates 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)
})
}

View File

@@ -0,0 +1,22 @@
package v1
import (
"net/http"
"github.com/go-chi/chi/v5/_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}
}

View File

@@ -0,0 +1,30 @@
package v2
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5/_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}
}

View File

@@ -0,0 +1,39 @@
package v3
import (
"fmt"
"math/rand"
"net/http"
"github.com/go-chi/chi/v5/_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}
}