Skip to content

Commit abe97a2

Browse files
committed
[REFACTOR] simplify JsonInjector and NetworkDeserializer with safer parsing, validation, and created test classes as well
1 parent 8c522b4 commit abe97a2

File tree

4 files changed

+183
-35
lines changed

4 files changed

+183
-35
lines changed

src/main/java/com/maxmind/geoip2/JsonInjector.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,31 @@
55
import com.fasterxml.jackson.databind.InjectableValues;
66
import com.maxmind.db.Network;
77
import com.maxmind.geoip2.record.Traits;
8+
import java.util.HashMap;
89
import java.util.List;
10+
import java.util.Map;
911

10-
class JsonInjector extends InjectableValues {
11-
private final List<String> locales;
12-
private final String ip;
13-
private final Network network;
12+
final class JsonInjector extends InjectableValues {
13+
private final Map<String, Object> injectables;
1414

15-
public JsonInjector(List<String> locales, String ip, Network network) {
16-
this.locales = locales;
17-
this.ip = ip;
18-
this.network = network;
15+
JsonInjector(List<String> locales, String ip, Network network) {
16+
var map = new HashMap<String, Object>(4, 1f);
17+
if (locales != null) {
18+
map.put("locales", locales);
19+
}
20+
if (ip != null) {
21+
map.put("ip_address", ip);
22+
}
23+
if (network != null) {
24+
map.put("network", network);
25+
}
26+
map.put("traits", new Traits(ip, network));
27+
this.injectables = Map.copyOf(map);
1928
}
2029

2130
@Override
2231
public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
2332
BeanProperty forProperty, Object beanInstance) {
24-
if ("locales".equals(valueId)) {
25-
return locales;
26-
}
27-
if ("ip_address".equals(valueId)) {
28-
return ip;
29-
}
30-
if ("network".equals(valueId)) {
31-
return network;
32-
}
33-
if ("traits".equals(valueId)) {
34-
return new Traits(ip, network);
35-
}
36-
return null;
33+
return (valueId instanceof String k) ? injectables.get(k) : null;
3734
}
38-
}
35+
}

src/main/java/com/maxmind/geoip2/NetworkDeserializer.java

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
/**
1212
* This class provides a deserializer for the Network class.
1313
*/
14-
public class NetworkDeserializer extends StdDeserializer<Network> {
14+
public final class NetworkDeserializer extends StdDeserializer<Network> {
1515

1616
/**
17-
* Constructs a @{code NetworkDeserializer} object.
17+
* Constructs a {@code NetworkDeserializer} with no type specified.
1818
*/
1919
public NetworkDeserializer() {
2020
this(null);
@@ -30,23 +30,46 @@ public NetworkDeserializer(Class<?> vc) {
3030
}
3131

3232
@Override
33-
public Network deserialize(
34-
JsonParser jsonparser, DeserializationContext context)
35-
throws IOException {
33+
public Network deserialize(JsonParser jsonparser, DeserializationContext context)
34+
throws IOException {
3635

37-
String cidr = jsonparser.getText();
38-
if (cidr == null) {
36+
final String cidr = jsonparser.getValueAsString();
37+
if (cidr == null || cidr.isBlank()) {
3938
return null;
4039
}
41-
String[] parts = cidr.split("/", 2);
40+
return parseCidr(cidr.trim());
41+
}
42+
43+
private static Network parseCidr(String cidr) throws IOException {
44+
final String[] parts = cidr.split("/", 2);
4245
if (parts.length != 2) {
43-
throw new RuntimeException("Invalid cidr format: " + cidr);
46+
throw new IllegalArgumentException("Invalid CIDR format: " + cidr);
4447
}
45-
int prefixLength = Integer.parseInt(parts[1]);
48+
49+
final String addrPart = parts[0].trim();
50+
final String prefixPart = parts[1].trim();
51+
52+
final InetAddress address;
4653
try {
47-
return new Network(InetAddress.getByName(parts[0]), prefixLength);
54+
address = InetAddress.getByName(addrPart);
4855
} catch (UnknownHostException e) {
49-
throw new RuntimeException(e);
56+
throw new IOException("Unknown host in CIDR: " + cidr, e);
5057
}
58+
59+
final int prefixLength;
60+
try {
61+
prefixLength = Integer.parseInt(prefixPart);
62+
} catch (NumberFormatException e) {
63+
throw new IllegalArgumentException(
64+
"Invalid prefix length in CIDR: " + cidr, e);
65+
}
66+
67+
final int maxPrefix = (address.getAddress().length == 4) ? 32 : 128;
68+
if (prefixLength < 0 || prefixLength > maxPrefix) {
69+
throw new IllegalArgumentException(
70+
"Prefix length out of range (0-" + maxPrefix + ") for CIDR: " + cidr);
71+
}
72+
73+
return new Network(address, prefixLength);
5174
}
52-
}
75+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.maxmind.geoip2;
2+
3+
import com.maxmind.db.Network;
4+
import com.maxmind.geoip2.record.Traits;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.net.InetAddress;
8+
import java.util.List;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
final class JsonInjectorTest {
13+
14+
@Test
15+
void injectsValuesAndReusesTraits() throws Exception {
16+
var locales = List.of("en", "fr");
17+
var ip = "1.2.3.4";
18+
var network = new Network(InetAddress.getByName("1.2.3.0"), 24);
19+
20+
var injector = new JsonInjector(locales, ip, network);
21+
22+
assertEquals(locales, injector.findInjectableValue("locales", null, null, null));
23+
assertEquals(ip, injector.findInjectableValue("ip_address", null, null, null));
24+
assertEquals(network, injector.findInjectableValue("network", null, null, null));
25+
26+
var traits1 = injector.findInjectableValue("traits", null, null, null);
27+
var traits2 = injector.findInjectableValue("traits", null, null, null);
28+
assertNotNull(traits1);
29+
assertSame(traits1, traits2);
30+
assertEquals(ip, ((Traits) traits1).getIpAddress());
31+
assertEquals(network, ((Traits) traits1).getNetwork());
32+
}
33+
34+
@Test
35+
void unknownOrNonStringKeyReturnsNull() {
36+
var injector = new JsonInjector(List.of(), null, null);
37+
assertNull(injector.findInjectableValue("unknown", null, null, null));
38+
assertNull(injector.findInjectableValue(123, null, null, null));
39+
}
40+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.maxmind.geoip2;
2+
3+
import com.fasterxml.jackson.core.JsonFactory;
4+
import com.fasterxml.jackson.core.JsonParser;
5+
import com.maxmind.db.Network;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.io.IOException;
9+
import java.net.InetAddress;
10+
11+
import static org.junit.jupiter.api.Assertions.*;
12+
13+
final class NetworkDeserializerTest {
14+
15+
16+
private static Network parse(String jsonString) throws IOException {
17+
var deserializer = new NetworkDeserializer();
18+
JsonFactory jf = new JsonFactory();
19+
try (JsonParser p = jf.createParser(jsonString)) {
20+
p.nextToken();
21+
return deserializer.deserialize(p, null);
22+
}
23+
}
24+
private static void assertNetwork(Network n, String addr, int prefix) throws Exception {
25+
assertNotNull(n);
26+
assertEquals(InetAddress.getByName(addr), n.getNetworkAddress());
27+
assertEquals(prefix, n.getPrefixLength());
28+
}
29+
30+
@Test
31+
void parsesValidIPv4Cidr() throws Exception {
32+
Network actual = parse("\"1.2.3.0/24\"");
33+
assertNetwork(actual, "1.2.3.0", 24);
34+
}
35+
36+
@Test
37+
void parsesValidIPv6Cidr() throws Exception {
38+
Network actual = parse("\"2001:db8::/32\"");
39+
assertNetwork(actual, "2001:db8::", 32);
40+
}
41+
42+
@Test
43+
void trimsWhitespace() throws Exception {
44+
Network actual = parse("\" 10.0.0.0/8 \"");
45+
assertNetwork(actual, "10.0.0.0", 8);
46+
}
47+
48+
49+
@Test
50+
void returnsNullOnJsonNull() throws Exception {
51+
Network actual = parse("null");
52+
assertNull(actual);
53+
}
54+
55+
@Test
56+
void returnsNullOnBlankString() throws Exception {
57+
Network actual = parse("\" \"");
58+
assertNull(actual);
59+
}
60+
61+
@Test
62+
void throwsOnMissingSlash() {
63+
assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0\""));
64+
}
65+
66+
@Test
67+
void throwsOnNonNumericPrefix() {
68+
assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0/xx\""));
69+
}
70+
71+
@Test
72+
void throwsOnOutOfRangePrefixIpv4() {
73+
assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0/64\""));
74+
assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0/-1\""));
75+
}
76+
77+
@Test
78+
void throwsOnOutOfRangePrefixIpv6() {
79+
assertThrows(IllegalArgumentException.class, () -> parse("\"::/129\""));
80+
assertThrows(IllegalArgumentException.class, () -> parse("\"::/-1\""));
81+
}
82+
83+
@Test
84+
void wrapsUnknownHostInIOException() {
85+
IOException ex = assertThrows(IOException.class, () -> parse("\"999.999.999.999/24\""));
86+
assertNotNull(ex.getCause());
87+
}
88+
}

0 commit comments

Comments
 (0)