Skip to content

Commit 2fc6bd2

Browse files
oschwaldclaude
andcommitted
Add new email domain fields to minFraud response
This commit adds support for new email domain outputs from minFraud Insights and Factors services: - Added classification field (business, education, government, isp_email) - Added risk field (score from 0.01 to 99) - Added volume field (sightings per million) - Added visit object with status, last_visited_on, and has_redirect fields New classes: - EmailDomainVisit record with Status enum Updated classes: - EmailDomain record with Classification enum and new fields - Mapper configured for forward-compatible enum deserialization Enums use a simple pattern with toString() override and Jackson config to handle unknown values gracefully (returns null instead of throwing). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c216620 commit 2fc6bd2

File tree

11 files changed

+537
-5
lines changed

11 files changed

+537
-5
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ CHANGELOG
5959
* Added the input `/payment/method`. This is the payment method associated
6060
with the transaction. You may provide this using the `method` method on
6161
`Payment.Builder`.
62+
* Added new email domain fields to the `EmailDomain` response model:
63+
* `classification` - A classification of the email domain. Possible values
64+
are `BUSINESS`, `EDUCATION`, `GOVERNMENT`, and `ISP_EMAIL`.
65+
* `risk` - A risk score associated with the email domain, ranging from 0.01
66+
to 99. Higher scores indicate higher risk.
67+
* `volume` - The activity on the email domain across the minFraud network,
68+
expressed in sightings per million. This value ranges from 0.001 to
69+
1,000,000.
70+
* `visit` - An `EmailDomainVisit` object containing information about an
71+
automated visit to the email domain, including:
72+
* `status` - The status of the domain based on the automated visit.
73+
Possible values are `LIVE`, `DNS_ERROR`, `NETWORK_ERROR`, `HTTP_ERROR`,
74+
`PARKED`, and `PRE_DEVELOPMENT`.
75+
* `lastVisitedOn` - The date when the automated visit was last completed.
76+
* `hasRedirect` - Whether the domain redirects to another URL.
77+
* Added support for forward-compatible enum deserialization. Enums in response
78+
models will now return `null` for unknown values instead of throwing an
79+
exception. This allows the client to handle new enum values added by the
80+
server without requiring an immediate client update. This required adding
81+
`READ_ENUMS_USING_TO_STRING` and `READ_UNKNOWN_ENUM_VALUES_AS_NULL` to the
82+
Jackson `ObjectMapper` configuration.
6283

6384
3.9.0
6485
------------------

CLAUDE.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,46 @@ public record ScoreResponse(...) {
157157

158158
This ensures users can safely call methods without null checks: `response.disposition().action()`.
159159

160+
#### Enum Pattern for Response Models
161+
162+
Response model enums use a simple, forward-compatible pattern that gracefully handles unknown values from the server.
163+
164+
**Pattern:**
165+
```java
166+
public enum Status {
167+
LIVE,
168+
PARKED,
169+
DNS_ERROR;
170+
171+
@Override
172+
public String toString() {
173+
return name().toLowerCase();
174+
}
175+
}
176+
```
177+
178+
**How it works:**
179+
- Enum constants use `UPPER_SNAKE_CASE` (e.g., `DNS_ERROR`, `ISP_EMAIL`)
180+
- Override `toString()` to return `name().toLowerCase()` for JSON serialization
181+
- The `Mapper` class configures Jackson with:
182+
- `READ_ENUMS_USING_TO_STRING` - Deserializes using `toString()`
183+
- `READ_UNKNOWN_ENUM_VALUES_AS_NULL` - Unknown values become `null` instead of throwing exceptions
184+
185+
**Example:**
186+
```java
187+
// Serialization: DNS_ERROR → "dns_error"
188+
Status status = Status.DNS_ERROR;
189+
System.out.println(status); // "dns_error"
190+
191+
// Deserialization: "dns_error" → DNS_ERROR
192+
// Unknown: "future_value" → null (no exception!)
193+
```
194+
195+
**When to use:**
196+
- Use enums for response fields with fixed/enumerated values
197+
- Use String for open-ended text fields
198+
- Request enums use the same pattern but don't need forward compatibility concerns
199+
160200
### Deprecation Strategy
161201

162202
**Do NOT add deprecated getter methods for new fields.** Deprecated getters only exist for backward compatibility with fields that had JavaBeans-style getters before the record migration in version 4.0.0.

src/main/java/com/maxmind/minfraud/Mapper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class Mapper {
1414
.addModule(new JavaTimeModule())
1515
.defaultDateFormat(new StdDateFormat().withColonInTimeZone(true))
1616
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
17+
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
18+
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
1719
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
1820
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
1921
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

src/main/java/com/maxmind/minfraud/response/EmailDomain.java

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,78 @@
77
/**
88
* This class contains minFraud response data related to the email domain.
99
*
10-
* @param firstSeen A date to identify the date an email domain was first seen by MaxMind.
10+
* @param classification A classification of the email domain. Possible values are: business,
11+
* education, government, isp_email.
12+
* @param firstSeen The date an email domain was first seen by MaxMind.
13+
* @param risk A risk score associated with the email domain, ranging from 0.01 to 99.
14+
* Higher scores indicate higher risk.
15+
* @param visit An {@code EmailDomainVisit} object containing information about an
16+
* automated visit to the email domain.
17+
* @param volume The activity on the email domain across the minFraud network, expressed in
18+
* sightings per million. This value ranges from 0.001 to 1,000,000.
1119
*/
1220
public record EmailDomain(
21+
@JsonProperty("classification")
22+
Classification classification,
23+
1324
@JsonProperty("first_seen")
14-
LocalDate firstSeen
25+
LocalDate firstSeen,
26+
27+
@JsonProperty("risk")
28+
Double risk,
29+
30+
@JsonProperty("visit")
31+
EmailDomainVisit visit,
32+
33+
@JsonProperty("volume")
34+
Double volume
1535
) implements JsonSerializable {
1636

37+
/**
38+
* The classification of an email domain.
39+
*/
40+
public enum Classification {
41+
/**
42+
* A business email domain.
43+
*/
44+
BUSINESS,
45+
46+
/**
47+
* An educational institution email domain.
48+
*/
49+
EDUCATION,
50+
51+
/**
52+
* A government email domain.
53+
*/
54+
GOVERNMENT,
55+
56+
/**
57+
* An ISP-provided email domain (e.g., gmail.com, yahoo.com).
58+
*/
59+
ISP_EMAIL;
60+
61+
/**
62+
* @return a string representation of the classification in lowercase with underscores.
63+
*/
64+
@Override
65+
public String toString() {
66+
return name().toLowerCase();
67+
}
68+
}
69+
70+
/**
71+
* Compact canonical constructor that sets defaults for null values.
72+
*/
73+
public EmailDomain {
74+
visit = visit != null ? visit : new EmailDomainVisit();
75+
}
76+
1777
/**
1878
* Constructs an instance of {@code EmailDomain} with no data.
1979
*/
2080
public EmailDomain() {
21-
this(null);
81+
this(null, null, null, null, null);
2282
}
2383

2484
/**
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.maxmind.minfraud.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.maxmind.minfraud.JsonSerializable;
5+
import java.time.LocalDate;
6+
7+
/**
8+
* This class contains information about an automated visit to the email domain.
9+
*
10+
* @param hasRedirect Whether the domain redirects to another URL. This field is only present if
11+
* the value is true.
12+
* @param lastVisitedOn The date when the automated visit was last completed.
13+
* @param status The status of the domain based on the automated visit. Possible values are:
14+
* live, dns_error, network_error, http_error, parked, pre_development.
15+
*/
16+
public record EmailDomainVisit(
17+
@JsonProperty("has_redirect")
18+
Boolean hasRedirect,
19+
20+
@JsonProperty("last_visited_on")
21+
LocalDate lastVisitedOn,
22+
23+
@JsonProperty("status")
24+
Status status
25+
) implements JsonSerializable {
26+
27+
/**
28+
* The status of an email domain based on an automated visit.
29+
*/
30+
public enum Status {
31+
/**
32+
* The domain is live and responding normally.
33+
*/
34+
LIVE,
35+
36+
/**
37+
* A DNS error occurred when attempting to visit the domain.
38+
*/
39+
DNS_ERROR,
40+
41+
/**
42+
* A network error occurred when attempting to visit the domain.
43+
*/
44+
NETWORK_ERROR,
45+
46+
/**
47+
* An HTTP error occurred when attempting to visit the domain.
48+
*/
49+
HTTP_ERROR,
50+
51+
/**
52+
* The domain is parked.
53+
*/
54+
PARKED,
55+
56+
/**
57+
* The domain is in pre-development.
58+
*/
59+
PRE_DEVELOPMENT;
60+
61+
/**
62+
* @return a string representation of the status in lowercase with underscores.
63+
*/
64+
@Override
65+
public String toString() {
66+
return name().toLowerCase();
67+
}
68+
}
69+
70+
/**
71+
* Constructs an instance of {@code EmailDomainVisit} with no data.
72+
*/
73+
public EmailDomainVisit() {
74+
this(null, null, null);
75+
}
76+
}

src/test/java/com/maxmind/minfraud/WebServiceClientTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.hamcrest.core.StringStartsWith.startsWith;
1919
import static org.junit.jupiter.api.Assertions.assertEquals;
2020
import static org.junit.jupiter.api.Assertions.assertFalse;
21+
import static org.junit.jupiter.api.Assertions.assertNotNull;
2122
import static org.junit.jupiter.api.Assertions.assertThrows;
2223
import static org.junit.jupiter.api.Assertions.assertTrue;
2324

@@ -28,6 +29,9 @@
2829
import com.maxmind.minfraud.exception.InsufficientFundsException;
2930
import com.maxmind.minfraud.exception.InvalidRequestException;
3031
import com.maxmind.minfraud.exception.MinFraudException;
32+
import com.maxmind.minfraud.response.EmailDomain;
33+
import com.maxmind.minfraud.response.EmailDomainVisit;
34+
import java.time.LocalDate;
3135
import com.maxmind.minfraud.exception.PermissionRequiredException;
3236
import com.maxmind.minfraud.request.Device;
3337
import com.maxmind.minfraud.request.Shipping;
@@ -118,6 +122,15 @@ public void testFullInsightsTransaction() throws Exception {
118122

119123
assertTrue(response.creditCard().isVirtual());
120124

125+
// Test email domain fields
126+
assertEquals(EmailDomain.Classification.EDUCATION, response.email().domain().classification());
127+
assertEquals(15.5, response.email().domain().risk());
128+
assertEquals(630000.0, response.email().domain().volume());
129+
assertNotNull(response.email().domain().visit());
130+
assertEquals(EmailDomainVisit.Status.LIVE, response.email().domain().visit().status());
131+
assertTrue(response.email().domain().visit().hasRedirect());
132+
assertEquals(LocalDate.parse("2024-11-15"), response.email().domain().visit().lastVisitedOn());
133+
121134
var reasons = response.ipAddress().riskReasons();
122135

123136
assertEquals(2, reasons.size(), "two IP risk reasons");
@@ -153,6 +166,14 @@ public void testFullFactorsTransaction() throws Exception {
153166
"response.ipAddress().representedCountry().isInEuropeanUnion() does not return false"
154167
);
155168

169+
// Test email domain fields
170+
assertEquals(EmailDomain.Classification.ISP_EMAIL, response.email().domain().classification());
171+
assertEquals(25.0, response.email().domain().risk());
172+
assertEquals(500000.5, response.email().domain().volume());
173+
assertNotNull(response.email().domain().visit());
174+
assertEquals(EmailDomainVisit.Status.PARKED, response.email().domain().visit().status());
175+
assertFalse(response.email().domain().visit().hasRedirect());
176+
assertEquals(LocalDate.parse("2024-10-20"), response.email().domain().visit().lastVisitedOn());
156177

157178
assertEquals("152.216.7.110", response.ipAddress().traits().ipAddress().getHostAddress());
158179
assertEquals("81.2.69.0/24",

src/test/java/com/maxmind/minfraud/response/AbstractOutputTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ <T> T deserialize(Class<T> cls, String json) throws IOException {
2020
.addModule(new JavaTimeModule())
2121
.defaultDateFormat(new StdDateFormat().withColonInTimeZone(true))
2222
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
23+
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
24+
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
2325
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
2426
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
2527
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

0 commit comments

Comments
 (0)