From 6ba9bade0a45049dbb90e1bd2394a66840ac62e4 Mon Sep 17 00:00:00 2001 From: IQBAL HASAN Date: Tue, 9 Sep 2025 16:39:08 +0600 Subject: [PATCH 1/2] feat: Add comprehensive message validation with configurable Laravel-style validation rules - Add message validation configuration to textify.php config - Implement validation for required, min, and max message length - Use Laravel-style validation naming (required, min, max) for consistency - Add validateMessageContent() method to BaseProvider - Enhanced error handling in all Bangladeshi SMS providers - Add comprehensive MessageValidationTest suite - Support for nullable messages when required=false - Proper whitespace handling and error messages - All tests passing with PHPStan validation Fixes issue with ReveSMS error 114 'Content not provided' and similar validation issues across all providers. --- config/textify.php | 23 +++++ .../Bangladeshi/BulkSmsBdProvider.php | 1 - src/Providers/BaseProvider.php | 78 +++++++++++++++++ tests/MessageValidationTest.php | 86 +++++++++++++++++++ 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 tests/MessageValidationTest.php diff --git a/config/textify.php b/config/textify.php index b453bfd..b8cc40a 100644 --- a/config/textify.php +++ b/config/textify.php @@ -207,4 +207,27 @@ 'events' => [ 'enabled' => env('TEXTIFY_EVENTS_ENABLED', true), ], + + /* + |-------------------------------------------------------------------------- + | Message Validation + |-------------------------------------------------------------------------- + | + | Configure validation rules for SMS messages before sending. + | Uses Laravel-style validation rules for consistency. + | + */ + + 'validation' => [ + 'message' => [ + // Set to false to allow empty messages + 'required' => env('TEXTIFY_MESSAGE_REQUIRED', true), + + // Minimum message length + 'min' => env('TEXTIFY_MESSAGE_MIN_LENGTH', 1), + + // Maximum message length (null = no limit) + 'max' => env('TEXTIFY_MESSAGE_MAX_LENGTH', null), + ], + ], ]; diff --git a/src/Providers/Bangladeshi/BulkSmsBdProvider.php b/src/Providers/Bangladeshi/BulkSmsBdProvider.php index 00c7387..43a6021 100644 --- a/src/Providers/Bangladeshi/BulkSmsBdProvider.php +++ b/src/Providers/Bangladeshi/BulkSmsBdProvider.php @@ -119,7 +119,6 @@ protected function parseResponse(array $response): TextifyResponse '1006' => 'Balance Validity Not Available', '1007' => 'Balance Insufficient', '1011' => 'User Id not found', - // Add more error codes as needed ]; $errorMessage = $errorMessages[$errorCode] ?? $responseText; } diff --git a/src/Providers/BaseProvider.php b/src/Providers/BaseProvider.php index c93d1ce..6427c60 100644 --- a/src/Providers/BaseProvider.php +++ b/src/Providers/BaseProvider.php @@ -132,6 +132,15 @@ public function send(TextifyMessage $message): TextifyResponse ); } + // Validate message content + $messageValidationResult = $this->validateMessageContent($message->message); + if (! $messageValidationResult['valid']) { + return TextifyResponse::failed( + errorMessage: $messageValidationResult['error'], + errorCode: 'INVALID_MESSAGE_CONTENT' + ); + } + // Format phone number $formattedMessage = new TextifyMessage( to: $this->formatPhoneNumber($message->to), @@ -292,4 +301,73 @@ protected function ensureConfigKeys(): void ); } } + + /** + * Validate message content according to configuration rules + * + * @param string $message The message content to validate + * @return array{valid: bool, error?: string} + */ + protected function validateMessageContent(string $message): array + { + // Use Laravel-style validation config structure + $required = config('textify.validation.message.required', true); + $minLength = config('textify.validation.message.min', 1); + $maxLength = config('textify.validation.message.max', null); + + $trimmedMessage = trim($message); + + // Check if message is required but empty + if ($required && empty($trimmedMessage)) { + return [ + 'valid' => false, + 'error' => 'The message field is required.', + ]; + } + + // Skip length validation if message is empty and not required + if (!$required && empty($trimmedMessage)) { + return ['valid' => true]; + } + + // Check minimum length (combines empty check with min length) + if (strlen($trimmedMessage) < $minLength) { + return [ + 'valid' => false, + 'error' => sprintf('The message must be at least %d character%s.', $minLength, $minLength === 1 ? '' : 's'), + ]; + } + + // Check maximum length if specified + if ($maxLength !== null && strlen($trimmedMessage) > $maxLength) { + return [ + 'valid' => false, + 'error' => sprintf('The message may not be greater than %d characters.', $maxLength), + ]; + } + + return ['valid' => true]; + } + + /** + * {@inheritdoc} + */ + public function validatePhoneNumber(string $phoneNumber): bool + { + // Basic validation - check if not empty and contains only digits, +, -, spaces, and parentheses + $cleaned = preg_replace('/[\s\-\(\)]+/', '', $phoneNumber); + + return ! empty(trim($phoneNumber)) && + preg_match('/^\+?[0-9]{7,15}$/', $cleaned); + } + + /** + * {@inheritdoc} + */ + public function formatPhoneNumber(string $phoneNumber): string + { + // Default implementation - just trim whitespace + // Providers should override this method for their specific formatting requirements + return trim($phoneNumber); + } } diff --git a/tests/MessageValidationTest.php b/tests/MessageValidationTest.php new file mode 100644 index 0000000..a872bbc --- /dev/null +++ b/tests/MessageValidationTest.php @@ -0,0 +1,86 @@ +send($message); + + expect($response->isSuccessful())->toBeFalse(); + expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT'); + expect($response->getErrorMessage())->toContain('field is required'); +}); + +it('accepts empty messages when not required (nullable)', function () { + // Temporarily override config to make message not required (nullable) + config(['textify.validation.message.required' => false]); + + $provider = new LogProvider([]); + + $message = TextifyMessage::create('01712345678', '', 'TestSender'); + $response = $provider->send($message); + + expect($response->isSuccessful())->toBeTrue(); + + // Reset config + config(['textify.validation.message.required' => true]); +}); + +it('rejects messages shorter than minimum length', function () { + config(['textify.validation.message.min' => 5]); + + $provider = new LogProvider([]); + + $message = TextifyMessage::create('01712345678', 'Hi', 'TestSender'); + $response = $provider->send($message); + + expect($response->isSuccessful())->toBeFalse(); + expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT'); + expect($response->getErrorMessage())->toContain('must be at least 5 character'); + + // Reset config + config(['textify.validation.message.min' => 1]); +}); + +it('rejects messages longer than maximum length', function () { + config(['textify.validation.message.max' => 10]); + + $provider = new LogProvider([]); + + $message = TextifyMessage::create('01712345678', 'This message is way too long', 'TestSender'); + $response = $provider->send($message); + + expect($response->isSuccessful())->toBeFalse(); + expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT'); + expect($response->getErrorMessage())->toContain('may not be greater than 10 characters'); + + // Reset config + config(['textify.validation.message.max' => null]); +}); + +it('accepts valid messages', function () { + $provider = new LogProvider([]); + + $message = TextifyMessage::create('01712345678', 'Valid message content', 'TestSender'); + $response = $provider->send($message); + + expect($response->isSuccessful())->toBeTrue(); +}); + +it('validates whitespace-only messages as empty', function () { + $provider = new LogProvider([]); + + $message = TextifyMessage::create('01712345678', ' ', 'TestSender'); + $response = $provider->send($message); + + expect($response->isSuccessful())->toBeFalse(); + expect($response->getErrorCode())->toBe('INVALID_MESSAGE_CONTENT'); + expect($response->getErrorMessage())->toContain('field is required'); +}); From c198a1a0f1b103fe217c8a239fc5810791e2d8e5 Mon Sep 17 00:00:00 2001 From: iqbalhasandev <39612205+iqbalhasandev@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:39:32 +0000 Subject: [PATCH 2/2] Fix styling --- src/Providers/BaseProvider.php | 8 ++++---- tests/MessageValidationTest.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Providers/BaseProvider.php b/src/Providers/BaseProvider.php index 6427c60..f8a7a88 100644 --- a/src/Providers/BaseProvider.php +++ b/src/Providers/BaseProvider.php @@ -316,7 +316,7 @@ protected function validateMessageContent(string $message): array $maxLength = config('textify.validation.message.max', null); $trimmedMessage = trim($message); - + // Check if message is required but empty if ($required && empty($trimmedMessage)) { return [ @@ -326,7 +326,7 @@ protected function validateMessageContent(string $message): array } // Skip length validation if message is empty and not required - if (!$required && empty($trimmedMessage)) { + if (! $required && empty($trimmedMessage)) { return ['valid' => true]; } @@ -356,8 +356,8 @@ public function validatePhoneNumber(string $phoneNumber): bool { // Basic validation - check if not empty and contains only digits, +, -, spaces, and parentheses $cleaned = preg_replace('/[\s\-\(\)]+/', '', $phoneNumber); - - return ! empty(trim($phoneNumber)) && + + return ! empty(trim($phoneNumber)) && preg_match('/^\+?[0-9]{7,15}$/', $cleaned); } diff --git a/tests/MessageValidationTest.php b/tests/MessageValidationTest.php index a872bbc..9036dbd 100644 --- a/tests/MessageValidationTest.php +++ b/tests/MessageValidationTest.php @@ -21,7 +21,7 @@ it('accepts empty messages when not required (nullable)', function () { // Temporarily override config to make message not required (nullable) config(['textify.validation.message.required' => false]); - + $provider = new LogProvider([]); $message = TextifyMessage::create('01712345678', '', 'TestSender'); @@ -35,7 +35,7 @@ it('rejects messages shorter than minimum length', function () { config(['textify.validation.message.min' => 5]); - + $provider = new LogProvider([]); $message = TextifyMessage::create('01712345678', 'Hi', 'TestSender'); @@ -51,7 +51,7 @@ it('rejects messages longer than maximum length', function () { config(['textify.validation.message.max' => 10]); - + $provider = new LogProvider([]); $message = TextifyMessage::create('01712345678', 'This message is way too long', 'TestSender');