ValidatePolymSubTypeTest.java
package com.fasterxml.jackson.databind.jsontype.vld;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Tests to verify working of customizable {@PolymorphicTypeValidator},
* see [databind#2195], regarding verification of subtype instances.
*
* @since 2.10
*/
public class ValidatePolymSubTypeTest extends DatabindTestUtil
{
// // // Value types
static abstract class BaseValue {
public int x = 3;
@Override
public boolean equals(Object other) {
return (other != null)
&& (getClass() == other.getClass())
&& ((BaseValue) other).x == this.x
;
}
}
static class BadValue extends BaseValue { }
static class GoodValue extends BaseValue { }
static class MehValue extends BaseValue { }
// // // Wrapper types
static class AnnotatedWrapper {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public BaseValue value;
protected AnnotatedWrapper() { }
public AnnotatedWrapper(BaseValue v) { value = v; }
}
static class AnnotatedMinimalWrapper {
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public BaseValue value;
protected AnnotatedMinimalWrapper() { }
public AnnotatedMinimalWrapper(BaseValue v) { value = v; }
}
static class DefTypeWrapper {
public BaseValue value;
protected DefTypeWrapper() { }
public DefTypeWrapper(BaseValue v) { value = v; }
}
// // // Validator implementations
static class SimpleNameBasedValidator extends PolymorphicTypeValidator {
private static final long serialVersionUID = 1L;
// No categoric determination, depends on subtype
@Override
public Validity validateBaseType(MapperConfig<?> ctxt, JavaType baseType) {
return Validity.INDETERMINATE;
}
@Override
public Validity validateSubClassName(MapperConfig<?> ctxt, JavaType baseType, String subClassName) {
if (subClassName.equals(BadValue.class.getName())) {
return Validity.DENIED;
}
if (subClassName.equals(GoodValue.class.getName())) {
return Validity.ALLOWED;
}
return Validity.INDETERMINATE;
}
@Override
public Validity validateSubType(MapperConfig<?> ctxt, JavaType baseType, JavaType subType) {
return Validity.DENIED;
}
}
static class SimpleClassBasedValidator extends PolymorphicTypeValidator {
private static final long serialVersionUID = 1L;
@Override
public Validity validateBaseType(MapperConfig<?> ctxt, JavaType baseType) {
return Validity.INDETERMINATE;
}
@Override
public Validity validateSubClassName(MapperConfig<?> ctxt, JavaType baseType, String subClassName) {
return Validity.INDETERMINATE;
}
@Override
public Validity validateSubType(MapperConfig<?> ctxt, JavaType baseType, JavaType subType) {
if (subType.hasRawClass(BadValue.class)) {
return Validity.DENIED;
}
if (subType.hasRawClass(GoodValue.class)) {
return Validity.ALLOWED;
}
// defaults to denied, then:
return Validity.INDETERMINATE; }
}
// // // Mappers with Default Typing
private final ObjectMapper MAPPER_DEF_TYPING_NAME_CHECK = jsonMapperBuilder()
.activateDefaultTyping(new SimpleNameBasedValidator())
.build();
private final ObjectMapper MAPPER_DEF_TYPING_CLASS_CHECK = jsonMapperBuilder()
.activateDefaultTyping(new SimpleClassBasedValidator())
.build();
// // // Mappers without Default Typing (explicit annotation needed)
private final ObjectMapper MAPPER_EXPLICIT_NAME_CHECK = jsonMapperBuilder()
.polymorphicTypeValidator(new SimpleNameBasedValidator())
.build();
private final ObjectMapper MAPPER_EXPLICIT_CLASS_CHECK = jsonMapperBuilder()
.polymorphicTypeValidator(new SimpleClassBasedValidator())
.build();
/*
/**********************************************************************
/* Test methods: default typing
/**********************************************************************
*/
// // With Name check
@Test
public void testWithDefaultTypingNameAccept() throws Exception
{
final BaseValue inputValue = new GoodValue();
DefTypeWrapper result = _roundTripDefault(MAPPER_DEF_TYPING_NAME_CHECK, inputValue);
assertEquals(inputValue, result.value);
}
@Test
public void testWithDefaultTypingNameDenyExplicit() throws Exception
{
_verifyBadDefaultValue(MAPPER_DEF_TYPING_NAME_CHECK);
}
@Test
public void testWithDefaultTypingNameDenyDefault() throws Exception
{
_verifyMehDefaultValue(MAPPER_DEF_TYPING_NAME_CHECK);
}
// // With Class check
@Test
public void testWithDefaultTypingClassAccept() throws Exception
{
final BaseValue inputValue = new GoodValue();
DefTypeWrapper result = _roundTripDefault(MAPPER_DEF_TYPING_CLASS_CHECK, inputValue);
assertEquals(inputValue, result.value);
}
@Test
public void testWithDefaultTypingClassDenyExplicit() throws Exception
{
_verifyBadDefaultValue(MAPPER_DEF_TYPING_CLASS_CHECK);
}
@Test
public void testWithDefaultTypingClassDenyDefault() throws Exception
{
_verifyMehDefaultValue(MAPPER_DEF_TYPING_CLASS_CHECK);
}
/*
/**********************************************************************
/* Test methods, annotated typing, full class
/**********************************************************************
*/
// // With Name
@Test
public void testWithAnnotationNameAccept() throws Exception
{
final BaseValue inputValue = new GoodValue();
AnnotatedWrapper result = _roundTripAnnotated(MAPPER_EXPLICIT_NAME_CHECK, inputValue);
assertEquals(inputValue, result.value);
}
@Test
public void testWithAnnotationNameDenyExplicit() throws Exception
{
_verifyBadAnnotatedValue(MAPPER_EXPLICIT_NAME_CHECK);
}
@Test
public void testWithAnnotationNameDenyDefault() throws Exception
{
_verifyMehAnnotatedValue(MAPPER_EXPLICIT_NAME_CHECK);
}
// // With Class
@Test
public void testWithAnnotationClassAccept() throws Exception
{
final BaseValue inputValue = new GoodValue();
AnnotatedWrapper result = _roundTripAnnotated(MAPPER_EXPLICIT_CLASS_CHECK, inputValue);
assertEquals(inputValue, result.value);
}
@Test
public void testWithAnnotationClassDenyExplicit() throws Exception
{
_verifyBadAnnotatedValue(MAPPER_EXPLICIT_CLASS_CHECK);
}
@Test
public void testWithAnnotationClassDenyDefault() throws Exception
{
_verifyMehAnnotatedValue(MAPPER_EXPLICIT_CLASS_CHECK);
}
/*
/**********************************************************************
/* Test methods, annotated typing, minimal class name
/**********************************************************************
*/
// // With Name
@Test
public void testWithAnnotationMinClassNameAccept() throws Exception
{
final BaseValue inputValue = new GoodValue();
AnnotatedMinimalWrapper result = _roundTripAnnotatedMinimal(MAPPER_EXPLICIT_NAME_CHECK, inputValue);
assertEquals(inputValue, result.value);
}
@Test
public void testWithAnnotationMinClassNameDenyExplicit() throws Exception
{
_verifyBadAnnotatedMinValue(MAPPER_EXPLICIT_NAME_CHECK);
}
@Test
public void testWithAnnotationMinClassNameDenyDefault() throws Exception
{
_verifyMehAnnotatedMinValue(MAPPER_EXPLICIT_NAME_CHECK);
}
// // With Class
@Test
public void testWithAnnotationMinClassClassAccept() throws Exception
{
final BaseValue inputValue = new GoodValue();
AnnotatedMinimalWrapper result = _roundTripAnnotatedMinimal(MAPPER_EXPLICIT_CLASS_CHECK, inputValue);
assertEquals(inputValue, result.value);
}
@Test
public void testWithAnnotationMinClassClassDenyExplicit() throws Exception
{
_verifyBadAnnotatedMinValue(MAPPER_EXPLICIT_CLASS_CHECK);
}
@Test
public void testWithAnnotationMinClassClassDenyDefault() throws Exception
{
_verifyMehAnnotatedMinValue(MAPPER_EXPLICIT_CLASS_CHECK);
}
/*
/**********************************************************************
/* Helper methods, round-trip (ok case)
/**********************************************************************
*/
private DefTypeWrapper _roundTripDefault(ObjectMapper mapper, BaseValue input) throws Exception {
final String json = mapper.writeValueAsString(new DefTypeWrapper(input));
return mapper.readValue(json, DefTypeWrapper.class);
}
private AnnotatedWrapper _roundTripAnnotated(ObjectMapper mapper, BaseValue input) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedWrapper(input));
return mapper.readValue(json, AnnotatedWrapper.class);
}
private AnnotatedMinimalWrapper _roundTripAnnotatedMinimal(ObjectMapper mapper, BaseValue input) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(input));
return mapper.readValue(json, AnnotatedMinimalWrapper.class);
}
/*
/**********************************************************************
/* Helper methods, failing deser verification
/**********************************************************************
*/
private void _verifyBadDefaultValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new DefTypeWrapper(new BadValue()));
_verifyBadValue(mapper, json, DefTypeWrapper.class);
}
private void _verifyMehDefaultValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new DefTypeWrapper(new MehValue()));
_verifyMehValue(mapper, json, DefTypeWrapper.class);
}
private void _verifyBadAnnotatedValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedWrapper(new BadValue()));
_verifyBadValue(mapper, json, AnnotatedWrapper.class);
}
private void _verifyMehAnnotatedValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedWrapper(new MehValue()));
_verifyMehValue(mapper, json, AnnotatedWrapper.class);
}
private void _verifyBadAnnotatedMinValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(new BadValue()));
_verifyBadValue(mapper, json, AnnotatedMinimalWrapper.class);
}
private void _verifyMehAnnotatedMinValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(new MehValue()));
_verifyMehValue(mapper, json, AnnotatedMinimalWrapper.class);
}
private void _verifyBadValue(ObjectMapper mapper, String json, Class<?> type) throws Exception {
try {
mapper.readValue(json, type);
fail("Should not pass");
} catch (InvalidTypeIdException e) {
verifyException(e, "Could not resolve type id");
verifyException(e, "`PolymorphicTypeValidator`");
verifyException(e, "denied resolution");
}
}
private void _verifyMehValue(ObjectMapper mapper, String json, Class<?> type) throws Exception {
try {
mapper.readValue(json, type);
fail("Should not pass");
} catch (InvalidTypeIdException e) {
verifyException(e, "Could not resolve type id");
verifyException(e, "`PolymorphicTypeValidator`");
verifyException(e, "denied resolution");
}
}
}