feat(staticweb): add path prefix stripping to EmbedFSProvider

Adds WithStripPrefix method to allow serving files from subdirectories
at the root path. For example, files at /dist/assets can be made
accessible via /assets by calling WithStripPrefix("/dist").
This commit is contained in:
2026-01-03 14:25:04 +02:00
parent c864aa4d90
commit d33747c2d3

View File

@@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"path"
"strings"
"sync" "sync"
"github.com/bitechdev/ResolveSpec/pkg/server/zipfs" "github.com/bitechdev/ResolveSpec/pkg/server/zipfs"
@@ -15,16 +17,18 @@ import (
// EmbedFSProvider serves files from an embedded filesystem. // EmbedFSProvider serves files from an embedded filesystem.
// It supports both direct embedded directories and embedded zip files. // It supports both direct embedded directories and embedded zip files.
type EmbedFSProvider struct { type EmbedFSProvider struct {
embedFS *embed.FS embedFS *embed.FS
zipFile string // Optional: path within embedded FS to zip file zipFile string // Optional: path within embedded FS to zip file
zipReader *zip.Reader stripPrefix string // Optional: prefix to strip from requested paths (e.g., "/dist")
fs fs.FS zipReader *zip.Reader
mu sync.RWMutex fs fs.FS
mu sync.RWMutex
} }
// NewEmbedFSProvider creates a new EmbedFSProvider. // NewEmbedFSProvider creates a new EmbedFSProvider.
// If zipFile is empty, the embedded FS is used directly. // If zipFile is empty, the embedded FS is used directly.
// If zipFile is specified, it's treated as a path to a zip file within the embedded FS. // If zipFile is specified, it's treated as a path to a zip file within the embedded FS.
// Use WithStripPrefix to configure path prefix stripping.
func NewEmbedFSProvider(embedFS fs.FS, zipFile string) (*EmbedFSProvider, error) { func NewEmbedFSProvider(embedFS fs.FS, zipFile string) (*EmbedFSProvider, error) {
if embedFS == nil { if embedFS == nil {
return nil, fmt.Errorf("embedded filesystem cannot be nil") return nil, fmt.Errorf("embedded filesystem cannot be nil")
@@ -81,6 +85,9 @@ func NewEmbedFSProvider(embedFS fs.FS, zipFile string) (*EmbedFSProvider, error)
} }
// Open opens the named file from the embedded filesystem. // Open opens the named file from the embedded filesystem.
// If a strip prefix is configured, it prepends the prefix to the requested path.
// For example, with stripPrefix="/dist", requesting "/assets/style.css" will
// open "/dist/assets/style.css" from the embedded filesystem.
func (p *EmbedFSProvider) Open(name string) (fs.File, error) { func (p *EmbedFSProvider) Open(name string) (fs.File, error) {
p.mu.RLock() p.mu.RLock()
defer p.mu.RUnlock() defer p.mu.RUnlock()
@@ -89,7 +96,21 @@ func (p *EmbedFSProvider) Open(name string) (fs.File, error) {
return nil, fmt.Errorf("embedded filesystem is closed") return nil, fmt.Errorf("embedded filesystem is closed")
} }
return p.fs.Open(name) // Apply prefix stripping by prepending the prefix to the requested path
actualPath := name
if p.stripPrefix != "" {
// Clean the paths to handle leading/trailing slashes
prefix := strings.Trim(p.stripPrefix, "/")
cleanName := strings.TrimPrefix(name, "/")
if prefix != "" {
actualPath = path.Join(prefix, cleanName)
} else {
actualPath = cleanName
}
}
return p.fs.Open(actualPath)
} }
// Close releases any resources held by the provider. // Close releases any resources held by the provider.
@@ -117,3 +138,21 @@ func (p *EmbedFSProvider) Type() string {
func (p *EmbedFSProvider) ZipFile() string { func (p *EmbedFSProvider) ZipFile() string {
return p.zipFile return p.zipFile
} }
// WithStripPrefix sets the prefix to strip from requested paths.
// For example, WithStripPrefix("/dist") will make files at "/dist/assets"
// accessible via "/assets".
// Returns the provider for method chaining.
func (p *EmbedFSProvider) WithStripPrefix(prefix string) *EmbedFSProvider {
p.mu.Lock()
defer p.mu.Unlock()
p.stripPrefix = prefix
return p
}
// StripPrefix returns the configured strip prefix.
func (p *EmbedFSProvider) StripPrefix() string {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stripPrefix
}