Select Git revision
error_logging_middleware.go
assets.go 3.86 KiB
package perxis
import (
"io"
"io/fs"
"path/filepath"
"git.perx.ru/perxis/perxis-go/pkg/errors"
jsoniter "github.com/json-iterator/go"
"gopkg.in/yaml.v3"
)
// Assets предоставляет методы для загрузки данных из файловой системы
type Assets[T any] struct {
Constructor func() T
}
// NewAssets возвращает новый экземпляр загрузчика
func NewAssets[T any]() *Assets[T] {
return &Assets[T]{
Constructor: func() (t T) { return t },
}
}
type FromFSFunc[T any] func(fsys fs.FS) ([]T, error)
type FromFileFunc[T any] func(file fs.File) ([]T, error)
func (a *Assets[T]) Funcs() (FromFSFunc[T], FromFileFunc[T]) {
return a.FromFS, a.FromFile
}
// WithConstructor устанавливает конструктор для создания новых экземпляров
func (a *Assets[T]) WithConstructor(t func() T) *Assets[T] {
a.Constructor = t
return a
}
// MustFrom возвращает все записи в переданной файловой системе
func (a *Assets[T]) MustFrom(fsys fs.FS, path string) []T {
res, err := a.From(fsys, path)
if err != nil {
panic(err)
}
return res
}
// MustOneFrom возвращает одну запись из переданного файла
func (a *Assets[T]) MustOneFrom(fsys fs.FS, path string) T {
res, err := a.From(fsys, path)
if err != nil {
panic(err)
}
if len(res) == 0 {
panic(errors.Errorf("no entries found"))
}
if len(res) > 1 {
panic(errors.Errorf("multiple entries found"))
}
return res[0]
}
// From возвращает записи из переданного файла
func (a *Assets[T]) From(fsys fs.FS, path string) ([]T, error) {
f, err := fsys.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return nil, err
}
if stat.IsDir() {
sub, err := fs.Sub(fsys, path)
if err != nil {
return nil, err
}
return a.FromFS(sub)
}
return a.FromFile(f)
}
// FromFile возвращает записи в переданном файле
func (a *Assets[T]) FromFile(file fs.File) ([]T, error) {
stat, err := file.Stat()
if err != nil {
return nil, err
}
switch filepath.Ext(stat.Name()) {
case ".json":
entry, err := a.FromJSON(file)
if err != nil {
return nil, errors.Wrapf(err, "file '%s'", stat.Name())
}
return []T{entry}, nil
case ".yaml", ".yml":
entries, err := a.FromYAML(file)
return entries, errors.Wrapf(err, "file '%s'", stat.Name())
}
return nil, errors.Errorf("file '%s' must be in JSON or YAML format", stat.Name())
}
// FromFS возвращает все записи в переданной файловой системе
func (a *Assets[T]) FromFS(fsys fs.FS) (result []T, err error) {
if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error {
file, err := fsys.Open(path)
if err != nil {
return err
}
defer file.Close()
if entries, err := a.FromFile(file); err == nil {
result = append(result, entries...)
}
return nil
}); err != nil {
return nil, err
}
return result, nil
}
// FromJSON возвращает запись из JSON
func (c *Assets[T]) FromJSON(r io.Reader) (T, error) {
entry := c.Constructor()
data, err := io.ReadAll(r)
if err != nil {
return entry, err
}
err = jsoniter.Unmarshal(data, &entry)
return entry, err
}
// FromYAML возвращает записи из YAML
func (c *Assets[T]) FromYAML(r io.Reader) (result []T, err error) {
decoder := yaml.NewDecoder(r)
for {
var data interface{}
err = decoder.Decode(&data)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, err
}
json, err := jsoniter.Marshal(data)
if err != nil {
return nil, err
}
entry := c.Constructor()
if err = jsoniter.Unmarshal(json, &entry); err != nil {
return nil, err
}
result = append(result, entry)
}
return result, nil
}