Skip to content

Commit 632304f

Browse files
committed
Enhanced validation taking base into account for batch send, added unit-tests with base
1 parent f752d2e commit 632304f

File tree

15 files changed

+526
-84
lines changed

15 files changed

+526
-84
lines changed

src/main/java/io/mailtrap/api/apiresource/SendApiResource.java

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import io.mailtrap.CustomValidator;
44
import io.mailtrap.config.MailtrapConfig;
55
import io.mailtrap.exception.InvalidRequestBodyException;
6+
import io.mailtrap.model.mailvalidation.ContentView;
7+
import io.mailtrap.model.mailvalidation.MailContentView;
8+
import io.mailtrap.model.mailvalidation.ResolvedMailContentView;
9+
import io.mailtrap.model.mailvalidation.ResolvedMailView;
10+
import io.mailtrap.model.request.emails.BatchEmailBase;
611
import io.mailtrap.model.request.emails.MailtrapBatchMail;
712
import io.mailtrap.model.request.emails.MailtrapMail;
813
import org.apache.commons.collections4.MapUtils;
@@ -19,74 +24,93 @@ protected SendApiResource(MailtrapConfig config, CustomValidator customValidator
1924
super(config, customValidator);
2025
}
2126

22-
protected void assertBatchMailNotNull(MailtrapBatchMail batchMail) {
23-
if (batchMail == null) {
24-
throw new InvalidRequestBodyException("BatchMail must not be null");
25-
}
26-
if (batchMail.getRequests() == null || batchMail.getRequests().isEmpty()) {
27-
throw new InvalidRequestBodyException("BatchMail.requests must not be null or empty");
28-
}
29-
if (batchMail.getRequests().stream().anyMatch(Objects::isNull)) {
30-
throw new InvalidRequestBodyException("BatchMail.requests must not contain null items");
31-
}
32-
}
33-
3427
/**
3528
* Validates the request body of an email message and throws an exception if it is invalid.
3629
*
3730
* @param mail The email message to be validated.
3831
* @throws InvalidRequestBodyException If the request body is invalid.
3932
*/
40-
protected void validateMailPayload(MailtrapMail mail) throws InvalidRequestBodyException {
41-
// Check if the mail object itself is null
33+
protected void validateMailPayload(MailtrapMail mail) {
4234
if (mail == null) {
4335
throw new InvalidRequestBodyException("Mail must not be null");
4436
}
4537

46-
// Check if all three subject, text, and html are empty
47-
boolean isSubjectTextHtmlEmpty = StringUtils.isBlank(mail.getSubject())
48-
&& StringUtils.isBlank(mail.getText())
49-
&& StringUtils.isBlank(mail.getHtml());
50-
51-
// Validate depending on whether the templateUuid is set
52-
if (StringUtils.isEmpty(mail.getTemplateUuid())) {
53-
// Validation for the scenario where templateUuid is not provided
54-
validateWithoutTemplate(mail, isSubjectTextHtmlEmpty);
55-
} else {
56-
// Validation for the scenario where templateUuid is provided
57-
validateWithTemplate(mail);
58-
}
59-
60-
// Additional validation logic (assumed to be provided by the user)
38+
// Perform bean validation (NotNull, etc.)
6139
validateRequestBodyAndThrowException(mail);
40+
41+
// Validate subject/text/html/templateUuid
42+
validateContentRules(MailContentView.of(mail));
6243
}
6344

64-
private void validateWithoutTemplate(MailtrapMail mail, boolean isSubjectTextHtmlEmpty) throws InvalidRequestBodyException {
65-
// Ensure that at least subject, text, or html is provided if templateUuid is not set
66-
if (isSubjectTextHtmlEmpty) {
67-
throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
68-
}
45+
/**
46+
* Validates the request body of batch email and throws an exception if it is invalid.
47+
*
48+
* @param batch batch request to be validated.
49+
* @throws InvalidRequestBodyException If the request body is invalid.
50+
*/
51+
protected void validateBatchPayload(MailtrapBatchMail batch) {
52+
assertBatchMailNotNull(batch);
6953

70-
// Ensure templateVariables are not used if templateUuid is not set
71-
if (MapUtils.isNotEmpty(mail.getTemplateVariables())) {
72-
throw new InvalidRequestBodyException("Mail templateVariables must only be used with templateUuid");
73-
}
54+
BatchEmailBase base = batch.getBase();
55+
56+
for (int i = 0; i < batch.getRequests().size(); i++) {
57+
MailtrapMail mail = batch.getRequests().get(i);
58+
ResolvedMailView mailView = new ResolvedMailView(base, mail);
59+
60+
try {
61+
// Perform bean validation (NotNull, etc.)
62+
validateRequestBodyAndThrowException(mailView);
63+
} catch (InvalidRequestBodyException e) {
64+
throw new InvalidRequestBodyException("requests[" + i + "]: " + e.getMessage(), e);
65+
}
66+
67+
validateContentRules(ResolvedMailContentView.of(mailView));
7468

75-
// Ensure the subject is not empty
76-
if (StringUtils.isBlank(mail.getSubject())) {
77-
throw new InvalidRequestBodyException("Subject must not be null or empty");
69+
if (mailView.getFrom() == null) {
70+
throw new InvalidRequestBodyException("requests[" + i + "]: from is required (either in mail or base)");
71+
}
7872
}
73+
}
7974

80-
// Ensure at least one of text or html is present
81-
if (StringUtils.isBlank(mail.getText()) && StringUtils.isBlank(mail.getHtml())) {
82-
throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
75+
private void assertBatchMailNotNull(MailtrapBatchMail batchMail) {
76+
if (batchMail == null) {
77+
throw new InvalidRequestBodyException("BatchMail must not be null");
8378
}
79+
if (batchMail.getRequests() == null || batchMail.getRequests().isEmpty()) {
80+
throw new InvalidRequestBodyException("BatchMail.requests must not be null or empty");
81+
}
82+
if (batchMail.getRequests().stream().anyMatch(Objects::isNull)) {
83+
throw new InvalidRequestBodyException("BatchMail.requests must not contain null items");
84+
}
85+
8486
}
8587

86-
private void validateWithTemplate(MailtrapMail mail) throws InvalidRequestBodyException {
87-
// Ensure that subject, text, and html are not used when templateUuid is set
88-
if (StringUtils.isNotEmpty(mail.getText()) || StringUtils.isNotEmpty(mail.getHtml()) || StringUtils.isNotEmpty(mail.getSubject())) {
89-
throw new InvalidRequestBodyException("When templateUuid is used, subject, text, and html must not be used");
88+
private void validateContentRules(ContentView v) {
89+
boolean templateUuidBlank = StringUtils.isBlank(v.getTemplateUuid());
90+
91+
boolean subjectTextHtmlEmpty = StringUtils.isBlank(v.getSubject())
92+
&& StringUtils.isBlank(v.getText())
93+
&& StringUtils.isBlank(v.getHtml());
94+
95+
if (templateUuidBlank) {
96+
if (subjectTextHtmlEmpty) {
97+
throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
98+
}
99+
if (MapUtils.isNotEmpty(v.getTemplateVariables())) {
100+
throw new InvalidRequestBodyException("Mail templateVariables must only be used with templateUuid");
101+
}
102+
if (StringUtils.isBlank(v.getSubject())) {
103+
throw new InvalidRequestBodyException("Subject must not be null or empty");
104+
}
105+
if (StringUtils.isBlank(v.getText()) && StringUtils.isBlank(v.getHtml())) {
106+
throw new InvalidRequestBodyException("Mail must have subject and either text or html when templateUuid is not provided");
107+
}
108+
} else {
109+
if (StringUtils.isNotEmpty(v.getSubject())
110+
|| StringUtils.isNotEmpty(v.getText())
111+
|| StringUtils.isNotEmpty(v.getHtml()))
112+
throw new InvalidRequestBodyException("When templateUuid is used, subject, text, and html must not be used");
90113
}
91114
}
115+
92116
}

src/main/java/io/mailtrap/api/bulkemails/BulkEmailsImpl.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ public SendResponse send(MailtrapMail mail) {
3434

3535
@Override
3636
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
37-
assertBatchMailNotNull(mail);
38-
39-
mail
40-
.getRequests()
41-
.forEach(this::validateMailPayload);
37+
validateBatchPayload(mail);
4238

4339
return
4440
httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);

src/main/java/io/mailtrap/api/sendingemails/SendingEmailsImpl.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ public SendResponse send(MailtrapMail mail) {
3434

3535
@Override
3636
public BatchSendResponse batchSend(MailtrapBatchMail mail) throws HttpException, InvalidRequestBodyException {
37-
assertBatchMailNotNull(mail);
38-
39-
mail
40-
.getRequests()
41-
.forEach(this::validateMailPayload);
37+
validateBatchPayload(mail);
4238

4339
return
4440
httpClient.post(apiHost + "/api/batch", mail, new RequestData(), BatchSendResponse.class);

src/main/java/io/mailtrap/api/testingemails/TestingEmailsImpl.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ public SendResponse send(MailtrapMail mail, long inboxId) {
3434

3535
@Override
3636
public BatchSendResponse batchSend(MailtrapBatchMail mail, long inboxId) throws HttpException, InvalidRequestBodyException {
37-
assertBatchMailNotNull(mail);
38-
39-
mail
40-
.getRequests()
41-
.forEach(this::validateMailPayload);
37+
validateBatchPayload(mail);
4238

4339
return
4440
httpClient.post(String.format(apiHost + "/api/batch/%d", inboxId), mail, new RequestData(), BatchSendResponse.class);

src/main/java/io/mailtrap/exception/InvalidRequestBodyException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ public InvalidRequestBodyException(String errorMessage) {
99
super(errorMessage);
1010
}
1111

12+
public InvalidRequestBodyException(String errorMessage, Exception cause) {
13+
super(errorMessage, cause);
14+
}
15+
1216
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.mailtrap.model.mailvalidation;
2+
3+
import java.util.Map;
4+
5+
/**
6+
* An adapter used by mail content validation rules. It exposes only the fields that participate in
7+
* subject/text/html/template logic so the same rules can run for both single and batch sends.
8+
*/
9+
public interface ContentView {
10+
11+
String getSubject();
12+
13+
String getText();
14+
15+
String getHtml();
16+
17+
String getTemplateUuid();
18+
19+
Map<String, Object> getTemplateVariables();
20+
21+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.mailtrap.model.mailvalidation;
2+
3+
import io.mailtrap.model.request.emails.MailtrapMail;
4+
5+
import java.util.Map;
6+
7+
/**
8+
* ContentView implementation for a single-send MailtrapMail.
9+
*/
10+
public final class MailContentView implements ContentView {
11+
private final MailtrapMail mail;
12+
13+
public String getSubject() {
14+
return mail.getSubject();
15+
}
16+
17+
public String getText() {
18+
return mail.getText();
19+
}
20+
21+
public String getHtml() {
22+
return mail.getHtml();
23+
}
24+
25+
public String getTemplateUuid() {
26+
return mail.getTemplateUuid();
27+
}
28+
29+
public Map<String, Object> getTemplateVariables() {
30+
return mail.getTemplateVariables();
31+
}
32+
33+
public static ContentView of(MailtrapMail content) {
34+
return new MailContentView(content);
35+
}
36+
37+
private MailContentView(MailtrapMail content) {
38+
this.mail = content;
39+
}
40+
}
41+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.mailtrap.model.mailvalidation;
2+
3+
import java.util.Map;
4+
5+
/**
6+
* ContentView implementation for a batch item via ResolvedMailView. Exposes the already-resolved (item-first, base-fallback)
7+
* values to the shared content rules.
8+
*/
9+
public final class ResolvedMailContentView implements ContentView {
10+
private final ResolvedMailView mailView;
11+
12+
public String getSubject() {
13+
return mailView.getSubject();
14+
}
15+
16+
public String getText() {
17+
return mailView.getText();
18+
}
19+
20+
public String getHtml() {
21+
return mailView.getHtml();
22+
}
23+
24+
public String getTemplateUuid() {
25+
return mailView.getTemplateUuid();
26+
}
27+
28+
public Map<String, Object> getTemplateVariables() {
29+
return mailView.getTemplateVariables();
30+
}
31+
32+
public static ContentView of(ResolvedMailView mailView) {
33+
return new ResolvedMailContentView(mailView);
34+
}
35+
36+
private ResolvedMailContentView(ResolvedMailView mailView) {
37+
this.mailView = mailView;
38+
}
39+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package io.mailtrap.model.mailvalidation;
2+
3+
import io.mailtrap.model.request.emails.Address;
4+
import io.mailtrap.model.request.emails.BatchEmailBase;
5+
import io.mailtrap.model.request.emails.EmailAttachment;
6+
import io.mailtrap.model.request.emails.MailtrapMail;
7+
import jakarta.validation.Valid;
8+
import jakarta.validation.constraints.NotEmpty;
9+
import jakarta.validation.constraints.NotNull;
10+
import org.apache.commons.lang3.StringUtils;
11+
12+
import java.util.List;
13+
import java.util.Map;
14+
15+
/**
16+
* Read-only overlay that resolves the effective values (either from `base` or mail item) for batch request validation
17+
*/
18+
public final class ResolvedMailView {
19+
20+
// may be null
21+
private final BatchEmailBase base;
22+
private final MailtrapMail item;
23+
24+
public ResolvedMailView(BatchEmailBase base, MailtrapMail item) {
25+
this.base = base;
26+
this.item = item;
27+
}
28+
29+
@NotNull
30+
@Valid
31+
public Address getFrom() {
32+
Address from = item.getFrom();
33+
34+
if (from == null && base != null) {
35+
from = base.getFrom();
36+
}
37+
38+
return from;
39+
}
40+
41+
public String getSubject() {
42+
String subject = item.getSubject();
43+
44+
if (StringUtils.isBlank(subject) && base != null) {
45+
subject = base.getSubject();
46+
}
47+
48+
return subject;
49+
}
50+
51+
public String getText() {
52+
String text = item.getText();
53+
54+
if (StringUtils.isBlank(text) && base != null) {
55+
text = base.getText();
56+
}
57+
58+
return text;
59+
}
60+
61+
public String getHtml() {
62+
String html = item.getHtml();
63+
64+
if (StringUtils.isBlank(html) && base != null) {
65+
html = base.getHtml();
66+
}
67+
68+
return html;
69+
}
70+
71+
public String getTemplateUuid() {
72+
String templateUuid = item.getTemplateUuid();
73+
74+
if (StringUtils.isBlank(templateUuid) && base != null) {
75+
templateUuid = base.getTemplateUuid();
76+
}
77+
78+
return templateUuid;
79+
}
80+
81+
public Map<String, Object> getTemplateVariables() {
82+
Map<String, Object> templateVariables = item.getTemplateVariables();
83+
84+
if ((templateVariables == null || templateVariables.isEmpty()) && base != null) {
85+
templateVariables = base.getTemplateVariables();
86+
}
87+
88+
return templateVariables;
89+
}
90+
}

0 commit comments

Comments
 (0)