JDK7TypesTest.java
package tools.jackson.databind.deser.jdk;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.jdk.JDKFromStringDeserializer.NioPathDeserializer;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;
import static org.junit.jupiter.api.Assertions.*;
public class JDK7TypesTest extends DatabindTestUtil
{
private boolean isWindows() {
return System.getProperty("os.name").contains("Windows");
}
@Test
public void testPathRoundTrip() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Path input = Path.of(isWindows() ? "c:/tmp" : "/tmp", "foo.txt");
String json = mapper.writeValueAsString(input);
assertNotNull(json);
Path p = mapper.readValue(json, Path.class);
assertNotNull(p);
assertEquals(input.toUri(), p.toUri());
assertEquals(input.toAbsolutePath(), p.toAbsolutePath());
}
// [databind#6129]: Only accept "file" scheme or no scheme; reject jar:, http:, s3:, etc.
@Test
public void testRejectNonFileSchemes() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// Bare path (no scheme) should work
assertNotNull(mapper.readValue(q(isWindows() ? "c:/tmp/foo.txt" : "/tmp/foo.txt"),
Path.class));
// but non-"file" schemes should not
_verifyRejectScheme(mapper, "jar:http://example.com/foo.jar!/path");
_verifyRejectScheme(mapper, "http://example.com/path");
_verifyRejectScheme(mapper, "s3://bucket/key");
_verifyRejectScheme(mapper, "custom://something");
// and message should list schemes that are allowed
try {
mapper.readValue(q("s3://bucket/key"), Path.class);
fail("Should not pass");
} catch (Exception e) {
verifyException(e, "scheme 's3' not allowed");
verifyException(e, "allowed: [\"file\"]");
}
}
// [databind#6129]: scheme matching case-insensitive, as by `Path.of(URI)` itself
@Test
public void testAllowedSchemeCaseInsensitive() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
final Path input = Path.of(isWindows() ? "c:/tmp" : "/tmp", "foo.txt");
// Construct "file:" URI the portable way, then vary case of the scheme
final String uriStr = input.toUri().toString();
for (String scheme : new String[] { "FILE", "FiLe" }) {
String json = q(scheme + uriStr.substring(4));
Path p = mapper.readValue(json, Path.class);
assertNotNull(p);
assertEquals(input.toAbsolutePath(), p.toAbsolutePath());
}
}
// [databind#6129]: allowed schemes may be overridden
@Test
public void testCustomAllowedSchemes() throws Exception
{
final Path input = Path.of(isWindows() ? "c:/tmp" : "/tmp", "foo.txt");
final String fileUri = input.toUri().toString();
// Case-insensitive both ways: allow-list entry in upper case, input in lower
ObjectMapper mapper = _mapperWithSchemes(Arrays.asList("FILE"));
Path p = mapper.readValue(q(fileUri), Path.class);
assertNotNull(p);
assertEquals(input.toAbsolutePath(), p.toAbsolutePath());
// But if "file" not included, it gets rejected like any other scheme
mapper = _mapperWithSchemes(Arrays.asList("jar", "jrt"));
try {
mapper.readValue(q(fileUri), Path.class);
fail("Should not pass");
} catch (Exception e) {
verifyException(e, "scheme 'file' not allowed");
verifyException(e, "allowed: [\"jar\", \"jrt\"]");
}
// Scheme-less values, however, are always accepted
assertNotNull(mapper.readValue(q(isWindows() ? "c:/tmp/foo.txt" : "/tmp/foo.txt"),
Path.class));
// and empty allow-list is legal, if unhelpful
_verifyRejectScheme(_mapperWithSchemes(Collections.emptyList()), fileUri);
}
// [databind#6129]: allowed scheme with no provider installed must go through
// provider look up (see [databind#2120]) and fail with the original cause --
// and NOT with `ServiceConfigurationError` (see `module-info.java` `uses`)
@Test
public void testAllowedSchemeWithNoProvider() throws Exception
{
ObjectMapper mapper = _mapperWithSchemes(Arrays.asList("jimfs"));
try {
mapper.readValue(q("jimfs://bucket/foo.txt"), Path.class);
fail("Should not pass");
} catch (DatabindException e) {
verifyException(e, "Provider \"jimfs\" not installed");
}
}
@Test
public void testNullAllowedSchemes() throws Exception
{
try {
new NioPathDeserializer(null);
fail("Should not pass");
} catch (IllegalArgumentException e) {
verifyException(e, "`allowedSchemes` must not be null");
}
}
// NOTE: cast needed since `JDKFromStringDeserializer` is typed as
// `ValueDeserializer<Object>`, not `ValueDeserializer<Path>`
@SuppressWarnings("unchecked")
private ObjectMapper _mapperWithSchemes(Collection<String> allowedSchemes) {
SimpleModule module = new SimpleModule();
module.addDeserializer(Path.class,
(ValueDeserializer<Path>) (ValueDeserializer<?>) new NioPathDeserializer(allowedSchemes));
return jsonMapperBuilder().addModule(module).build();
}
private void _verifyRejectScheme(ObjectMapper mapper, String input) {
try {
mapper.readValue(q(input), Path.class);
fail("Should have thrown for scheme in: " + input);
} catch (Exception e) {
// expected - handleWeirdStringValue will throw by default
verifyException(e, "not allowed for Path deserialization");
}
}
// [databind#1688]
@Test
public void testPolymorphicPath() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.activateDefaultTyping(NoCheckSubTypeValidator.instance,
DefaultTyping.NON_FINAL)
.build();
Path input = Path.of(isWindows() ? "c:/tmp" : "/tmp", "foo.txt");
String json = mapper.writeValueAsString(new Object[]{input});
Object[] obs = mapper.readValue(json, Object[].class);
assertEquals(1, obs.length);
Object ob = obs[0];
if (!(ob instanceof Path)) {
fail("Should deserialize as `Path`, got: `" + ob.getClass().getName() + "`");
}
assertEquals(input.toAbsolutePath().toString(), ob.toString());
}
}