diff --git a/auth/service_provider.go b/auth/service_provider.go index 19763ce8a..2f2d41155 100644 --- a/auth/service_provider.go +++ b/auth/service_provider.go @@ -32,7 +32,7 @@ func (database *ServiceProvider) Boot(app foundation.Application) { } func (database *ServiceProvider) registerCommands(app foundation.Application) { - app.MakeArtisan().Register([]contractconsole.Command{ + app.Commands([]contractconsole.Command{ console.NewJwtSecretCommand(app.MakeConfig()), console.NewPolicyMakeCommand(), }) diff --git a/cache/service_provider.go b/cache/service_provider.go index f60ca980f..7ed8bfd12 100644 --- a/cache/service_provider.go +++ b/cache/service_provider.go @@ -26,7 +26,7 @@ func (database *ServiceProvider) Boot(app foundation.Application) { } func (database *ServiceProvider) registerCommands(app foundation.Application) { - app.MakeArtisan().Register([]contractsconsole.Command{ + app.Commands([]contractsconsole.Command{ console.NewClearCommand(app.MakeCache()), }) } diff --git a/console/application.go b/console/application.go index 34193084a..a5a858836 100644 --- a/console/application.go +++ b/console/application.go @@ -47,19 +47,31 @@ func (c *Application) Register(commands []console.Command) { // Call Run an Artisan console command by name. func (c *Application) Call(command string) { + if len(os.Args) == 0 { + return + } + commands := []string{os.Args[0]} + if c.isArtisan { commands = append(commands, "artisan") } + c.Run(append(commands, strings.Split(command, " ")...), false) } // CallAndExit Run an Artisan console command by name and exit. func (c *Application) CallAndExit(command string) { + if len(os.Args) == 0 { + return + } + commands := []string{os.Args[0]} + if c.isArtisan { commands = append(commands, "artisan") } + c.Run(append(commands, strings.Split(command, " ")...), true) } diff --git a/console/progress_bar.go b/console/progress_bar.go index bd6a6b5d6..75066dd8d 100644 --- a/console/progress_bar.go +++ b/console/progress_bar.go @@ -19,10 +19,16 @@ func NewProgressBar(total int) *ProgressBar { } func (r *ProgressBar) Advance(step ...int) { + var instance *pterm.ProgressbarPrinter + if len(step) > 0 { - r.instance = r.instance.Add(step[0]) + instance = r.instance.Add(step[0]) } else { - r.instance = r.instance.Increment() + instance = r.instance.Increment() + } + + if instance != nil { + r.instance = instance } } diff --git a/console/service_provider.go b/console/service_provider.go index 4c8eb0eb0..c89aaebc3 100644 --- a/console/service_provider.go +++ b/console/service_provider.go @@ -4,6 +4,7 @@ import ( "github.com/goravel/framework/console/console" consolecontract "github.com/goravel/framework/contracts/console" "github.com/goravel/framework/contracts/foundation" + "github.com/goravel/framework/support/color" ) const Binding = "goravel.console" @@ -25,12 +26,22 @@ func (receiver *ServiceProvider) Boot(app foundation.Application) { } func (receiver *ServiceProvider) registerCommands(app foundation.Application) { - artisan := app.MakeArtisan() - config := app.MakeConfig() - artisan.Register([]consolecontract.Command{ - console.NewListCommand(artisan), - console.NewKeyGenerateCommand(config), + artisanFacade := app.MakeArtisan() + if artisanFacade == nil { + color.Yellow().Println("Warning: Artisan Facade is not initialized. Skipping command registration.") + return + } + + configFacade := app.MakeConfig() + if configFacade == nil { + color.Yellow().Println("Warning: Config Facade is not initialized. Skipping certain command registrations.") + return + } + + artisanFacade.Register([]consolecontract.Command{ + console.NewListCommand(artisanFacade), + console.NewKeyGenerateCommand(configFacade), console.NewMakeCommand(), - console.NewBuildCommand(config), + console.NewBuildCommand(configFacade), }) } diff --git a/crypt/aes.go b/crypt/aes.go index bf7d15dc7..ef82b83e0 100644 --- a/crypt/aes.go +++ b/crypt/aes.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "encoding/base64" "errors" + "fmt" "io" "github.com/goravel/framework/contracts/config" @@ -20,24 +21,27 @@ type AES struct { } // NewAES returns a new AES hasher. -func NewAES(config config.Config, json foundation.Json) *AES { +func NewAES(config config.Config, json foundation.Json) (*AES, error) { key := config.GetString("app.key") // Don't use AES in artisan when the key is empty. if support.Env == support.EnvArtisan && len(key) == 0 { - return nil + return nil, ErrAppKeyNotSetInArtisan } + keyLength := len(key) // check key length before using it - if len(key) != 16 && len(key) != 24 && len(key) != 32 { - color.Red().Println("[Crypt] Empty or invalid APP_KEY, please reset it.\nExample command:\ngo run . artisan key:generate") - return nil + if keyLength != 16 && keyLength != 24 && keyLength != 32 { + color.Red().Printf("[Crypt] Invalid APP_KEY length. Expected 16, 24, or 32 bytes, but got %d bytes.\n", len(key)) + color.Red().Println("Please reset it using the following command:\ngo run . artisan key:generate") + return nil, fmt.Errorf("%w: %d bytes", ErrInvalidAppKeyLength, keyLength) } + keyBytes := []byte(key) return &AES{ key: keyBytes, json: json, - } + }, nil } // EncryptString encrypts the given string, and returns the iv and ciphertext as base64 encoded strings. diff --git a/crypt/aes_test.go b/crypt/aes_test.go index 697c6e2d1..beb61e944 100644 --- a/crypt/aes_test.go +++ b/crypt/aes_test.go @@ -3,6 +3,7 @@ package crypt import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/goravel/framework/foundation/json" @@ -17,8 +18,12 @@ type AesTestSuite struct { func TestAesTestSuite(t *testing.T) { mockConfig := &configmock.Config{} mockConfig.On("GetString", "app.key").Return("11111111111111111111111111111111").Once() + aes, err := NewAES(mockConfig, json.NewJson()) + + assert.NoError(t, err) + suite.Run(t, &AesTestSuite{ - aes: NewAES(mockConfig, json.NewJson()), + aes: aes, }) mockConfig.AssertExpectations(t) } @@ -61,7 +66,11 @@ func (s *AesTestSuite) TestDecryptString() { func Benchmark_EncryptString(b *testing.B) { mockConfig := &configmock.Config{} mockConfig.On("GetString", "app.key").Return("11111111111111111111111111111111").Once() - aes := NewAES(mockConfig, json.NewJson()) + aes, err := NewAES(mockConfig, json.NewJson()) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() for i := 0; i < b.N; i++ { @@ -75,7 +84,11 @@ func Benchmark_EncryptString(b *testing.B) { func Benchmark_DecryptString(b *testing.B) { mockConfig := &configmock.Config{} mockConfig.On("GetString", "app.key").Return("11111111111111111111111111111111").Once() - aes := NewAES(mockConfig, json.NewJson()) + aes, err := NewAES(mockConfig, json.NewJson()) + if err != nil { + b.Fatal(err) + } + payload, err := aes.EncryptString("Goravel") if err != nil { b.Error(err) diff --git a/crypt/errors.go b/crypt/errors.go new file mode 100644 index 000000000..c2b4c032e --- /dev/null +++ b/crypt/errors.go @@ -0,0 +1,12 @@ +package crypt + +import ( + "errors" +) + +var ( + ErrConfigNotSet = errors.New("config must not be nil") + ErrJsonParserNotSet = errors.New("JSON parser must not be nil") + ErrAppKeyNotSetInArtisan = errors.New("APP_KEY is required in artisan environment") + ErrInvalidAppKeyLength = errors.New("invalid APP_KEY length") +) diff --git a/crypt/service_provider.go b/crypt/service_provider.go index c79ef4b50..8b00d018b 100644 --- a/crypt/service_provider.go +++ b/crypt/service_provider.go @@ -11,7 +11,17 @@ type ServiceProvider struct { func (crypt *ServiceProvider) Register(app foundation.Application) { app.Singleton(Binding, func(app foundation.Application) (any, error) { - return NewAES(app.MakeConfig(), app.GetJson()), nil + c := app.MakeConfig() + if c == nil { + return nil, ErrConfigNotSet + } + + j := app.GetJson() + if j == nil { + return nil, ErrJsonParserNotSet + } + + return NewAES(c, j) }) } diff --git a/database/gorm/dialector_test.go b/database/gorm/dialector_test.go index 87dc71a87..f39ec21db 100644 --- a/database/gorm/dialector_test.go +++ b/database/gorm/dialector_test.go @@ -46,11 +46,12 @@ func (s *DialectorTestSuite) TestMysql() { s.mockConfig.On("GetString", "database.connections.mysql.loc"). Return("Local").Once() dialectors, err := dialector.Make([]databasecontract.Config{s.config}) + s.Nil(err) + s.NotEmpty(dialectors) s.Equal(mysql.New(mysql.Config{ DSN: fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=%s&multiStatements=true", s.config.Username, s.config.Password, s.config.Host, s.config.Port, s.config.Database, "utf8mb4", true, "Local"), }), dialectors[0]) - s.Nil(err) } func (s *DialectorTestSuite) TestPostgres() { @@ -62,11 +63,12 @@ func (s *DialectorTestSuite) TestPostgres() { s.mockConfig.On("GetString", "database.connections.postgres.timezone"). Return("UTC").Once() dialectors, err := dialector.Make([]databasecontract.Config{s.config}) + s.Nil(err) + s.NotEmpty(dialectors) s.Equal(postgres.New(postgres.Config{ DSN: fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s&timezone=%s", s.config.Username, s.config.Password, s.config.Host, s.config.Port, s.config.Database, "disable", "UTC"), }), dialectors[0]) - s.Nil(err) } func (s *DialectorTestSuite) TestSqlite() { @@ -74,8 +76,9 @@ func (s *DialectorTestSuite) TestSqlite() { s.mockConfig.On("GetString", "database.connections.sqlite.driver"). Return(orm.DriverSqlite.String()).Once() dialectors, err := dialector.Make([]databasecontract.Config{s.config}) - s.Equal(sqlite.Open(fmt.Sprintf("%s?multi_stmts=true", s.config.Database)), dialectors[0]) s.Nil(err) + s.NotEmpty(dialectors) + s.Equal(sqlite.Open(fmt.Sprintf("%s?multi_stmts=true", s.config.Database)), dialectors[0]) } func (s *DialectorTestSuite) TestSqlserver() { @@ -85,9 +88,10 @@ func (s *DialectorTestSuite) TestSqlserver() { s.mockConfig.On("GetString", "database.connections.sqlserver.charset"). Return("utf8mb4").Once() dialectors, err := dialector.Make([]databasecontract.Config{s.config}) + s.Nil(err) + s.NotEmpty(dialectors) s.Equal(sqlserver.New(sqlserver.Config{ DSN: fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s&charset=%s&MultipleActiveResultSets=true", s.config.Username, s.config.Password, s.config.Host, s.config.Port, s.config.Database, "utf8mb4"), }), dialectors[0]) - s.Nil(err) } diff --git a/database/gorm/gorm.go b/database/gorm/gorm.go index 076b086df..d8a923851 100644 --- a/database/gorm/gorm.go +++ b/database/gorm/gorm.go @@ -52,6 +52,10 @@ func (r *GormImpl) Make() (*gormio.DB, error) { return nil, fmt.Errorf("init gorm dialector error: %v", err) } + if len(writeDialectors) == 0 { + return nil, errors.New("no write dialectors found") + } + if err := r.init(writeDialectors[0]); err != nil { return nil, err } diff --git a/database/service_provider.go b/database/service_provider.go index 7fe997aa2..5d7a361a0 100644 --- a/database/service_provider.go +++ b/database/service_provider.go @@ -51,21 +51,22 @@ func (database *ServiceProvider) Boot(app foundation.Application) { } func (database *ServiceProvider) registerCommands(app foundation.Application) { - config := app.MakeConfig() - seeder := app.MakeSeeder() - artisan := app.MakeArtisan() - app.MakeArtisan().Register([]consolecontract.Command{ - console.NewMigrateMakeCommand(config), - console.NewMigrateCommand(config), - console.NewMigrateRollbackCommand(config), - console.NewMigrateResetCommand(config), - console.NewMigrateRefreshCommand(config, artisan), - console.NewMigrateFreshCommand(config, artisan), - console.NewMigrateStatusCommand(config), - console.NewModelMakeCommand(), - console.NewObserverMakeCommand(), - console.NewSeedCommand(config, seeder), - console.NewSeederMakeCommand(), - console.NewFactoryMakeCommand(), - }) + if artisanFacade := app.MakeArtisan(); artisanFacade != nil { + config := app.MakeConfig() + seeder := app.MakeSeeder() + artisanFacade.Register([]consolecontract.Command{ + console.NewMigrateMakeCommand(config), + console.NewMigrateCommand(config), + console.NewMigrateRollbackCommand(config), + console.NewMigrateResetCommand(config), + console.NewMigrateRefreshCommand(config, artisanFacade), + console.NewMigrateFreshCommand(config, artisanFacade), + console.NewMigrateStatusCommand(config), + console.NewModelMakeCommand(), + console.NewObserverMakeCommand(), + console.NewSeedCommand(config, seeder), + console.NewSeederMakeCommand(), + console.NewFactoryMakeCommand(), + }) + } } diff --git a/event/service_provider.go b/event/service_provider.go index ffaa25c24..f4346a2c4 100644 --- a/event/service_provider.go +++ b/event/service_provider.go @@ -22,7 +22,7 @@ func (receiver *ServiceProvider) Boot(app foundation.Application) { } func (receiver *ServiceProvider) registerCommands(app foundation.Application) { - app.MakeArtisan().Register([]console.Command{ + app.Commands([]console.Command{ &eventConsole.EventMakeCommand{}, &eventConsole.ListenerMakeCommand{}, }) diff --git a/facades/app.go b/facades/app.go index a02fbf533..429b6c6fc 100644 --- a/facades/app.go +++ b/facades/app.go @@ -6,5 +6,9 @@ import ( ) func App() foundationcontract.Application { - return foundation.App + if foundation.App == nil { + panic(ErrApplicationNotSet) + } else { + return foundation.App + } } diff --git a/facades/errors.go b/facades/errors.go new file mode 100644 index 000000000..7971b834f --- /dev/null +++ b/facades/errors.go @@ -0,0 +1,7 @@ +package facades + +import "errors" + +var ( + ErrApplicationNotSet = errors.New("application instance not initialized") +) diff --git a/filesystem/errors.go b/filesystem/errors.go new file mode 100644 index 000000000..4751296a4 --- /dev/null +++ b/filesystem/errors.go @@ -0,0 +1,7 @@ +package filesystem + +import "errors" + +var ( + ErrStorageFacadeNotSet = errors.New("storage facade not set") +) diff --git a/filesystem/file.go b/filesystem/file.go index 32375d107..324e90a60 100644 --- a/filesystem/file.go +++ b/filesystem/file.go @@ -25,6 +25,10 @@ type File struct { } func NewFile(file string) (*File, error) { + if ConfigFacade == nil { + return nil, errors.New("config facade not set") + } + if !supportfile.Exists(file) { return nil, errors.New("file doesn't exist") } @@ -39,6 +43,10 @@ func NewFile(file string) (*File, error) { } func NewFileFromRequest(fileHeader *multipart.FileHeader) (*File, error) { + if ConfigFacade == nil { + return nil, errors.New("config facade not set") + } + src, err := fileHeader.Open() if err != nil { return nil, err @@ -111,6 +119,10 @@ func (f *File) HashName(path ...string) string { } func (f *File) LastModified() (time.Time, error) { + if f.config == nil { + return time.Time{}, errors.New("config facade is not initialized") + } + return supportfile.LastModified(f.path, f.config.GetString("app.timezone")) } @@ -123,9 +135,25 @@ func (f *File) Size() (int64, error) { } func (f *File) Store(path string) (string, error) { + if err := f.validateStorageFacade(); err != nil { + return "", err + } + return f.storage.Disk(f.disk).PutFile(path, f) } func (f *File) StoreAs(path string, name string) (string, error) { + if err := f.validateStorageFacade(); err != nil { + return "", err + } + return f.storage.Disk(f.disk).PutFileAs(path, f, name) } + +func (f *File) validateStorageFacade() error { + if f.storage == nil { + return ErrStorageFacadeNotSet + } + + return nil +} diff --git a/filesystem/file_test.go b/filesystem/file_test.go index cfdd255e1..82735f66a 100644 --- a/filesystem/file_test.go +++ b/filesystem/file_test.go @@ -31,16 +31,17 @@ func (s *FileTestSuite) SetupTest() { s.mockConfig.On("GetString", "filesystems.default").Return("local").Once() ConfigFacade = s.mockConfig - var err error - s.file, err = NewFile("./file.go") + f, err := NewFile("./file.go") s.Nil(err) - s.NotNil(s.file) + s.NotNil(f) + + s.file = f } func (s *FileTestSuite) TestNewFile_Error() { - file, err := NewFile("./file1.go") + f, err := NewFile("./file1.go") s.EqualError(err, "file doesn't exist") - s.Nil(file) + s.Nil(f) } func (s *FileTestSuite) TestGetClientOriginalName() { @@ -70,6 +71,7 @@ func TestNewFileFromRequest(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test.txt") + assert.NotNil(t, w) if assert.NoError(t, err) { _, err = w.Write([]byte("test")) assert.NoError(t, err) diff --git a/filesystem/local_test.go b/filesystem/local_test.go index 13bf57d11..cb9881215 100644 --- a/filesystem/local_test.go +++ b/filesystem/local_test.go @@ -46,13 +46,17 @@ func (s *LocalTestSuite) SetupTest() { s.mockConfig.On("GetString", "filesystems.disks.local.url").Return("https://goravel.dev").Once() ConfigFacade = s.mockConfig - s.local, err = NewLocal(s.mockConfig, "local") + l, err := NewLocal(s.mockConfig, "local") s.Nil(err) - s.NotNil(s.local) + s.NotNil(l) - s.file, err = NewFile("./file.go") + s.local = l + + f, err := NewFile("./file.go") s.Nil(err) - s.NotNil(s.file) + s.NotNil(f) + + s.file = f s.mockConfig.AssertExpectations(s.T()) } diff --git a/foundation/application.go b/foundation/application.go index 0cc8b7b76..758d5434a 100644 --- a/foundation/application.go +++ b/foundation/application.go @@ -14,6 +14,7 @@ import ( "github.com/goravel/framework/foundation/json" "github.com/goravel/framework/support" "github.com/goravel/framework/support/carbon" + "github.com/goravel/framework/support/color" ) var ( @@ -90,7 +91,12 @@ func (app *Application) StoragePath(path ...string) string { } func (app *Application) LangPath(path ...string) string { - path = append([]string{app.MakeConfig().GetString("app.lang_path", "lang")}, path...) + defaultPath := "lang" + if configFacade := app.MakeConfig(); configFacade != nil { + defaultPath = configFacade.GetString("app.lang_path", defaultPath) + } + + path = append([]string{defaultPath}, path...) return filepath.Join(path...) } @@ -121,11 +127,23 @@ func (app *Application) Version() string { } func (app *Application) CurrentLocale(ctx context.Context) string { - return app.MakeLang(ctx).CurrentLocale() + lang := app.MakeLang(ctx) + if lang == nil { + color.Red().Println("Error: Lang facade not initialized.") + return "" + } + + return lang.CurrentLocale() } func (app *Application) SetLocale(ctx context.Context, locale string) context.Context { - return app.MakeLang(ctx).SetLocale(locale) + lang := app.MakeLang(ctx) + if lang == nil { + color.Red().Println("Error: Lang facade not initialized.") + return ctx + } + + return lang.SetLocale(locale) } func (app *Application) SetJson(j foundation.Json) { @@ -160,7 +178,13 @@ func (app *Application) addPublishGroup(group string, paths map[string]string) { // bootArtisan Boot artisan command. func (app *Application) bootArtisan() { - app.MakeArtisan().Run(os.Args, true) + artisanFacade := app.MakeArtisan() + if artisanFacade == nil { + color.Yellow().Println("Warning: Artisan Facade is not initialized. Skipping artisan command execution.") + return + } + + artisanFacade.Run(os.Args, true) } // getBaseServiceProviders Get base service providers. @@ -172,7 +196,18 @@ func (app *Application) getBaseServiceProviders() []foundation.ServiceProvider { // getConfiguredServiceProviders Get configured service providers. func (app *Application) getConfiguredServiceProviders() []foundation.ServiceProvider { - return app.MakeConfig().Get("app.providers").([]foundation.ServiceProvider) + configFacade := app.MakeConfig() + if configFacade == nil { + color.Yellow().Println("Warning: config facade is not initialized. Skipping registering service providers.") + return []foundation.ServiceProvider{} + } + + providers, ok := configFacade.Get("app.providers").([]foundation.ServiceProvider) + if !ok { + color.Yellow().Println("Warning: providers configuration is not of type []foundation.ServiceProvider. Skipping registering service providers.") + return []foundation.ServiceProvider{} + } + return providers } // registerBaseServiceProviders Register base service providers. @@ -210,11 +245,25 @@ func (app *Application) bootServiceProviders(serviceProviders []foundation.Servi } func (app *Application) registerCommands(commands []contractsconsole.Command) { - app.MakeArtisan().Register(commands) + artisanFacade := app.MakeArtisan() + if artisanFacade == nil { + color.Yellow().Println("Warning: Artisan Facade is not initialized. Skipping command registration.") + return + } + + artisanFacade.Register(commands) } func (app *Application) setTimezone() { - carbon.SetTimezone(app.MakeConfig().GetString("app.timezone", carbon.UTC)) + configFacade := app.MakeConfig() + if configFacade == nil { + color.Yellow().Println("Warning: config facade is not initialized. Using default timezone UTC.") + carbon.SetTimezone(carbon.UTC) + return + } + + carbon.SetTimezone(configFacade.GetString("app.timezone", carbon.UTC)) + } func setEnv() { diff --git a/foundation/application_test.go b/foundation/application_test.go index 58e37c1b5..0aee16d1c 100644 --- a/foundation/application_test.go +++ b/foundation/application_test.go @@ -18,6 +18,7 @@ import ( "github.com/goravel/framework/database" "github.com/goravel/framework/event" "github.com/goravel/framework/filesystem" + "github.com/goravel/framework/foundation/json" "github.com/goravel/framework/grpc" "github.com/goravel/framework/hash" "github.com/goravel/framework/http" @@ -203,6 +204,7 @@ func (s *ApplicationTestSuite) TestMakeCrypt() { s.app.Singleton(frameworkconfig.Binding, func(app foundation.Application) (any, error) { return mockConfig, nil }) + s.app.SetJson(json.NewJson()) serviceProvider := &crypt.ServiceProvider{} serviceProvider.Register(s.app) @@ -402,6 +404,7 @@ func (s *ApplicationTestSuite) TestMakeSession() { s.app.Singleton(frameworkconfig.Binding, func(app foundation.Application) (any, error) { return mockConfig, nil }) + s.app.SetJson(json.NewJson()) serviceProvider := &frameworksession.ServiceProvider{} // error diff --git a/http/limit/store.go b/http/limit/store.go index 81fb47b95..c3ed9a431 100644 --- a/http/limit/store.go +++ b/http/limit/store.go @@ -2,9 +2,11 @@ package limit import ( "context" + "errors" "sync" "time" + "github.com/goravel/framework/contracts/cache" "github.com/goravel/framework/http" "github.com/goravel/framework/support/carbon" ) @@ -56,6 +58,7 @@ type Store interface { type store struct { tokens uint64 interval time.Duration + cache cache.Cache } func NewStore(tokens uint64, interval time.Duration) (Store, error) { @@ -67,9 +70,14 @@ func NewStore(tokens uint64, interval time.Duration) (Store, error) { interval = 1 * time.Second } + if http.CacheFacade == nil { + return nil, errors.New("cache facade is not initialized") + } + s := &store{ tokens: tokens, interval: interval, + cache: http.CacheFacade, } return s, nil @@ -79,13 +87,13 @@ func NewStore(tokens uint64, interval time.Duration) (Store, error) { // successful, it returns true, otherwise false. It also returns the configured // limit, remaining tokens, and reset time. func (s *store) Take(_ context.Context, key string) (uint64, uint64, uint64, bool, error) { - b, ok := http.CacheFacade.Get(key).(*Bucket) + b, ok := s.cache.Get(key).(*Bucket) if ok { return b.take() } nb := NewBucket(s.tokens, s.interval) - if err := http.CacheFacade.Put(key, nb, s.interval); err != nil { + if err := s.cache.Put(key, nb, s.interval); err != nil { return 0, 0, 0, false, err } @@ -94,7 +102,7 @@ func (s *store) Take(_ context.Context, key string) (uint64, uint64, uint64, boo // Get retrieves the information about the key, if any exists. func (s *store) Get(_ context.Context, key string) (uint64, uint64, error) { - b, ok := http.CacheFacade.Get(key).(*Bucket) + b, ok := s.cache.Get(key).(*Bucket) if ok { return b.get() } @@ -105,12 +113,12 @@ func (s *store) Get(_ context.Context, key string) (uint64, uint64, error) { // Set configures the Bucket-specific tokens and interval. func (s *store) Set(_ context.Context, key string, tokens uint64, interval time.Duration) error { b := NewBucket(tokens, interval) - return http.CacheFacade.Put(key, b, interval) + return s.cache.Put(key, b, interval) } // Burst adds the provided value to the Bucket's currently available tokens. func (s *store) Burst(_ context.Context, key string, tokens uint64) error { - b, ok := http.CacheFacade.Get(key).(*Bucket) + b, ok := s.cache.Get(key).(*Bucket) if ok { b.lock.Lock() b.availableTokens = b.availableTokens + tokens @@ -119,7 +127,7 @@ func (s *store) Burst(_ context.Context, key string, tokens uint64) error { } nb := NewBucket(s.tokens+tokens, s.interval) - return http.CacheFacade.Put(key, nb, s.interval) + return s.cache.Put(key, nb, s.interval) } // Bucket is an internal wrapper around a taker. diff --git a/http/service_provider.go b/http/service_provider.go index 5b99c147f..3d9650d48 100644 --- a/http/service_provider.go +++ b/http/service_provider.go @@ -35,7 +35,7 @@ func (http *ServiceProvider) Boot(app foundation.Application) { } func (http *ServiceProvider) registerCommands(app foundation.Application) { - app.MakeArtisan().Register([]consolecontract.Command{ + app.Commands([]consolecontract.Command{ &console.RequestMakeCommand{}, &console.ControllerMakeCommand{}, &console.MiddlewareMakeCommand{}, diff --git a/log/logrus_writer_test.go b/log/logrus_writer_test.go index 1fdc8d97b..685d35f26 100644 --- a/log/logrus_writer_test.go +++ b/log/logrus_writer_test.go @@ -404,8 +404,14 @@ func TestLogrusWithCustomLogger(t *testing.T) { filename := "custom.log" logger := NewApplication(mockConfig, json.NewJson()) - logger.Channel("customLogger"). - WithTrace(). + + assert.NotNil(t, logger) + + channel := logger.Channel("customLogger") + + assert.NotNil(t, channel) + + channel.WithTrace(). With(map[string]any{"filename": filename}). User(map[string]any{"name": "kkumar-gcc"}). Owner("team@goravel.dev"). @@ -422,6 +428,8 @@ func TestLogrus_Fatal(t *testing.T) { mockDriverConfig(mockConfig) log := NewApplication(mockConfig, json.NewJson()) + assert.NotNil(t, log) + if os.Getenv("FATAL") == "1" { log.Fatal("Goravel") return @@ -442,6 +450,8 @@ func TestLogrus_Fatalf(t *testing.T) { mockDriverConfig(mockConfig) log := NewApplication(mockConfig, json.NewJson()) + assert.NotNil(t, log) + if os.Getenv("FATAL") == "1" { log.Fatalf("Goravel") return diff --git a/mail/job.go b/mail/job.go index 90a576bad..6ea6160fd 100644 --- a/mail/job.go +++ b/mail/job.go @@ -1,6 +1,8 @@ package mail import ( + "fmt" + "github.com/goravel/framework/contracts/config" ) @@ -21,5 +23,49 @@ func (r *SendMailJob) Signature() string { // Handle Execute the job. func (r *SendMailJob) Handle(args ...any) error { - return SendMail(r.config, args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].([]string), args[5].([]string), args[6].([]string), args[7].([]string)) + if len(args) != 8 { + return fmt.Errorf("expected 8 arguments, got %d", len(args)) + } + + from, ok := args[0].(string) + if !ok { + return fmt.Errorf("FROM should be of type string") + } + + subject, ok := args[1].(string) + if !ok { + return fmt.Errorf("SUBJECT should be of type string") + } + + body, ok := args[2].(string) + if !ok { + return fmt.Errorf("BODY should be of type string") + } + + recipient, ok := args[3].(string) + if !ok { + return fmt.Errorf("RECIPIENT should be of type string") + } + + cc, ok := args[4].([]string) + if !ok { + return fmt.Errorf("CC should be of type []string") + } + + bcc, ok := args[5].([]string) + if !ok { + return fmt.Errorf("BCC should be of type []string") + } + + replyTo, ok := args[6].([]string) + if !ok { + return fmt.Errorf("ReplyTo should be of type []string") + } + + attachments, ok := args[7].([]string) + if !ok { + return fmt.Errorf("ATTACHMENTS should be of type []string") + } + + return SendMail(r.config, from, subject, body, recipient, cc, bcc, replyTo, attachments) } diff --git a/mail/service_provider.go b/mail/service_provider.go index 6626da25c..5df27ff65 100644 --- a/mail/service_provider.go +++ b/mail/service_provider.go @@ -5,6 +5,7 @@ import ( "github.com/goravel/framework/contracts/foundation" "github.com/goravel/framework/contracts/queue" "github.com/goravel/framework/mail/console" + "github.com/goravel/framework/support/color" ) const Binding = "goravel.mail" @@ -19,11 +20,27 @@ func (route *ServiceProvider) Register(app foundation.Application) { } func (route *ServiceProvider) Boot(app foundation.Application) { - app.MakeQueue().Register([]queue.Job{ - NewSendMailJob(app.MakeConfig()), + app.Commands([]consolecontract.Command{ + console.NewMailMakeCommand(), }) - app.MakeArtisan().Register([]consolecontract.Command{ - console.NewMailMakeCommand(), + route.registerJobs(app) +} + +func (route *ServiceProvider) registerJobs(app foundation.Application) { + queueFacade := app.MakeQueue() + if queueFacade == nil { + color.Yellow().Println("Warning: Queue Facade is not initialized. Skipping job registration.") + return + } + + configFacade := app.MakeConfig() + if configFacade == nil { + color.Yellow().Println("Warning: Config Facade is not initialized. Skipping job registration.") + return + } + + queueFacade.Register([]queue.Job{ + NewSendMailJob(configFacade), }) } diff --git a/queue/service_provider.go b/queue/service_provider.go index dfebea0a1..5325cc74b 100644 --- a/queue/service_provider.go +++ b/queue/service_provider.go @@ -18,11 +18,7 @@ func (receiver *ServiceProvider) Register(app foundation.Application) { } func (receiver *ServiceProvider) Boot(app foundation.Application) { - receiver.registerCommands(app) -} - -func (receiver *ServiceProvider) registerCommands(app foundation.Application) { - app.MakeArtisan().Register([]console.Command{ + app.Commands([]console.Command{ &queueConsole.JobMakeCommand{}, }) } diff --git a/queue/task_test.go b/queue/task_test.go index b95ea731b..e82967195 100644 --- a/queue/task_test.go +++ b/queue/task_test.go @@ -1,6 +1,7 @@ package queue import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -20,7 +21,16 @@ func (receiver *Test) Signature() string { // Handle Execute the job. func (receiver *Test) Handle(args ...any) error { - return file.Create("test.txt", args[0].(string)) + if len(args) == 0 { + return fmt.Errorf("no arguments provided") + } + + arg, ok := args[0].(string) + if !ok { + return fmt.Errorf("expected a string argument") + } + + return file.Create("test.txt", arg) } func TestDispatchSync(t *testing.T) { diff --git a/session/errors.go b/session/errors.go index a8904053d..87118d632 100644 --- a/session/errors.go +++ b/session/errors.go @@ -3,5 +3,7 @@ package session import "errors" var ( - ErrDriverNotSet = errors.New("session driver is not set") + ErrDriverNotSet = errors.New("session driver is not set") + ErrConfigFacadeNotSet = errors.New("config facade is not initialized") + ErrJSONNotSet = errors.New("JSON parser is not initialized") ) diff --git a/session/manager_test.go b/session/manager_test.go index 821fffb88..ebf215b94 100644 --- a/session/manager_test.go +++ b/session/manager_test.go @@ -99,10 +99,11 @@ func (s *ManagerTestSuite) TestBuildSession() { s.mockConfig.On("GetString", "session.cookie").Return("test_cookie").Once() session, err := s.manager.BuildSession(driver) - session.Put("name", "goravel") - s.Nil(err) s.NotNil(session) + + session.Put("name", "goravel") + s.Equal("test_cookie", session.GetName()) s.Equal("goravel", session.Get("name")) diff --git a/session/service_provider.go b/session/service_provider.go index a3c3d2c44..682b18d9a 100644 --- a/session/service_provider.go +++ b/session/service_provider.go @@ -19,7 +19,15 @@ type ServiceProvider struct { func (receiver *ServiceProvider) Register(app foundation.Application) { app.Singleton(Binding, func(app foundation.Application) (any, error) { c := app.MakeConfig() + if c == nil { + return nil, ErrConfigFacadeNotSet + } + j := app.GetJson() + if j == nil { + return nil, ErrJSONNotSet + } + return NewManager(c, j), nil }) } diff --git a/support/json/errors.go b/support/json/errors.go new file mode 100644 index 000000000..7adff9010 --- /dev/null +++ b/support/json/errors.go @@ -0,0 +1,7 @@ +package json + +import "errors" + +var ( + ErrApplicationNotSet = errors.New("application instance not initialized") +) diff --git a/support/json/json.go b/support/json/json.go index 5ec84650e..4092dd966 100644 --- a/support/json/json.go +++ b/support/json/json.go @@ -4,12 +4,22 @@ import "github.com/goravel/framework/foundation" // Marshal serializes the given value to a JSON-encoded byte slice. func Marshal(v any) ([]byte, error) { - return foundation.App.GetJson().Marshal(v) + app := foundation.App + if app == nil { + return nil, ErrApplicationNotSet + } + + return app.GetJson().Marshal(v) } // Unmarshal deserializes the given JSON-encoded byte slice into the provided value. func Unmarshal(data []byte, v any) error { - return foundation.App.GetJson().Unmarshal(data, v) + app := foundation.App + if app == nil { + return ErrApplicationNotSet + } + + return app.GetJson().Unmarshal(data, v) } // MarshalString serializes the given value to a JSON-encoded string. diff --git a/support/str/str.go b/support/str/str.go index 5714f729d..117bc067f 100644 --- a/support/str/str.go +++ b/support/str/str.go @@ -804,12 +804,14 @@ func (s *String) Upper() *String { } // When returns the String instance with the given callback applied if the given condition is true. -// If the condition is false, the fallback callback is applied.(if provided) +// If the condition is false, the fallback callback is applied (if provided). func (s *String) When(condition bool, callback ...func(*String) *String) *String { if condition { - return callback[0](s) + if len(callback) > 0 && callback[0] != nil { + return callback[0](s) + } } else { - if len(callback) > 1 { + if len(callback) > 1 && callback[1] != nil { return callback[1](s) } } diff --git a/testing/docker/database.go b/testing/docker/database.go index e1c27bb8c..67205011a 100644 --- a/testing/docker/database.go +++ b/testing/docker/database.go @@ -21,13 +21,21 @@ type Database struct { connection string } -func NewDatabase(app foundation.Application, connection string) *Database { +func NewDatabase(app foundation.Application, connection string) (*Database, error) { config := app.MakeConfig() + if config == nil { + return nil, ErrConfigNotSet + } if connection == "" { connection = config.GetString("database.default") } + artisanFacade := app.MakeArtisan() + if artisanFacade == nil { + return nil, ErrArtisanNotSet + } + driver := config.GetString(fmt.Sprintf("database.connections.%s.driver", connection)) database := config.GetString(fmt.Sprintf("database.connections.%s.database", connection)) username := config.GetString(fmt.Sprintf("database.connections.%s.username", connection)) @@ -36,11 +44,11 @@ func NewDatabase(app foundation.Application, connection string) *Database { return &Database{ app: app, - artisan: app.MakeArtisan(), + artisan: artisanFacade, config: config, connection: connection, DatabaseDriver: databaseDriver, - } + }, nil } func (receiver *Database) Build() error { diff --git a/testing/docker/database_test.go b/testing/docker/database_test.go index 7bb6449f1..bb9505a8f 100644 --- a/testing/docker/database_test.go +++ b/testing/docker/database_test.go @@ -145,8 +145,9 @@ func TestNewDatabase(t *testing.T) { t.Run(tt.name, func(t *testing.T) { beforeEach() tt.setup() - gotDatabase := NewDatabase(mockApp, tt.connection) + gotDatabase, err := NewDatabase(mockApp, tt.connection) + assert.Nil(t, err) assert.Equal(t, tt.wantDatabase(), gotDatabase) }) } diff --git a/testing/docker/docker.go b/testing/docker/docker.go index 34fd1d63b..f6008ce72 100644 --- a/testing/docker/docker.go +++ b/testing/docker/docker.go @@ -17,8 +17,8 @@ func NewDocker(app foundation.Application) *Docker { func (receiver *Docker) Database(connection ...string) (testing.Database, error) { if len(connection) == 0 { - return NewDatabase(receiver.app, ""), nil + return NewDatabase(receiver.app, "") } else { - return NewDatabase(receiver.app, connection[0]), nil + return NewDatabase(receiver.app, connection[0]) } } diff --git a/testing/docker/errors.go b/testing/docker/errors.go new file mode 100644 index 000000000..e4431d72f --- /dev/null +++ b/testing/docker/errors.go @@ -0,0 +1,8 @@ +package docker + +import "errors" + +var ( + ErrArtisanNotSet = errors.New("artisan facade is not initialized") + ErrConfigNotSet = errors.New("config facade is not initialized") +) diff --git a/testing/service_provider.go b/testing/service_provider.go index 04281dbf2..8e9c1b175 100644 --- a/testing/service_provider.go +++ b/testing/service_provider.go @@ -3,11 +3,12 @@ package testing import ( contractsconsole "github.com/goravel/framework/contracts/console" "github.com/goravel/framework/contracts/foundation" + "github.com/goravel/framework/support/color" ) const Binding = "goravel.testing" -var artisanFacades contractsconsole.Artisan +var artisanFacade contractsconsole.Artisan type ServiceProvider struct { } @@ -19,5 +20,8 @@ func (receiver *ServiceProvider) Register(app foundation.Application) { } func (receiver *ServiceProvider) Boot(app foundation.Application) { - artisanFacades = app.MakeArtisan() + artisanFacade = app.MakeArtisan() + if artisanFacade == nil { + color.Red().Println("Warning: Artisan facade is not initialized") + } } diff --git a/testing/test_case.go b/testing/test_case.go index b0414751b..d8b28b9ee 100644 --- a/testing/test_case.go +++ b/testing/test_case.go @@ -18,9 +18,9 @@ func (receiver *TestCase) Seed(seeds ...seeder.Seeder) { } } - artisanFacades.Call(command) + artisanFacade.Call(command) } func (receiver *TestCase) RefreshDatabase(seeds ...seeder.Seeder) { - artisanFacades.Call("migrate:refresh") + artisanFacade.Call("migrate:refresh") } diff --git a/testing/test_case_test.go b/testing/test_case_test.go index dc8a04c66..8701cbc07 100644 --- a/testing/test_case_test.go +++ b/testing/test_case_test.go @@ -22,7 +22,7 @@ func TestTestCaseSuite(t *testing.T) { func (s *TestCaseSuite) SetupTest() { s.mockArtisan = &consolemocks.Artisan{} s.testCase = &TestCase{} - artisanFacades = s.mockArtisan + artisanFacade = s.mockArtisan } func (s *TestCaseSuite) TestSeed() { diff --git a/validation/errors_test.go b/validation/errors_test.go index 4cacb911e..3833c7138 100644 --- a/validation/errors_test.go +++ b/validation/errors_test.go @@ -74,9 +74,14 @@ func TestOne(t *testing.T) { test.rules, test.options..., ) + assert.Nil(t, err, test.describe) + assert.NotNil(t, validator, test.describe) + if test.expectRes != "" { - assert.Equal(t, test.expectRes, validator.Errors().One(), test.describe) + errors := validator.Errors() + assert.NotNil(t, errors) + assert.Equal(t, test.expectRes, errors.One(), test.describe) } } } @@ -116,10 +121,14 @@ func TestGet(t *testing.T) { ) assert.Nil(t, err, test.describe) if len(test.expectA) > 0 { - assert.Equal(t, test.expectA, validator.Errors().Get("a"), test.describe) + errors := validator.Errors() + assert.NotNil(t, errors) + assert.Equal(t, test.expectA, errors.Get("a"), test.describe) } if len(test.expectB) > 0 { - assert.Equal(t, test.expectB, validator.Errors().Get("b"), test.describe) + errors := validator.Errors() + assert.NotNil(t, errors) + assert.Equal(t, test.expectB, errors.Get("b"), test.describe) } } } @@ -161,7 +170,9 @@ func TestAll(t *testing.T) { ) assert.Nil(t, err, test.describe) if len(test.expectRes) > 0 { - assert.Equal(t, test.expectRes, validator.Errors().All(), test.describe) + errors := validator.Errors() + assert.NotNil(t, errors) + assert.Equal(t, test.expectRes, errors.All(), test.describe) } } } @@ -199,7 +210,9 @@ func TestHas(t *testing.T) { ) assert.Nil(t, err, test.describe) if test.expectRes { - assert.Equal(t, test.expectRes, validator.Errors().Has("a"), test.describe) + errors := validator.Errors() + assert.NotNil(t, errors) + assert.Equal(t, test.expectRes, errors.Has("a"), test.describe) } } } diff --git a/validation/service_provider.go b/validation/service_provider.go index 6b1eacdfb..6d0658ba0 100644 --- a/validation/service_provider.go +++ b/validation/service_provider.go @@ -18,11 +18,7 @@ func (database *ServiceProvider) Register(app foundation.Application) { } func (database *ServiceProvider) Boot(app foundation.Application) { - database.registerCommands(app) -} - -func (database *ServiceProvider) registerCommands(app foundation.Application) { - app.MakeArtisan().Register([]consolecontract.Command{ + app.Commands([]consolecontract.Command{ &console.RuleMakeCommand{}, &console.FilterMakeCommand{}, }) diff --git a/validation/validation_test.go b/validation/validation_test.go index e32ff4850..b5656c398 100644 --- a/validation/validation_test.go +++ b/validation/validation_test.go @@ -2894,7 +2894,11 @@ func (receiver *Uppercase) Signature() string { func (receiver *Uppercase) Passes(data httpvalidate.Data, val any, options ...any) bool { name, exist := data.Get("name") - return strings.ToUpper(val.(string)) == val.(string) && len(val.(string)) == cast.ToInt(options[0]) && name == val && exist + if len(options) > 0 { + return strings.ToUpper(val.(string)) == val.(string) && len(val.(string)) == cast.ToInt(options[0]) && name == val && exist + } + + return false } // Message Get the validation error message. @@ -2914,7 +2918,11 @@ func (receiver *Lowercase) Signature() string { func (receiver *Lowercase) Passes(data httpvalidate.Data, val any, options ...any) bool { address, exist := data.Get("address") - return strings.ToLower(val.(string)) == val.(string) && len(val.(string)) == cast.ToInt(options[0]) && address == val && exist + if len(options) > 0 { + return strings.ToLower(val.(string)) == val.(string) && len(val.(string)) == cast.ToInt(options[0]) && address == val && exist + } + + return false } // Message Get the validation error message.