Files
mq/services/loader.go
Oarkflow 86a35f5947 update
2025-09-05 21:43:43 +05:45

533 lines
13 KiB
Go

package services
import (
"errors"
"os"
"path/filepath"
"slices"
"strings"
"github.com/oarkflow/filters"
"github.com/oarkflow/jenv"
"github.com/oarkflow/json"
"github.com/oarkflow/jsonschema"
"github.com/oarkflow/metadata"
"gopkg.in/yaml.v3"
)
var userConfig *UserConfig
var ConfigPath string
func GetUserConfig() *UserConfig {
return userConfig
}
type Loader struct {
path string
configFile string
ParsedPath string
UserConfig *UserConfig
}
var JsonSchemaFunctions = map[string]jsonschema.DefaultFunc{
"now": jsonschema.DefaultNowFunc,
}
func NewLoader(path string, configFiles ...string) *Loader {
var configFile string
if len(configFiles) > 0 {
configFile = configFiles[0]
}
return &Loader{
path: path,
configFile: configFile,
}
}
func (l *Loader) Prefix() string {
return ""
}
func (l *Loader) Load() {
l.ParsedPath = l.prepareConfigPath()
cfg, err := l.loadConfig()
if err != nil {
panic(err)
}
l.UserConfig = cfg
userConfig = cfg // Set the global userConfig variable
}
func (l *Loader) prepareConfigPath() string {
path := l.path
if !filepath.IsAbs(path) {
b, err := filepath.Abs(path)
if err == nil {
path = b
}
}
return path
}
func (l *Loader) loadConfig() (*UserConfig, error) {
cfg := &UserConfig{}
configFile := l.configFile
if configFile != "" {
err := readFile(configFile, cfg)
if err != nil {
return nil, err
}
initializeConfig(cfg)
}
if l.ParsedPath != "" {
err := readPath(l.ParsedPath, cfg)
if err != nil {
return nil, err
}
}
ConfigPath = l.ParsedPath
return cfg, nil
}
func readPath(path string, cfg *UserConfig) error {
err := readSchemas(path, cfg)
if err != nil {
return err
}
readers := []func(string, *UserConfig) error{
readConfig, readCredentials, readConditions, readHandlers,
readModels, readApplicationRules, readCommands, readBackgroundTasks,
readWeb, readRenderer, readApis,
}
for _, read := range readers {
if err := read(path, cfg); err != nil && !os.IsNotExist(err) {
return err
}
}
return nil
}
func readFile(path string, cfg any) error {
content, err := os.ReadFile(path)
if err != nil {
return err
}
return unmarshalConfig(content, path, cfg)
}
func unmarshalConfig(data []byte, path string, cfg any) error {
ext := filepath.Ext(path)
switch ext {
case ".json":
return jenv.UnmarshalJSON(data, cfg)
case ".yaml", ".yml":
return jenv.UnmarshalYAML(data, cfg)
default:
return errors.New("unsupported file format. Only yaml and json supported")
}
}
func initializeConfig(cfg *UserConfig) {
// Initialize Application Rules
for i, applicationRule := range cfg.Policy.ApplicationRules {
if applicationRule.Rule != nil {
applicationRule.BuildRuleFromRequest(cfg.GetCondition)
cfg.Policy.ApplicationRules[i] = applicationRule
}
}
// Initialize Background Handlers
for i, command := range cfg.Policy.BackgroundHandlers {
if command.HandlerKey != "" {
if handler := cfg.GetHandler(command.HandlerKey); handler != nil {
command.Handler = *handler
cfg.Policy.BackgroundHandlers[i] = command
}
}
}
// Initialize API Handlers
for i, api := range cfg.Policy.Web.Apis {
for j, route := range api.Routes {
if route.HandlerKey != "" {
if handler := cfg.GetHandler(route.HandlerKey); handler != nil {
route.Handler = *handler
api.Routes[j] = route
cfg.Policy.Web.Apis[i] = api
}
}
}
}
}
// Helper function to read either JSON or YAML config files
func readConfigFile[T any](path string, appendFn func(T)) error {
var out T
var err error
if content, readErr := os.ReadFile(path + ".json"); readErr == nil {
err = json.Unmarshal(content, &out)
} else if content, readErr := os.ReadFile(path + ".yaml"); readErr == nil {
err = yaml.Unmarshal(content, &out)
} else {
err = readErr // Set to the most recent read error if both files are missing
}
if err != nil {
return err
}
appendFn(out)
return nil
}
func unmarshalContent[T any](content []byte, dataType string, appendFn func(T)) error {
var out T
var err error
switch dataType {
case "json":
err = jenv.UnmarshalJSON(content, &out)
case "yaml":
err = jenv.UnmarshalYAML(content, &out)
}
if err != nil {
return err
}
appendFn(out)
return nil
}
// Helper function to read either JSON or YAML config files
func readArrayConfigFile[T any](path string, appendFn func(T)) error {
var err error
var data []json.RawMessage
var dataType string
if content, readErr := os.ReadFile(path + ".json"); readErr == nil {
dataType = "json"
err = json.Unmarshal(content, &data)
} else if content, readErr := os.ReadFile(path + ".yaml"); readErr == nil {
dataType = "yaml"
err = yaml.Unmarshal(content, &data)
} else {
err = readErr // Set to the most recent read error if both files are missing
}
if err != nil {
return err
}
for _, d := range data {
err = unmarshalContent(d, dataType, appendFn)
if err != nil {
return err
}
}
return nil
}
// Sample read function with YAML/JSON support
func readConditions(path string, cfg *UserConfig) error {
path = filepath.Join(path, "policies", "conditions")
return readConfigFile(path, func(data []*filters.Filter) {
cfg.Policy.Conditions = append(cfg.Policy.Conditions, data...)
})
}
func readSchemas(path string, cfg *UserConfig) error {
path = filepath.Join(path, "policies", "schemas")
entries, err := os.ReadDir(path)
if err != nil {
return err
}
compiler := jsonschema.NewCompiler()
for name, fn := range JsonSchemaFunctions {
compiler.RegisterDefaultFunc(name, fn)
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(path, entry.Name())
content, err := os.ReadFile(file)
if err != nil {
return err
}
schema, err := compiler.Compile(content)
if err != nil {
return err
}
if schema == nil {
return errors.New("compiled schema is nil")
}
cfg.Policy.Schemas = append(cfg.Policy.Schemas, Schema{File: entry.Name(), Instance: schema})
}
}
return nil
}
func readApplicationRules(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "application_rules")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var applicationRule *filters.ApplicationRule
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &applicationRule)
if err != nil {
return err
}
if applicationRule.Rule != nil {
applicationRule.BuildRuleFromRequest(cfg.GetCondition)
cfg.Policy.ApplicationRules = append(cfg.Policy.ApplicationRules, applicationRule)
}
}
}
return nil
}
func readCommands(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "commands")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var command GenericCommand
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &command)
if err != nil {
return err
}
if command.HandlerKey != "" {
if handler := cfg.GetHandler(command.HandlerKey); handler != nil {
command.Handler = *handler
}
}
cfg.Policy.Commands = append(cfg.Policy.Commands, &command)
}
}
return nil
}
func readDatabases(path string, cfg *UserConfig) error {
path = filepath.Join(path, "credentials", "databases")
return readArrayConfigFile(path, func(data metadata.Config) {
cfg.Core.Credentials.Databases = append(cfg.Core.Credentials.Databases, data)
})
}
func readStorages(path string, cfg *UserConfig) error {
path = filepath.Join(path, "credentials", "storages")
return readArrayConfigFile(path, func(data Storage) {
cfg.Core.Credentials.Storages = append(cfg.Core.Credentials.Storages, data)
})
}
func readCaches(path string, cfg *UserConfig) error {
path = filepath.Join(path, "credentials", "caches")
return readArrayConfigFile(path, func(data Cache) {
cfg.Core.Credentials.Caches = append(cfg.Core.Credentials.Caches, data)
})
}
func readCredentials(path string, cfg *UserConfig) error {
if err := readDatabases(path, cfg); err != nil && !os.IsNotExist(err) {
return err
}
if err := readStorages(path, cfg); err != nil && !os.IsNotExist(err) {
return err
}
err := readCaches(path, cfg)
if !os.IsNotExist(err) {
return err
}
return nil
}
func readConfig(path string, cfg *UserConfig) error {
path = filepath.Join(path, "conf")
return readConfigFile(path, func(coreData Core) {
if cfg.Core.Enums == nil {
cfg.Core.Enums = make(map[string]map[string]any)
}
for key, val := range coreData.Enums {
cfg.Core.Enums[key] = val
}
if cfg.Core.Consts == nil {
cfg.Core.Consts = make(map[string]any)
}
for key, val := range coreData.Consts {
cfg.Core.Consts[key] = val
}
})
}
func readModels(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "models")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var model Model
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &model)
if err != nil {
return err
}
fileName := strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name()))
if model.Name == "" {
model.Name = fileName
}
if cfg.GetModel(model.Name) == nil {
cfg.Policy.Models = append(cfg.Policy.Models, model)
}
}
}
return nil
}
func readHandlers(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "handlers")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var handler Handler
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &handler)
if err != nil {
return err
}
cfg.Policy.Handlers = append(cfg.Policy.Handlers, handler)
}
}
return nil
}
func readApis(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "apis")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
compiler := jsonschema.NewCompiler()
for name, fn := range JsonSchemaFunctions {
compiler.RegisterDefaultFunc(name, fn)
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var api Api
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &api)
if err != nil {
return err
}
for i, route := range api.Routes {
if len(route.Schema) > 0 {
schema, err := compiler.Compile(route.Schema)
if err != nil {
return err
}
if schema == nil {
return errors.New("compiled schema is nil for route: " + route.Uri)
}
route.schema = schema
} else if route.SchemaFile != "" {
schema := cfg.GetSchemaInstance(route.SchemaFile)
if schema == nil {
return errors.New("schema not found for route: " + route.Uri + ", file: " + route.SchemaFile)
}
route.schema = schema.Instance
}
if route.HandlerKey != "" {
if handler := cfg.GetHandler(route.HandlerKey); handler != nil {
route.Handler = *handler
api.Routes[i] = route
}
}
}
cfg.Policy.Web.Apis = append(cfg.Policy.Web.Apis, api)
}
}
return nil
}
func readBackgroundTasks(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "background")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var background BackgroundHandler
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &background)
if err != nil {
return err
}
cfg.Policy.BackgroundHandlers = append(cfg.Policy.BackgroundHandlers, &background)
}
}
return nil
}
func readRenderer(path string, cfg *UserConfig) error {
modelsPath := filepath.Join(path, "policies", "renderer")
entries, err := os.ReadDir(modelsPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) {
file := filepath.Join(modelsPath, entry.Name())
var background RenderConfig
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = unmarshalConfig(content, file, &background)
if err != nil {
return err
}
cfg.Policy.Web.Render = append(cfg.Policy.Web.Render, &background)
}
}
return nil
}
func readWeb(path string, cfg *UserConfig) error {
path = filepath.Join(path, "policies", "web")
return readConfigFile(path, func(data Web) {
cfg.Policy.Web = data
})
}
func isSupportedExt(ext string) bool {
return slices.Contains([]string{".json", ".yaml"}, ext)
}