Move OnlyHTMLFS to internal and add test

This commit is contained in:
sunshineplan 2024-09-10 11:24:58 +08:00
parent e21fcce345
commit eb4a5e1866
9 changed files with 133 additions and 78 deletions

View File

@ -1351,6 +1351,8 @@ func main() {
router := gin.Default() router := gin.Default()
router.LoadHTMLGlob("templates/*") router.LoadHTMLGlob("templates/*")
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
//router.LoadHTMLFS(http.Dir("templates"), "template1.html", "template2.html")
//or
//router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html") //router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html")
router.GET("/index", func(c *gin.Context) { router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{ c.HTML(http.StatusOK, "index.tmpl", gin.H{

17
fs.go
View File

@ -5,7 +5,6 @@
package gin package gin
import ( import (
"io/fs"
"net/http" "net/http"
"os" "os"
) )
@ -26,22 +25,6 @@ func (o OnlyFilesFS) Open(name string) (http.File, error) {
return neutralizedReaddirFile{f}, nil return neutralizedReaddirFile{f}, nil
} }
// OnlyHTMLFS implements an [fs.FS].
type OnlyHTMLFS struct {
FileSystem http.FileSystem
}
// Open passes `Open` to the upstream implementation and return an [fs.File].
func (o OnlyHTMLFS) Open(name string) (fs.File, error) {
f, err := o.FileSystem.Open(name)
if err != nil {
return nil, err
}
return fs.File(f), nil
}
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`. // neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
type neutralizedReaddirFile struct { type neutralizedReaddirFile struct {
http.File http.File

View File

@ -48,36 +48,6 @@ func TestOnlyFilesFS_Open_err(t *testing.T) {
assert.Nil(t, file) assert.Nil(t, file)
} }
func TestOnlyHTMLFS_Open(t *testing.T) {
var testFile *os.File
mockFS := &mockFileSystem{
open: func(name string) (http.File, error) {
return testFile, nil
},
}
fs := &OnlyHTMLFS{FileSystem: mockFS}
file, err := fs.Open("foo")
require.NoError(t, err)
assert.Equal(t, testFile, file)
}
func TestOnlyHTMLFS_Open_err(t *testing.T) {
testError := errors.New("mock")
mockFS := &mockFileSystem{
open: func(_ string) (http.File, error) {
return nil, testError
},
}
fs := &OnlyHTMLFS{FileSystem: mockFS}
file, err := fs.Open("foo")
require.ErrorIs(t, err, testError)
assert.Nil(t, file)
}
func Test_neuteredReaddirFile_Readdir(t *testing.T) { func Test_neuteredReaddirFile_Readdir(t *testing.T) {
n := neutralizedReaddirFile{} n := neutralizedReaddirFile{}

6
gin.go
View File

@ -16,6 +16,7 @@ import (
"sync" "sync"
"github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/bytesconv"
filesystem "github.com/gin-gonic/gin/internal/fs"
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
"github.com/quic-go/quic-go/http3" "github.com/quic-go/quic-go/http3"
@ -289,11 +290,12 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
// and associates the result with HTML renderer. // and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) { func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
if IsDebugging() { if IsDebugging() {
engine.HTMLRender = render.HTMLDebug{FS: OnlyHTMLFS{fs}, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims} engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
return return
} }
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(OnlyHTMLFS{fs}, patterns...)) templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(
filesystem.FileSystem{FileSystem: fs}, patterns...))
engine.SetHTMLTemplate(templ) engine.SetHTMLTemplate(templ)
} }

View File

@ -6,7 +6,6 @@ package gin
import ( import (
"crypto/tls" "crypto/tls"
"embed"
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
@ -326,8 +325,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
assert.Equal(t, "Date: 2017/07/01", string(resp)) assert.Equal(t, "Date: 2017/07/01", string(resp))
} }
//go:embed testdata/template/*.tmpl var tmplFS = http.Dir("testdata/template")
var tmplFS embed.FS
func TestLoadHTMLFSTestMode(t *testing.T) { func TestLoadHTMLFSTestMode(t *testing.T) {
ts := setupHTMLFiles( ts := setupHTMLFiles(
@ -335,7 +333,7 @@ func TestLoadHTMLFSTestMode(t *testing.T) {
TestMode, TestMode,
false, false,
func(router *Engine) { func(router *Engine) {
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl") router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
}, },
) )
defer ts.Close() defer ts.Close()
@ -355,7 +353,7 @@ func TestLoadHTMLFSDebugMode(t *testing.T) {
DebugMode, DebugMode,
false, false,
func(router *Engine) { func(router *Engine) {
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl") router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
}, },
) )
defer ts.Close() defer ts.Close()
@ -375,7 +373,7 @@ func TestLoadHTMLFSReleaseMode(t *testing.T) {
ReleaseMode, ReleaseMode,
false, false,
func(router *Engine) { func(router *Engine) {
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl") router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
}, },
) )
defer ts.Close() defer ts.Close()
@ -395,7 +393,7 @@ func TestLoadHTMLFSUsingTLS(t *testing.T) {
TestMode, TestMode,
true, true,
func(router *Engine) { func(router *Engine) {
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl") router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
}, },
) )
defer ts.Close() defer ts.Close()
@ -422,7 +420,7 @@ func TestLoadHTMLFSFuncMap(t *testing.T) {
TestMode, TestMode,
false, false,
func(router *Engine) { func(router *Engine) {
router.LoadHTMLFS(http.FS(tmplFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl") router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
}, },
) )
defer ts.Close() defer ts.Close()

22
internal/fs/fs.go Normal file
View File

@ -0,0 +1,22 @@
package fs
import (
"io/fs"
"net/http"
)
// FileSystem implements an [fs.FS].
type FileSystem struct {
http.FileSystem
}
// Open passes `Open` to the upstream implementation and return an [fs.File].
func (o FileSystem) Open(name string) (fs.File, error) {
f, err := o.FileSystem.Open(name)
if err != nil {
return nil, err
}
return fs.File(f), nil
}

49
internal/fs/fs_test.go Normal file
View File

@ -0,0 +1,49 @@
package fs
import (
"errors"
"net/http"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockFileSystem struct {
open func(name string) (http.File, error)
}
func (m *mockFileSystem) Open(name string) (http.File, error) {
return m.open(name)
}
func TesFileSystem_Open(t *testing.T) {
var testFile *os.File
mockFS := &mockFileSystem{
open: func(name string) (http.File, error) {
return testFile, nil
},
}
fs := &FileSystem{mockFS}
file, err := fs.Open("foo")
require.NoError(t, err)
assert.Equal(t, testFile, file)
}
func TestFileSystem_Open_err(t *testing.T) {
testError := errors.New("mock")
mockFS := &mockFileSystem{
open: func(_ string) (http.File, error) {
return nil, testError
},
}
fs := &FileSystem{mockFS}
file, err := fs.Open("foo")
require.ErrorIs(t, err, testError)
assert.Nil(t, file)
}

View File

@ -6,8 +6,9 @@ package render
import ( import (
"html/template" "html/template"
"io/fs"
"net/http" "net/http"
"github.com/gin-gonic/gin/internal/fs"
) )
// Delims represents a set of Left and Right delimiters for HTML template rendering. // Delims represents a set of Left and Right delimiters for HTML template rendering.
@ -32,12 +33,12 @@ type HTMLProduction struct {
// HTMLDebug contains template delims and pattern and function with file list. // HTMLDebug contains template delims and pattern and function with file list.
type HTMLDebug struct { type HTMLDebug struct {
Files []string Files []string
Glob string Glob string
FS fs.FS FileSystem http.FileSystem
Patterns []string Patterns []string
Delims Delims Delims Delims
FuncMap template.FuncMap FuncMap template.FuncMap
} }
// HTML contains template reference and its name with given interface object. // HTML contains template reference and its name with given interface object.
@ -76,8 +77,9 @@ func (r HTMLDebug) loadTemplate() *template.Template {
if r.Glob != "" { if r.Glob != "" {
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
} }
if r.FS != nil && len(r.Patterns) > 0 { if r.FileSystem != nil && len(r.Patterns) > 0 {
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(r.FS, r.Patterns...)) return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(
fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...))
} }
panic("the HTML debug render was created without files or glob pattern or file system with patterns") panic("the HTML debug render was created without files or glob pattern or file system with patterns")
} }

View File

@ -489,10 +489,12 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
htmlRender := HTMLDebug{ htmlRender := HTMLDebug{
Files: []string{"../testdata/template/hello.tmpl"}, Files: []string{"../testdata/template/hello.tmpl"},
Glob: "", Glob: "",
Delims: Delims{Left: "{[{", Right: "}]}"}, FileSystem: nil,
FuncMap: nil, Patterns: nil,
Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil,
} }
instance := htmlRender.Instance("hello.tmpl", map[string]any{ instance := htmlRender.Instance("hello.tmpl", map[string]any{
"name": "thinkerou", "name": "thinkerou",
@ -508,10 +510,33 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
func TestRenderHTMLDebugGlob(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
htmlRender := HTMLDebug{ htmlRender := HTMLDebug{
Files: nil, Files: nil,
Glob: "../testdata/template/hello*", Glob: "../testdata/template/hello*",
Delims: Delims{Left: "{[{", Right: "}]}"}, FileSystem: nil,
FuncMap: nil, Patterns: nil,
Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil,
}
instance := htmlRender.Instance("hello.tmpl", map[string]any{
"name": "thinkerou",
})
err := instance.Render(w)
require.NoError(t, err)
assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestRenderHTMLDebugFS(t *testing.T) {
w := httptest.NewRecorder()
htmlRender := HTMLDebug{
Files: nil,
Glob: "",
FileSystem: http.Dir("../testdata/template"),
Patterns: []string{"hello.tmpl"},
Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil,
} }
instance := htmlRender.Instance("hello.tmpl", map[string]any{ instance := htmlRender.Instance("hello.tmpl", map[string]any{
"name": "thinkerou", "name": "thinkerou",
@ -526,10 +551,12 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
func TestRenderHTMLDebugPanics(t *testing.T) { func TestRenderHTMLDebugPanics(t *testing.T) {
htmlRender := HTMLDebug{ htmlRender := HTMLDebug{
Files: nil, Files: nil,
Glob: "", Glob: "",
Delims: Delims{"{{", "}}"}, FileSystem: nil,
FuncMap: nil, Patterns: nil,
Delims: Delims{"{{", "}}"},
FuncMap: nil,
} }
assert.Panics(t, func() { htmlRender.Instance("", nil) }) assert.Panics(t, func() { htmlRender.Instance("", nil) })
} }