Skip to content
Snippets Groups Projects
Commit c1e10c84 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

WIP: Assets

parent fce43bc1
No related branches found
No related tags found
No related merge requests found
assets.go 0 → 100644
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
}
File moved
File moved
File moved
package test
package perxis
import (
"os"
"testing"
"git.perx.ru/perxis/perxis-go/pkg/assets"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
......@@ -47,8 +46,41 @@ func TestFromFS(t *testing.T) {
i3 := *i1
i3.ID = "item3"
r, err := assets.FromFS[*testEntry](os.DirFS("assets"))
assets := NewAssets[*testEntry]()
r, err := assets.FromFS(os.DirFS("assets/tests"))
require.NoError(t, err)
require.Len(t, r, 3)
assert.ElementsMatch(t, []*testEntry{i1, &i2, &i3}, r)
}
func TestFrom(t *testing.T) {
tr := true
i1 := &testEntry{
ID: "item1",
Enum: State2,
Data: map[string]interface{}{
"obj": map[string]interface{}{"str": "value"},
"arr": []interface{}{"str1", "str2"},
},
Struct: &nested{
Option: &tr,
},
}
i2 := *i1
i2.ID = "item2"
i3 := *i1
i3.ID = "item3"
assets := NewAssets[*testEntry]()
r, err := assets.From(os.DirFS("assets"), "tests")
require.NoError(t, err)
require.Len(t, r, 3)
assert.ElementsMatch(t, []*testEntry{i1, &i2, &i3}, r)
r, err = assets.From(os.DirFS("assets"), "tests/items.yaml")
require.NoError(t, err)
require.Len(t, r, 2)
assert.Equal(t, []*testEntry{i1, &i2}, r)
}
package assets
import "io/fs"
// Для использования без объявления конфигурации:
// package items
// var FromFS = assets.FromFS[*Item]
func FromFS[T any](fsys fs.FS) ([]T, error) {
return New[T]().FromFS(fsys)
}
type Config[T any] struct {
Constructor func() T
}
// Для использования с настройкой:
// package schema
// var FromFS = assets.New[*Schema]().WithConstructor(customFunc).FromFS
func New[T any]() *Config[T] {
return &Config[T]{
Constructor: func() (t T) { return t },
}
}
func (c *Config[T]) WithConstructor(t func() T) *Config[T] {
c.Constructor = t
return c
}
package assets
import (
"io/fs"
"path/filepath"
"git.perx.ru/perxis/perxis-go/pkg/errors"
)
func (c *Config[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 := c.FromJSON(file)
if err != nil {
return nil, errors.Wrapf(err, "file '%s'", stat.Name())
}
return []T{entry}, nil
case ".yaml", ".yml":
entries, err := c.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 (c *Config[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 := c.FromFile(file); err == nil {
result = append(result, entries...)
}
return nil
}); err != nil {
return nil, err
}
return result, nil
}
package assets
import (
"errors"
"io"
jsoniter "github.com/json-iterator/go"
"gopkg.in/yaml.v3"
)
func (c *Config[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
}
func (c *Config[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
}
package clients
import "git.perx.ru/perxis/perxis-go/pkg/assets"
// Client - приложение имеющее доступ к API
type Client struct {
// Внутренний идентификатор клиента внутри системы
......@@ -28,8 +26,6 @@ type Client struct {
RoleID string `json:"role_id" bson:"role_id"`
}
var FromFS = assets.FromFS[*Client]
type OAuth struct {
ClientID string `bson:"client_id,omitempty" json:"client_id,omitempty"` // Идентификатор клиента выданные IdP сервером, используется для идентификации клиента
AuthID string `bson:"auth_id,omitempty" json:"auth_id,omitempty"` // Сервис, который используется для авторизации клиента
......
......@@ -3,6 +3,7 @@ package extension
import (
"context"
"git.perx.ru/perxis/perxis-go"
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/content"
"git.perx.ru/perxis/perxis-go/pkg/errors"
......@@ -41,6 +42,9 @@ var (
ErrNotInstalled = errors.New("not installed")
ErrUnknownExtension = errors.New("unknown extension")
ManifestAssets = perxis.NewAssets[*ExtensionDescriptor]()
ManifestFromFile = ManifestAssets.MustOneFrom
)
// Runnable описывает интерфейс сервиса с запуском и остановкой. Вызывается сервером расширений
......
......@@ -5,7 +5,6 @@ import (
"fmt"
"time"
"git.perx.ru/perxis/perxis-go/pkg/assets"
"git.perx.ru/perxis/perxis-go/pkg/data"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/locales"
......@@ -135,8 +134,6 @@ type Item struct {
Template bool `json:"template" bson:"template,omitempty"`
}
var FromFS = assets.FromFS[*Item]
func NewItem(spaceID, envID, collID, id string, data map[string]interface{}, translations map[string]map[string]interface{}) *Item {
return &Item{
ID: id,
......
......@@ -4,7 +4,6 @@ import (
"context"
"slices"
"git.perx.ru/perxis/perxis-go/pkg/assets"
"git.perx.ru/perxis/perxis-go/pkg/data"
"git.perx.ru/perxis/perxis-go/pkg/environments"
"git.perx.ru/perxis/perxis-go/pkg/permission"
......@@ -36,8 +35,6 @@ type Role struct {
AllowManagement bool `json:"allow_management" bson:"allow_management"`
}
var FromFS = assets.FromFS[*Role]
func (r Role) Clone() *Role {
return &Role{
ID: r.ID,
......
......@@ -4,7 +4,7 @@ import (
"context"
"reflect"
"git.perx.ru/perxis/perxis-go/pkg/assets"
"git.perx.ru/perxis/perxis-go"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/expr"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
......@@ -33,7 +33,9 @@ var (
Validate = validate.Validate
Evaluate = field.Evaluate
FromFS = assets.New[*Schema]().WithConstructor(func() *Schema { return New() }).FromFS
Assets = perxis.NewAssets[*Schema]().WithConstructor(func() *Schema { return New() })
FromFS = Assets.FromFS
FromFile = Assets.FromFile
)
func (s *Schema) Clone(reset bool) *Schema {
......
......@@ -4,6 +4,8 @@ import (
"errors"
"io/fs"
"git.perx.ru/perxis/perxis-go"
"git.perx.ru/perxis/perxis-go/pkg/clients"
"git.perx.ru/perxis/perxis-go/pkg/collections"
"git.perx.ru/perxis/perxis-go/pkg/items"
......@@ -110,11 +112,12 @@ func (cfg *Config) WithClientsOptions(filter func(c *clients.Client) bool, opts
// LoadItems загружает элементы из указанной файловой системы
func (cfg *Config) LoadClients(fsys fs.FS, opt ...ClientsOption) (*Config, error) {
clients, err := clients.FromFS(fsys)
assets := perxis.NewAssets[*clients.Client]()
cls, err := assets.FromFS(fsys)
if err != nil {
return nil, err
}
return cfg.AddClients(clients, opt...), nil
return cfg.AddClients(cls, opt...), nil
}
func (cfg *Config) MustLoadClients(fsys fs.FS, opt ...ClientsOption) *Config {
......@@ -141,11 +144,12 @@ func (c *Config) AddClient(client *clients.Client, opt ...ClientsOption) *Config
// LoadItems загружает элементы из указанной файловой системы
func (cfg *Config) LoadRoles(fsys fs.FS, opt ...RolesOption) (*Config, error) {
roles, err := roles.FromFS(fsys)
assets := perxis.NewAssets[*roles.Role]()
rls, err := assets.FromFS(fsys)
if err != nil {
return nil, err
}
return cfg.AddRoles(roles, opt...), nil
return cfg.AddRoles(rls, opt...), nil
}
func (cfg *Config) MustLoadRoles(fsys fs.FS, opt ...RolesOption) *Config {
......@@ -249,7 +253,8 @@ func (cfg *Config) GetCollectionConfig(id string) *CollectionConfig {
// LoadItems загружает элементы из указанной файловой системы
func (cfg *Config) LoadItems(fsys fs.FS, opt ...ItemsOption) (*Config, error) {
itms, err := items.FromFS(fsys)
assets := perxis.NewAssets[*items.Item]()
itms, err := assets.FromFS(fsys)
if err != nil {
return nil, err
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment