diff --git a/src/main/java/com/resend/core/net/RequestOptions.java b/src/main/java/com/resend/core/net/RequestOptions.java index d22745e..a7086df 100644 --- a/src/main/java/com/resend/core/net/RequestOptions.java +++ b/src/main/java/com/resend/core/net/RequestOptions.java @@ -1,10 +1,15 @@ package com.resend.core.net; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * Represents a request to create a request options. */ public class RequestOptions { private final String idempotencyKey; + private final Map additionalHeaders; /** * Constructs a RequestOptions object using the provided builder. @@ -13,6 +18,7 @@ public class RequestOptions { */ public RequestOptions(Builder builder) { this.idempotencyKey = builder.idempotencyKey; + this.additionalHeaders = Collections.unmodifiableMap(new HashMap<>(builder.additionalHeaders)); } /** @@ -24,6 +30,15 @@ public String getIdempotencyKey() { return idempotencyKey; } + /** + * Get the additional headers map. + * + * @return An unmodifiable map of additional headers. + */ + public Map getAdditionalHeaders() { + return additionalHeaders; + } + /** * Create a new builder instance for constructing RequestOptions objects. * @@ -38,6 +53,14 @@ public static Builder builder() { */ public static class Builder { private String idempotencyKey; + private final Map additionalHeaders; + + /** + * Constructs a new Builder with empty additional headers map. + */ + public Builder() { + this.additionalHeaders = new HashMap<>(); + } /** * Set the idempotencyKey. @@ -50,6 +73,28 @@ public Builder setIdempotencyKey(String idempotencyKey) { return this; } + /** + * Add a custom header to the additional headers map. + * + * @param name The header name. + * @param value The header value. + * @return The builder instance. + */ + public Builder add(String name, String value) { + this.additionalHeaders.put(name, value); + return this; + } + + /** + * Add multiple custom headers to the additional headers map. + * + * @param headers A map of headers to add. + * @return The builder instance. + */ + public Builder addAll(Map headers) { + this.additionalHeaders.putAll(headers); + return this; + } /** * Build a new RequestOptions object. @@ -60,4 +105,4 @@ public RequestOptions build() { return new RequestOptions(this); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/resend/core/net/impl/HttpClient.java b/src/main/java/com/resend/core/net/impl/HttpClient.java index b007e3d..ef01a4a 100644 --- a/src/main/java/com/resend/core/net/impl/HttpClient.java +++ b/src/main/java/com/resend/core/net/impl/HttpClient.java @@ -154,6 +154,13 @@ public AbstractHttpResponse perform( requestBuilder.addHeader("Idempotency-Key", requestOptions.getIdempotencyKey()); } + if (requestOptions.getAdditionalHeaders() != null + && !requestOptions.getAdditionalHeaders().isEmpty()) { + for (Map.Entry entry : requestOptions.getAdditionalHeaders().entrySet()) { + requestBuilder.addHeader(entry.getKey(), entry.getValue()); + } + } + Request request = requestBuilder.build(); try { diff --git a/src/main/java/com/resend/services/batch/Batch.java b/src/main/java/com/resend/services/batch/Batch.java index 25e65d9..2426af1 100644 --- a/src/main/java/com/resend/services/batch/Batch.java +++ b/src/main/java/com/resend/services/batch/Batch.java @@ -43,9 +43,7 @@ public CreateBatchEmailsResponse send(List emails) throws Re String responseBody = response.getBody(); - CreateBatchEmailsResponse createBatchEmailsResponse = resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class); - - return createBatchEmailsResponse; + return resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class); } /** @@ -67,9 +65,7 @@ public CreateBatchEmailsResponse send(List emails, RequestOp String responseBody = response.getBody(); - CreateBatchEmailsResponse createBatchEmailsResponse = resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class); - - return createBatchEmailsResponse; + return resendMapper.readValue(responseBody, CreateBatchEmailsResponse.class); } /** diff --git a/src/main/java/com/resend/services/batch/model/AbstractBatchEmailsResponse.java b/src/main/java/com/resend/services/batch/model/AbstractBatchEmailsResponse.java new file mode 100644 index 0000000..45f59ab --- /dev/null +++ b/src/main/java/com/resend/services/batch/model/AbstractBatchEmailsResponse.java @@ -0,0 +1,47 @@ +package com.resend.services.batch.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; + +import java.util.List; + +/** + * Base class for batch email responses. + * The actual implementation depends on the validation mode used. + */ +@JsonSubTypes({ + @JsonSubTypes.Type(CreateBatchEmailsResponse.class) +}) +public abstract class AbstractBatchEmailsResponse { + + /** + * The list of e-mail ids created. + */ + @JsonProperty("data") + protected List data; + + /** + * Default constructor. + */ + public AbstractBatchEmailsResponse() { + } + + /** + * Constructor with data. + * + * @param data A list of successfully created batch emails. + */ + public AbstractBatchEmailsResponse(final List data) { + this.data = data; + } + + /** + * Get the list of successfully created batch emails. + * + * @return A list of batch emails. + */ + public List getData() { + return data; + } + +} \ No newline at end of file diff --git a/src/main/java/com/resend/services/batch/model/BatchError.java b/src/main/java/com/resend/services/batch/model/BatchError.java new file mode 100644 index 0000000..5b88d6e --- /dev/null +++ b/src/main/java/com/resend/services/batch/model/BatchError.java @@ -0,0 +1,50 @@ +package com.resend.services.batch.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a validation error for a batch email in permissive mode. + */ +public class BatchError { + + @JsonProperty("index") + private Integer index; + + @JsonProperty("message") + private String message; + + /** + * Default constructor. + */ + public BatchError() { + } + + /** + * Constructor with index and message. + * + * @param index The index of the email in the batch request that failed validation. + * @param message The error message identifying the validation error. + */ + public BatchError(final Integer index, final String message) { + this.index = index; + this.message = message; + } + + /** + * Get the index of the email in the batch request that failed validation. + * + * @return The index of the failed email. + */ + public Integer getIndex() { + return index; + } + + /** + * Get the error message identifying the validation error. + * + * @return The error message. + */ + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/src/main/java/com/resend/services/batch/model/CreateBatchEmailsResponse.java b/src/main/java/com/resend/services/batch/model/CreateBatchEmailsResponse.java index 7b61743..9f47bf3 100644 --- a/src/main/java/com/resend/services/batch/model/CreateBatchEmailsResponse.java +++ b/src/main/java/com/resend/services/batch/model/CreateBatchEmailsResponse.java @@ -5,35 +5,57 @@ import java.util.List; /** - * Represents the response for creating batch emails. + * Response for batch emails sent in permissive mode. + * In permissive mode, partial success is allowed and validation errors are returned. */ -public class CreateBatchEmailsResponse { +public class CreateBatchEmailsResponse extends AbstractBatchEmailsResponse { - @JsonProperty("data") - private List data; + @JsonProperty("errors") + private List errors; /** * Default constructor. */ public CreateBatchEmailsResponse() { + super(); } /** - * Constructor with a list of batch emails. + * Constructor with data and errors. * - * @param data A list of batch emails. + * @param data A list of successfully created batch emails. + * @param errors A list of validation errors. */ - public CreateBatchEmailsResponse(final List data) { - this.data = data; + public CreateBatchEmailsResponse(final List data, final List errors) { + super(data); + this.errors = errors; } /** - * Get the list of batch emails. + * Get the list of validation errors. * - * @return A list of batch emails. + * @return A list of batch email errors, never null (but may be empty). */ - public List getData() { - return data; + public List getErrors() { + return errors; } -} + /** + * Check if the response has any validation errors. + * + * @return true if there are validation errors, false otherwise. + */ + public boolean hasErrors() { + return errors != null && !errors.isEmpty(); + } + + /** + * Get the total number of failed emails. + * + * @return The count of emails that failed validation. + */ + public int getErrorCount() { + return errors != null ? errors.size() : 0; + } + +} \ No newline at end of file diff --git a/src/test/java/com/resend/services/emails/EmailsTest.java b/src/test/java/com/resend/services/emails/EmailsTest.java index a3136a9..171272d 100644 --- a/src/test/java/com/resend/services/emails/EmailsTest.java +++ b/src/test/java/com/resend/services/emails/EmailsTest.java @@ -3,6 +3,7 @@ import com.resend.core.net.ListParams; import com.resend.core.net.RequestOptions; import com.resend.services.batch.Batch; +import com.resend.services.batch.model.AbstractBatchEmailsResponse; import com.resend.services.batch.model.CreateBatchEmailsResponse; import com.resend.services.emails.model.*; import com.resend.services.util.EmailsUtil; @@ -86,6 +87,22 @@ public void testCreateBatchEmails_Success() throws ResendException { assertEquals(expectedRes.getData().size(), sendBatchEmailsResponse.getData().size()); } + @Test + public void testCreatePermissiveBatchEmails_Success() throws ResendException { + RequestOptions options = RequestOptions.builder() + .add("x-batch-validation", "permissive").build(); + + List batchEmailsRequest = EmailsUtil.createBatchEmailOptions(); + CreateBatchEmailsResponse expectedRes = EmailsUtil.createPermissiveBatchEmailsResponse(); + + when(batch.send(batchEmailsRequest, options)).thenReturn(expectedRes); + + CreateBatchEmailsResponse sendBatchEmailsResponse = batch.send(batchEmailsRequest, options); + + assertNotNull(sendBatchEmailsResponse); + assertEquals(expectedRes.getData().size(), sendBatchEmailsResponse.getData().size()); + } + @Test public void testCreateBatchEmailsWithIdempotencyKey_Success() throws ResendException { List batchEmailsRequest = EmailsUtil.createBatchEmailOptions(); @@ -94,7 +111,7 @@ public void testCreateBatchEmailsWithIdempotencyKey_Success() throws ResendExcep when(batch.send(batchEmailsRequest, requestOptions)).thenReturn(expectedRes); - CreateBatchEmailsResponse sendBatchEmailsResponse = batch.send(batchEmailsRequest, requestOptions); + AbstractBatchEmailsResponse sendBatchEmailsResponse = batch.send(batchEmailsRequest, requestOptions); assertNotNull(sendBatchEmailsResponse); assertEquals(expectedRes.getData().size(), sendBatchEmailsResponse.getData().size()); diff --git a/src/test/java/com/resend/services/util/EmailsUtil.java b/src/test/java/com/resend/services/util/EmailsUtil.java index d31ce92..8891976 100644 --- a/src/test/java/com/resend/services/util/EmailsUtil.java +++ b/src/test/java/com/resend/services/util/EmailsUtil.java @@ -2,6 +2,7 @@ import com.resend.core.net.RequestOptions; import com.resend.services.batch.model.BatchEmail; +import com.resend.services.batch.model.BatchError; import com.resend.services.batch.model.CreateBatchEmailsResponse; import com.resend.services.emails.model.*; import com.resend.core.net.AbstractHttpResponse; @@ -65,7 +66,21 @@ public static List createBatchEmailOptions() { } public static CreateBatchEmailsResponse createBatchEmailsResponse() { - return new CreateBatchEmailsResponse(Arrays.asList(new BatchEmail("123"), new BatchEmail("321"))); + return new CreateBatchEmailsResponse(Arrays.asList(new BatchEmail("123"), new BatchEmail("321")), new ArrayList<>()); + } + + public static CreateBatchEmailsResponse createPermissiveBatchEmailsResponse() { + List successes = Arrays.asList( + new BatchEmail("123"), + new BatchEmail("321") + ); + + List errors = Arrays.asList( + new BatchError(456, "Invalid recipient address"), + new BatchError(789, "Domain not reachable") + ); + + return new CreateBatchEmailsResponse(successes, errors); } public static Email createTestEmail() {