From 51cda1c33d50d87a0e7ad23c3b24ba504cf7f264 Mon Sep 17 00:00:00 2001 From: Tournier Quentin Date: Thu, 5 Jun 2025 01:52:50 +0200 Subject: [PATCH 1/2] feat: update project to use new httpClient --- README.md | 159 +++--------------- src/main/java/dev/resms/ReSMS.java | 49 +----- .../java/dev/resms/api/ReSMSApiClient.java | 92 ---------- .../java/dev/resms/config/ReSMSConfig.java | 19 --- .../{ => core}/exception/ReSMSException.java | 2 +- .../dev/resms/{ => core}/model/Response.java | 2 +- .../dev/resms/core/net/impl/HttpClient.java | 4 +- .../senderid/NoDefaultSenderIdException.java | 9 - .../senderid/SenderIdNotFoundException.java | 9 - .../sms/CountryDetectionFailedException.java | 9 - .../sms/InsufficientSmsQuotaException.java | 9 - .../MessageStatusUpdateFailedException.java | 9 - .../PhoneNumberParsingFailedException.java | 9 - .../user/InvalidApiKeyException.java | 9 - .../validation/ReSMSValidationException.java | 9 - .../java/dev/resms/model/ErrorResponse.java | 14 -- .../resms/model/request/SendSmsRequest.java | 13 -- .../java/dev/resms/service/SmsService.java | 30 ---- src/main/java/dev/resms/services/sms/Sms.java | 47 ++++++ .../services/sms/model/SendSmsOptions.java | 14 ++ .../sms/model}/SendSmsResponse.java | 6 +- .../validator/SendSmsOptionsValidator.java | 41 +++++ .../dev/resms/validator/SendSmsValidator.java | 43 ----- .../SendSmsOptionsValidatorTest.java | 41 +++++ .../resms/validator/SendSmsValidatorTest.java | 43 ----- 25 files changed, 180 insertions(+), 511 deletions(-) delete mode 100644 src/main/java/dev/resms/api/ReSMSApiClient.java delete mode 100644 src/main/java/dev/resms/config/ReSMSConfig.java rename src/main/java/dev/resms/{ => core}/exception/ReSMSException.java (86%) rename src/main/java/dev/resms/{ => core}/model/Response.java (75%) delete mode 100644 src/main/java/dev/resms/exception/senderid/NoDefaultSenderIdException.java delete mode 100644 src/main/java/dev/resms/exception/senderid/SenderIdNotFoundException.java delete mode 100644 src/main/java/dev/resms/exception/sms/CountryDetectionFailedException.java delete mode 100644 src/main/java/dev/resms/exception/sms/InsufficientSmsQuotaException.java delete mode 100644 src/main/java/dev/resms/exception/sms/MessageStatusUpdateFailedException.java delete mode 100644 src/main/java/dev/resms/exception/sms/PhoneNumberParsingFailedException.java delete mode 100644 src/main/java/dev/resms/exception/user/InvalidApiKeyException.java delete mode 100644 src/main/java/dev/resms/exception/validation/ReSMSValidationException.java delete mode 100644 src/main/java/dev/resms/model/ErrorResponse.java delete mode 100644 src/main/java/dev/resms/model/request/SendSmsRequest.java delete mode 100644 src/main/java/dev/resms/service/SmsService.java create mode 100644 src/main/java/dev/resms/services/sms/Sms.java create mode 100644 src/main/java/dev/resms/services/sms/model/SendSmsOptions.java rename src/main/java/dev/resms/{model/response => services/sms/model}/SendSmsResponse.java (61%) create mode 100644 src/main/java/dev/resms/services/sms/validator/SendSmsOptionsValidator.java delete mode 100644 src/main/java/dev/resms/validator/SendSmsValidator.java create mode 100644 src/test/java/dev/resms/services/sms/validator/SendSmsOptionsValidatorTest.java delete mode 100644 src/test/java/dev/resms/validator/SendSmsValidatorTest.java diff --git a/README.md b/README.md index dcc1ea0..78255e0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ReSMS SDK for Java +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Maven Central](https://img.shields.io/maven-central/v/dev.resms/resms-java-sdk.svg)](https://search.maven.org/artifact/dev.resms/resms-java-sdk) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Java Version](https://img.shields.io/badge/Java-11%2B-blue)](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html) A lightweight, easy-to-use Java SDK for [ReSMS](https://resms.dev) - the simple and powerful SMS API for developers. @@ -11,9 +11,6 @@ A lightweight, easy-to-use Java SDK for [ReSMS](https://resms.dev) - the simple - [Requirements](#-requirements) - [Installation](#-installation) - [Quick Start](#-quick-start) -- [Usage Examples](#-usage-examples) -- [Error Handling](#-error-handling) -- [Advanced Configuration](#-advanced-configuration) - [Documentation](#-documentation) - [License](#-license) @@ -50,145 +47,31 @@ implementation("dev.resms:resms-java-sdk:1.0.1") 1. **Get your API key** from the [ReSMS Dashboard](https://resms.dev/dashboard) -2. **Initialize the client** +2. **Send your first SMS** ```java -import dev.resms.ReSMS; - -ReSMS client = new ReSMS("your-api-key"); -``` - -3. **Send your first SMS** - -```java -import dev.resms.ReSMS; -import dev.resms.exception.ReSMSException; -import dev.resms.model.response.SendSmsResponse; - -public class QuickStartExample { - public static void main(String[] args) { - ReSMS client = new ReSMS("your-api-key"); - - try { - SendSmsResponse response = client.sendSms("+33123456789", "Hello from ReSMS!"); - System.out.println("Message sent! ID: " + response.getData().getMessageId()); - } catch (ReSMSException e) { - System.err.println("Error sending SMS: " + e.getMessage()); - } - } -} -``` - -## 📱 Usage Examples - -### Basic SMS Sending - -```java -import dev.resms.ReSMS; -import dev.resms.model.response.SendSmsResponse; - -public class BasicExample { - public static void main(String[] args) { - // Initialize the client - ReSMS client = new ReSMS("your-api-key"); - - try { - // Send an SMS - SendSmsResponse response = client.sendSms("+33123456789", "Welcome to ReSMS!"); - - // Get the message ID and status - String messageId = response.getData().getMessageId(); - String status = response.getStatus(); - - System.out.println("Message sent successfully!"); - System.out.println("Message ID: " + messageId); - System.out.println("Status: " + status); - } catch (ReSMSException e) { - System.err.println("Failed to send SMS: " + e.getMessage()); - } +import dev.resms.core.exception.ReSMSException; +import dev.resms.services.sms.model.SendSmsOptions; + +public class Main { + public static void main(String[] args) { + ReSMS reSms = new ReSMS("re_123"); + + SendSmsOptions smsOptions = + SendSmsOptions.builder() + .to("+33123456789") + .message("Welcome to ReSMS!") + .senderId("ReSMS") + .build(); + + try { + reSms.sms().send(smsOptions); + } catch (ReSMSException e) { + e.printStackTrace(); } + } } -``` - -### Using Custom Configuration - -```java -import dev.resms.ReSMS; -import dev.resms.config.ReSMSConfig; - -public class ConfigExample { - public static void main(String[] args) { - // Create a custom configuration - ReSMSConfig config = new ReSMSConfig("your-api-key"); - - // Initialize the client with the custom configuration - ReSMS client = new ReSMS(config); - - // Now you can use the client as usual - // ... - } -} -``` - -## ⚠️ Error Handling - -The SDK uses a comprehensive exception hierarchy to help you handle errors effectively: - -- `ReSMSException`: Base exception class for all ReSMS-related errors - -### Specific Exceptions - -All these exceptions extend `ReSMSException`: - -- `InvalidApiKeyException`: Thrown when the API key is invalid -- `CountryDetectionFailedException`: Thrown when the country of the phone number cannot be detected -- `PhoneNumberParsingFailedException`: Thrown when the phone number cannot be parsed -- `InsufficientSmsQuotaException`: Thrown when you don't have enough SMS quota -- `MessageStatusUpdateFailedException`: Thrown when the message status cannot be updated - -### Example: Handling Specific Exceptions - -```java -import dev.resms.ReSMS; -import dev.resms.exception.ReSMSException; -import dev.resms.exception.sms.InsufficientSmsQuotaException; -import dev.resms.exception.user.InvalidApiKeyException; - -public class ErrorHandlingExample { - public static void main(String[] args) { - ReSMS client = new ReSMS("your-api-key"); - - try { - client.sendSms("+33123456789", "Test message"); - System.out.println("Message sent successfully!"); - } catch (InvalidApiKeyException e) { - System.err.println("Authentication error: " + e.getMessage()); - System.err.println("Please check your API key"); - } catch (InsufficientSmsQuotaException e) { - System.err.println("Quota exceeded: " + e.getMessage()); - System.err.println("Please upgrade your plan"); - } catch (ReSMSException e) { - System.err.println("ReSMS error: " + e.getMessage()); - } catch (Exception e) { - System.err.println("Unexpected error: " + e.getMessage()); - } - } -} -``` - -## ⚙️ Advanced Configuration - -The `ReSMSConfig` class allows you to customize the SDK's behavior: - -```java -import dev.resms.ReSMS; -import dev.resms.config.ReSMSConfig; - -// Create a configuration with your API key -ReSMSConfig config = new ReSMSConfig("your-api-key"); - // Initialize the client with the configuration - ReSMS client = new ReSMS(config); ``` ## 📚 Documentation diff --git a/src/main/java/dev/resms/ReSMS.java b/src/main/java/dev/resms/ReSMS.java index 0c9b617..8430d14 100644 --- a/src/main/java/dev/resms/ReSMS.java +++ b/src/main/java/dev/resms/ReSMS.java @@ -1,51 +1,20 @@ package dev.resms; -import dev.resms.config.ReSMSConfig; -import dev.resms.exception.ReSMSException; -import dev.resms.model.request.SendSmsRequest; -import dev.resms.model.response.SendSmsResponse; -import dev.resms.service.SmsService; -import lombok.NonNull; +import dev.resms.services.sms.Sms; +import lombok.RequiredArgsConstructor; /** ReSMS Java SDK - Client principal */ +@RequiredArgsConstructor public class ReSMS { - private final SmsService smsService; + /** The API key for the ReSMS service. */ + private final String apiKey; /** - * Creates a new ReSMS client with a default timeout of 10 seconds + * Returns an Sms object that can be used to interact with the SMS service. * - * @param apiKey API key for authentication + * @return An Sms object. */ - public ReSMS(@NonNull String apiKey) { - this(new ReSMSConfig(apiKey)); - } - - public ReSMS(@NonNull ReSMSConfig config) { - this.smsService = new SmsService(config); - } - - /** - * Sends an SMS message - Main method - * - * @param to Phone number to send the message to - * @param message Message content - * @return SendSmsResponse containing the message ID and status - * @throws ReSMSException if fails - */ - public SendSmsResponse sendSms(@NonNull String to, @NonNull String message) - throws ReSMSException { - SendSmsRequest request = new SendSmsRequest(to, message); - return this.sendSms(request); - } - - /** - * Sends an SMS message using request object - * - * @param request SendSmsRequest object - * @return SendSmsResponse containing the message ID and status - * @throws ReSMSException if fails - */ - public SendSmsResponse sendSms(@NonNull SendSmsRequest request) throws ReSMSException { - return smsService.sendSms(request); + public Sms sms() { + return new Sms(apiKey); } } diff --git a/src/main/java/dev/resms/api/ReSMSApiClient.java b/src/main/java/dev/resms/api/ReSMSApiClient.java deleted file mode 100644 index 9d109a0..0000000 --- a/src/main/java/dev/resms/api/ReSMSApiClient.java +++ /dev/null @@ -1,92 +0,0 @@ -package dev.resms.api; - -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.Moshi; -import dev.resms.config.ReSMSConfig; -import dev.resms.exception.ReSMSException; -import dev.resms.exception.sms.CountryDetectionFailedException; -import dev.resms.exception.sms.InsufficientSmsQuotaException; -import dev.resms.exception.sms.MessageStatusUpdateFailedException; -import dev.resms.exception.sms.PhoneNumberParsingFailedException; -import dev.resms.exception.user.InvalidApiKeyException; -import dev.resms.model.ErrorResponse; -import dev.resms.model.request.SendSmsRequest; -import dev.resms.model.response.SendSmsResponse; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import org.apache.hc.core5.http.HttpStatus; - -public class ReSMSApiClient { - private static final String SEND_SMS_PATH = "sms/send"; - - private final ReSMSConfig config; - private final HttpClient httpClient; - private final JsonAdapter requestAdapter; - private final JsonAdapter responseAdapter; - private final JsonAdapter errorResponseAdapter; - - public ReSMSApiClient(ReSMSConfig config) { - this.config = config; - this.httpClient = HttpClient.newBuilder().build(); - - Moshi moshi = new Moshi.Builder().build(); - - this.requestAdapter = moshi.adapter(SendSmsRequest.class); - this.responseAdapter = moshi.adapter(SendSmsResponse.class); - this.errorResponseAdapter = moshi.adapter(ErrorResponse.class); - } - - public SendSmsResponse sendSms(SendSmsRequest requestBody) throws ReSMSException { - String jsonBody = requestAdapter.toJson(requestBody); - - HttpRequest request = - HttpRequest.newBuilder() - .uri(URI.create(ReSMSConfig.BASE_URL + SEND_SMS_PATH)) - .header("Content-Type", "application/json") - .header("X-Api-Key", config.getApiKey()) - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) - .build(); - - HttpResponse httpResponse; - try { - httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - throw new ReSMSException("Failed to send HTTP request", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ReSMSException("HTTP request was interrupted", e); - } - - if (httpResponse.statusCode() == HttpStatus.SC_OK) { - try { - return responseAdapter.fromJson(httpResponse.body()); - } catch (IOException e) { - throw new ReSMSException("Failed to parse response body", e); - } - } else { - try { - ErrorResponse errorResponse = errorResponseAdapter.fromJson(httpResponse.body()); - - String errorName = errorResponse.getError().getName(); - String errorMessage = errorResponse.getError().getMessage(); - - switch (errorName) { - case "INVALID_API_KEY" -> throw new InvalidApiKeyException(errorMessage); - case "COUNTRY_DETECTION_FAILED" -> - throw new CountryDetectionFailedException(errorMessage); - case "PHONE_NUMBER_PARSING_FAILED" -> - throw new PhoneNumberParsingFailedException(errorMessage); - case "INSUFFICIENT_SMS_QUOTA" -> throw new InsufficientSmsQuotaException(errorMessage); - case "MESSAGE_STATUS_UPDATE_FAILED" -> - throw new MessageStatusUpdateFailedException(errorMessage); - default -> throw new ReSMSException(errorResponse.getStatus() + " " + errorMessage); - } - } catch (IOException e) { - throw new ReSMSException("Failed to parse error response", e); - } - } - } -} diff --git a/src/main/java/dev/resms/config/ReSMSConfig.java b/src/main/java/dev/resms/config/ReSMSConfig.java deleted file mode 100644 index c25e98f..0000000 --- a/src/main/java/dev/resms/config/ReSMSConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.resms.config; - -import lombok.Getter; -import lombok.NonNull; - -/** Configuration class for ReSMS SDK */ -@Getter -public class ReSMSConfig { - public static final String BASE_URL = "https://api.resms.dev/"; - - private final String apiKey; - - public ReSMSConfig(@NonNull String apiKey) { - if (apiKey.isBlank()) { - throw new IllegalArgumentException("API key cannot be blank"); - } - this.apiKey = apiKey.trim(); - } -} diff --git a/src/main/java/dev/resms/exception/ReSMSException.java b/src/main/java/dev/resms/core/exception/ReSMSException.java similarity index 86% rename from src/main/java/dev/resms/exception/ReSMSException.java rename to src/main/java/dev/resms/core/exception/ReSMSException.java index 02b2c4b..fc1d66c 100644 --- a/src/main/java/dev/resms/exception/ReSMSException.java +++ b/src/main/java/dev/resms/core/exception/ReSMSException.java @@ -1,4 +1,4 @@ -package dev.resms.exception; +package dev.resms.core.exception; public class ReSMSException extends RuntimeException { public ReSMSException(String message) { diff --git a/src/main/java/dev/resms/model/Response.java b/src/main/java/dev/resms/core/model/Response.java similarity index 75% rename from src/main/java/dev/resms/model/Response.java rename to src/main/java/dev/resms/core/model/Response.java index 273c21e..06aa081 100644 --- a/src/main/java/dev/resms/model/Response.java +++ b/src/main/java/dev/resms/core/model/Response.java @@ -1,4 +1,4 @@ -package dev.resms.model; +package dev.resms.core.model; import lombok.Getter; diff --git a/src/main/java/dev/resms/core/net/impl/HttpClient.java b/src/main/java/dev/resms/core/net/impl/HttpClient.java index 0e35438..3d88d52 100644 --- a/src/main/java/dev/resms/core/net/impl/HttpClient.java +++ b/src/main/java/dev/resms/core/net/impl/HttpClient.java @@ -17,7 +17,7 @@ public class HttpClient implements IHttpClient { /** The base URL for the API. */ - public static final String BASE_API = "https://api.resms.dev/"; + public static final String BASE_API = "https://api.resms.dev"; /** The OkHttpClient instance for handling HTTP requests. */ private final OkHttpClient httpClient; @@ -37,7 +37,7 @@ public HttpClient() { * @return An {@link AbstractHttpResponse} representing the response from the server. */ @Override - public AbstractHttpResponse perform( + public AbstractHttpResponse perform( final String path, final String apiKey, final HttpMethod method, final String payload) { RequestBody requestBody = null; diff --git a/src/main/java/dev/resms/exception/senderid/NoDefaultSenderIdException.java b/src/main/java/dev/resms/exception/senderid/NoDefaultSenderIdException.java deleted file mode 100644 index d507a01..0000000 --- a/src/main/java/dev/resms/exception/senderid/NoDefaultSenderIdException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.senderid; - -import dev.resms.exception.ReSMSException; - -public class NoDefaultSenderIdException extends ReSMSException { - public NoDefaultSenderIdException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/senderid/SenderIdNotFoundException.java b/src/main/java/dev/resms/exception/senderid/SenderIdNotFoundException.java deleted file mode 100644 index 6c04faf..0000000 --- a/src/main/java/dev/resms/exception/senderid/SenderIdNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.senderid; - -import dev.resms.exception.ReSMSException; - -public class SenderIdNotFoundException extends ReSMSException { - public SenderIdNotFoundException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/sms/CountryDetectionFailedException.java b/src/main/java/dev/resms/exception/sms/CountryDetectionFailedException.java deleted file mode 100644 index 049b9a7..0000000 --- a/src/main/java/dev/resms/exception/sms/CountryDetectionFailedException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.sms; - -import dev.resms.exception.ReSMSException; - -public class CountryDetectionFailedException extends ReSMSException { - public CountryDetectionFailedException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/sms/InsufficientSmsQuotaException.java b/src/main/java/dev/resms/exception/sms/InsufficientSmsQuotaException.java deleted file mode 100644 index 747bdfc..0000000 --- a/src/main/java/dev/resms/exception/sms/InsufficientSmsQuotaException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.sms; - -import dev.resms.exception.ReSMSException; - -public class InsufficientSmsQuotaException extends ReSMSException { - public InsufficientSmsQuotaException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/sms/MessageStatusUpdateFailedException.java b/src/main/java/dev/resms/exception/sms/MessageStatusUpdateFailedException.java deleted file mode 100644 index 2eec5b5..0000000 --- a/src/main/java/dev/resms/exception/sms/MessageStatusUpdateFailedException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.sms; - -import dev.resms.exception.ReSMSException; - -public class MessageStatusUpdateFailedException extends ReSMSException { - public MessageStatusUpdateFailedException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/sms/PhoneNumberParsingFailedException.java b/src/main/java/dev/resms/exception/sms/PhoneNumberParsingFailedException.java deleted file mode 100644 index a4c1400..0000000 --- a/src/main/java/dev/resms/exception/sms/PhoneNumberParsingFailedException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.sms; - -import dev.resms.exception.ReSMSException; - -public class PhoneNumberParsingFailedException extends ReSMSException { - public PhoneNumberParsingFailedException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/user/InvalidApiKeyException.java b/src/main/java/dev/resms/exception/user/InvalidApiKeyException.java deleted file mode 100644 index aa39b24..0000000 --- a/src/main/java/dev/resms/exception/user/InvalidApiKeyException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.user; - -import dev.resms.exception.ReSMSException; - -public class InvalidApiKeyException extends ReSMSException { - public InvalidApiKeyException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/exception/validation/ReSMSValidationException.java b/src/main/java/dev/resms/exception/validation/ReSMSValidationException.java deleted file mode 100644 index f8b710c..0000000 --- a/src/main/java/dev/resms/exception/validation/ReSMSValidationException.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.resms.exception.validation; - -import dev.resms.exception.ReSMSException; - -public class ReSMSValidationException extends ReSMSException { - public ReSMSValidationException(String message) { - super(message); - } -} diff --git a/src/main/java/dev/resms/model/ErrorResponse.java b/src/main/java/dev/resms/model/ErrorResponse.java deleted file mode 100644 index 8d415e1..0000000 --- a/src/main/java/dev/resms/model/ErrorResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.resms.model; - -import lombok.Getter; - -@Getter -public class ErrorResponse extends Response { - private ErrorDetails error; - - @Getter - public static class ErrorDetails { - private String name; - private String message; - } -} diff --git a/src/main/java/dev/resms/model/request/SendSmsRequest.java b/src/main/java/dev/resms/model/request/SendSmsRequest.java deleted file mode 100644 index 36c3c75..0000000 --- a/src/main/java/dev/resms/model/request/SendSmsRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.resms.model.request; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NonNull; - -/** Request object for sending SMS */ -@Getter -@AllArgsConstructor -public class SendSmsRequest { - @NonNull private final String to; - @NonNull private final String message; -} diff --git a/src/main/java/dev/resms/service/SmsService.java b/src/main/java/dev/resms/service/SmsService.java deleted file mode 100644 index e172c2b..0000000 --- a/src/main/java/dev/resms/service/SmsService.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.resms.service; - -import dev.resms.api.ReSMSApiClient; -import dev.resms.config.ReSMSConfig; -import dev.resms.exception.ReSMSException; -import dev.resms.model.request.SendSmsRequest; -import dev.resms.model.response.SendSmsResponse; -import dev.resms.validator.SendSmsValidator; -import lombok.NonNull; - -/** SMS service for sending SMS messages */ -public class SmsService { - private final ReSMSApiClient apiClient; - - public SmsService(@NonNull ReSMSConfig config) { - this.apiClient = new ReSMSApiClient(config); - } - - /** - * Sends an SMS message using request object - * - * @param request SendSmsRequest object - * @return SendSmsResponse containing the message ID and status - * @throws ReSMSException if fails - */ - public SendSmsResponse sendSms(@NonNull SendSmsRequest request) throws ReSMSException { - SendSmsValidator.validate(request); - return apiClient.sendSms(request); - } -} diff --git a/src/main/java/dev/resms/services/sms/Sms.java b/src/main/java/dev/resms/services/sms/Sms.java new file mode 100644 index 0000000..b987dd7 --- /dev/null +++ b/src/main/java/dev/resms/services/sms/Sms.java @@ -0,0 +1,47 @@ +package dev.resms.services.sms; + +import dev.resms.core.exception.ReSMSException; +import dev.resms.core.net.AbstractHttpResponse; +import dev.resms.core.net.HttpMethod; +import dev.resms.core.service.BaseService; +import dev.resms.services.sms.model.SendSmsOptions; +import dev.resms.services.sms.model.SendSmsResponse; +import dev.resms.services.sms.validator.SendSmsOptionsValidator; + +public class Sms extends BaseService { + private static final String SEND_SMS_PATH = "/sms/send"; + + /** + * Constructs an instance of the {@code Sms} class. + * + * @param apiKey The apiKey used for authentication. + */ + public Sms(final String apiKey) { + super(apiKey); + } + + /** + * Sends an SMS based on the provided SMS request. + * + * @param sendSmsOptions The request containing SMS details. + * @return The response indicating the status of the sms sending. + * @throws ReSMSException If an error occurs while sending the SMS. + */ + public SendSmsResponse send(SendSmsOptions sendSmsOptions) { + SendSmsOptionsValidator.validate(sendSmsOptions); + + String payload = super.reSMSMapper.toJson(sendSmsOptions); + + AbstractHttpResponse response = + super.httpClient.perform(SEND_SMS_PATH, apiKey, HttpMethod.POST, payload); + + if (!response.isSuccessful()) { + throw new ReSMSException( + "Failed to send sms: " + response.getCode() + " " + response.getBody()); + } + + String responseBody = response.getBody(); + + return reSMSMapper.fromJson(responseBody, SendSmsResponse.class); + } +} diff --git a/src/main/java/dev/resms/services/sms/model/SendSmsOptions.java b/src/main/java/dev/resms/services/sms/model/SendSmsOptions.java new file mode 100644 index 0000000..f9e1ca9 --- /dev/null +++ b/src/main/java/dev/resms/services/sms/model/SendSmsOptions.java @@ -0,0 +1,14 @@ +package dev.resms.services.sms.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class SendSmsOptions { + private String to; + private String message; + private String senderId; +} diff --git a/src/main/java/dev/resms/model/response/SendSmsResponse.java b/src/main/java/dev/resms/services/sms/model/SendSmsResponse.java similarity index 61% rename from src/main/java/dev/resms/model/response/SendSmsResponse.java rename to src/main/java/dev/resms/services/sms/model/SendSmsResponse.java index a9daeb4..ee57eec 100644 --- a/src/main/java/dev/resms/model/response/SendSmsResponse.java +++ b/src/main/java/dev/resms/services/sms/model/SendSmsResponse.java @@ -1,9 +1,9 @@ -package dev.resms.model.response; +package dev.resms.services.sms.model; -import dev.resms.model.Response; +import dev.resms.core.model.Response; import lombok.Getter; -/** Response object returned after sending an SMS */ +/** Represents a response after sending an SMS. */ @Getter public class SendSmsResponse extends Response { private SendSmsResponseData data; diff --git a/src/main/java/dev/resms/services/sms/validator/SendSmsOptionsValidator.java b/src/main/java/dev/resms/services/sms/validator/SendSmsOptionsValidator.java new file mode 100644 index 0000000..6538435 --- /dev/null +++ b/src/main/java/dev/resms/services/sms/validator/SendSmsOptionsValidator.java @@ -0,0 +1,41 @@ +package dev.resms.services.sms.validator; + +import dev.resms.core.exception.ReSMSException; +import dev.resms.services.sms.model.SendSmsOptions; + +public class SendSmsOptionsValidator { + private static final String PHONE_NUMBER_REGEX = "^\\+?[1-9]\\d{1,14}$"; + + public static void validate(SendSmsOptions sendSmsOptions) { + validatePhoneNumber(sendSmsOptions.getTo()); + validateMessage(sendSmsOptions.getMessage()); + } + + /** + * Validates a phone number format + * + * @param phoneNumber Phone number to validate + * @throws ReSMSException if the phone number is invalid + */ + private static void validatePhoneNumber(String phoneNumber) throws ReSMSException { + if (phoneNumber.isBlank()) { + throw new ReSMSException("Phone number cannot be empty"); + } + + if (!phoneNumber.matches(PHONE_NUMBER_REGEX)) { + throw new ReSMSException("Invalid phone number format"); + } + } + + /** + * Validates message content + * + * @param message Message to validate + * @throws ReSMSException if a message is invalid + */ + private static void validateMessage(String message) throws ReSMSException { + if (message.isBlank()) { + throw new ReSMSException("Message cannot be empty"); + } + } +} diff --git a/src/main/java/dev/resms/validator/SendSmsValidator.java b/src/main/java/dev/resms/validator/SendSmsValidator.java deleted file mode 100644 index 534de04..0000000 --- a/src/main/java/dev/resms/validator/SendSmsValidator.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.resms.validator; - -import dev.resms.exception.validation.ReSMSValidationException; -import dev.resms.model.request.SendSmsRequest; -import lombok.NonNull; - -public class SendSmsValidator { - private static final String PHONE_NUMBER_REGEX = "^\\+?[1-9]\\d{1,14}$"; - - public static void validate(@NonNull SendSmsRequest request) throws ReSMSValidationException { - validatePhoneNumber(request.getTo()); - validateMessage(request.getMessage()); - } - - /** - * Validates a phone number format - * - * @param phoneNumber Phone number to validate - * @throws ReSMSValidationException if the phone number is invalid - */ - public static void validatePhoneNumber(@NonNull String phoneNumber) - throws ReSMSValidationException { - if (phoneNumber.isBlank()) { - throw new ReSMSValidationException("Phone number cannot be empty"); - } - - if (!phoneNumber.matches(PHONE_NUMBER_REGEX)) { - throw new ReSMSValidationException("Invalid phone number format"); - } - } - - /** - * Validates message content - * - * @param message Message to validate - * @throws ReSMSValidationException if a message is invalid - */ - public static void validateMessage(@NonNull String message) throws ReSMSValidationException { - if (message.isBlank()) { - throw new ReSMSValidationException("Message cannot be empty"); - } - } -} diff --git a/src/test/java/dev/resms/services/sms/validator/SendSmsOptionsValidatorTest.java b/src/test/java/dev/resms/services/sms/validator/SendSmsOptionsValidatorTest.java new file mode 100644 index 0000000..d673015 --- /dev/null +++ b/src/test/java/dev/resms/services/sms/validator/SendSmsOptionsValidatorTest.java @@ -0,0 +1,41 @@ +package dev.resms.services.sms.validator; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import dev.resms.core.exception.ReSMSException; +import dev.resms.services.sms.model.SendSmsOptions; +import org.junit.jupiter.api.Test; + +class SendSmsOptionsValidatorTest { + @Test + void testValidate_validOptions() { + SendSmsOptions options = SendSmsOptions.builder().to("+1234567890").message("test").build(); + assertDoesNotThrow(() -> SendSmsOptionsValidator.validate(options)); + } + + @Test + void testValidate_emptyPhoneNumber() { + SendSmsOptions options = SendSmsOptions.builder().to(" ").message("test").build(); + ReSMSException ex = + assertThrows(ReSMSException.class, () -> SendSmsOptionsValidator.validate(options)); + assertEquals("Phone number cannot be empty", ex.getMessage()); + } + + @Test + void testValidate_invalidPhoneNumberFormat() { + SendSmsOptions options = SendSmsOptions.builder().to("123abc").message("test").build(); + ReSMSException ex = + assertThrows(ReSMSException.class, () -> SendSmsOptionsValidator.validate(options)); + assertEquals("Invalid phone number format", ex.getMessage()); + } + + @Test + void testValidate_emptyMessage() { + SendSmsOptions options = SendSmsOptions.builder().to("+1234567890").message(" ").build(); + ReSMSException ex = + assertThrows(ReSMSException.class, () -> SendSmsOptionsValidator.validate(options)); + assertEquals("Message cannot be empty", ex.getMessage()); + } +} diff --git a/src/test/java/dev/resms/validator/SendSmsValidatorTest.java b/src/test/java/dev/resms/validator/SendSmsValidatorTest.java deleted file mode 100644 index 9e62f41..0000000 --- a/src/test/java/dev/resms/validator/SendSmsValidatorTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.resms.validator; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import dev.resms.exception.validation.ReSMSValidationException; -import org.junit.jupiter.api.Test; - -class SendSmsValidatorTest { - @Test - void testValidatePhoneNumber_validNumber() { - assertDoesNotThrow(() -> SendSmsValidator.validatePhoneNumber("+1234567890")); - } - - @Test - void testValidatePhoneNumber_emptyNumber() { - ReSMSValidationException ex = - assertThrows( - ReSMSValidationException.class, () -> SendSmsValidator.validatePhoneNumber(" ")); - assertEquals("Phone number cannot be empty", ex.getMessage()); - } - - @Test - void testValidatePhoneNumber_invalidFormat() { - ReSMSValidationException ex = - assertThrows( - ReSMSValidationException.class, () -> SendSmsValidator.validatePhoneNumber("123abc")); - assertEquals("Invalid phone number format", ex.getMessage()); - } - - @Test - void testValidateMessage_validMessage() { - assertDoesNotThrow(() -> SendSmsValidator.validateMessage("Hello!")); - } - - @Test - void testValidateMessage_empty() { - ReSMSValidationException ex = - assertThrows(ReSMSValidationException.class, () -> SendSmsValidator.validateMessage(" ")); - assertEquals("Message cannot be empty", ex.getMessage()); - } -} From 81083240bd296d39d4fc6e5731cb6d0d03007b43 Mon Sep 17 00:00:00 2001 From: Tournier Quentin Date: Thu, 5 Jun 2025 01:56:37 +0200 Subject: [PATCH 2/2] fix: add throws to method signature --- src/main/java/dev/resms/services/sms/Sms.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/resms/services/sms/Sms.java b/src/main/java/dev/resms/services/sms/Sms.java index b987dd7..6c8193d 100644 --- a/src/main/java/dev/resms/services/sms/Sms.java +++ b/src/main/java/dev/resms/services/sms/Sms.java @@ -27,7 +27,7 @@ public Sms(final String apiKey) { * @return The response indicating the status of the sms sending. * @throws ReSMSException If an error occurs while sending the SMS. */ - public SendSmsResponse send(SendSmsOptions sendSmsOptions) { + public SendSmsResponse send(SendSmsOptions sendSmsOptions) throws ReSMSException { SendSmsOptionsValidator.validate(sendSmsOptions); String payload = super.reSMSMapper.toJson(sendSmsOptions);